diff options
249 files changed, 2897 insertions, 1109 deletions
diff --git a/.gitlab/merge_request_templates/Change documentation location.md b/.gitlab/merge_request_templates/Change documentation location.md index c80af95d5e5..7dc80a641c4 100644 --- a/.gitlab/merge_request_templates/Change documentation location.md +++ b/.gitlab/merge_request_templates/Change documentation location.md @@ -22,7 +22,7 @@ https://docs.gitlab.com/ce/development/documentation/index.html#changing-documen - [ ] Make sure internal links pointing to the document in question are not broken. - [ ] Search and replace any links referring to old docs in GitLab Rails app, specifically under the `app/views/` and `ee/app/views` (for GitLab EE) directories. -- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ce/development/writing_documentation.html#redirections-for-pages-with-disqus-comments) +- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ce/development/documentation/index.html#redirections-for-pages-with-disqus-comments) to the new document if there are any Disqus comments on the old document thread. - [ ] Update the link in `features.yml` (if applicable) - [ ] If working on CE and the `ee-compat-check` jobs fails, submit an MR to EE @@ -431,7 +431,7 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 1.36.0', require: 'gitaly' +gem 'gitaly-proto', '~> 1.37.0', require: 'gitaly' gem 'grpc', '~> 1.19.0' diff --git a/Gemfile.lock b/Gemfile.lock index bf469de3835..beded888ffd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -310,7 +310,7 @@ GEM gettext_i18n_rails (>= 0.7.1) po_to_json (>= 1.0.0) rails (>= 3.2.0) - gitaly-proto (1.36.0) + gitaly-proto (1.37.0) grpc (~> 1.0) github-markup (1.7.0) gitlab-labkit (0.3.0) @@ -1119,7 +1119,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.3) - gitaly-proto (~> 1.36.0) + gitaly-proto (~> 1.37.0) github-markup (~> 1.7.0) gitlab-labkit (~> 0.3.0) gitlab-markup (~> 1.7.0) diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js index b23de36f860..dbc28beffbe 100644 --- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js +++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js @@ -36,7 +36,8 @@ export default function renderMermaid($els) { }); $els.each((i, el) => { - const source = el.textContent; + // Mermaid doesn't like `<br />` tags, so collapse all like tags into `<br>`, which is parsed correctly. + const source = el.textContent.replace(/<br\s*\/>/g, '<br>'); /** * Restrict the rendering to a certain amount of character to diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js index 052168bb21c..dce9c1a5410 100644 --- a/app/assets/javascripts/create_merge_request_dropdown.js +++ b/app/assets/javascripts/create_merge_request_dropdown.js @@ -182,7 +182,7 @@ export default class CreateMergeRequestDropdown { } enable() { - if (!canCreateConfidentialMergeRequest()) return; + if (isConfidentialIssue() && !canCreateConfidentialMergeRequest()) return; this.createMergeRequestButton.classList.remove('disabled'); this.createMergeRequestButton.removeAttribute('disabled'); diff --git a/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue b/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue index 6d908524da9..f0112a5a623 100644 --- a/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue +++ b/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue @@ -65,7 +65,7 @@ export default { <template> <div v-if="isFetchingMergeRequests || (!isFetchingMergeRequests && totalCount)"> - <div id="merge-requests" class="card-slim mt-3"> + <div id="merge-requests" class="card card-slim mt-3"> <div class="card-header"> <div class="card-title mt-0 mb-0 h5 merge-requests-title"> <span class="mr-1"> diff --git a/app/assets/javascripts/reports/components/issue_status_icon.vue b/app/assets/javascripts/reports/components/issue_status_icon.vue index 04fba43b2f3..386653b9444 100644 --- a/app/assets/javascripts/reports/components/issue_status_icon.vue +++ b/app/assets/javascripts/reports/components/issue_status_icon.vue @@ -16,7 +16,7 @@ export default { statusIconSize: { type: Number, required: false, - default: 32, + default: 24, }, }, computed: { diff --git a/app/assets/javascripts/reports/components/report_item.vue b/app/assets/javascripts/reports/components/report_item.vue index 2be9c37b00a..d477fafd3f5 100644 --- a/app/assets/javascripts/reports/components/report_item.vue +++ b/app/assets/javascripts/reports/components/report_item.vue @@ -27,7 +27,7 @@ export default { statusIconSize: { type: Number, required: false, - default: 32, + default: 24, }, isNew: { type: Boolean, @@ -43,12 +43,15 @@ export default { }; </script> <template> - <li :class="{ 'is-dismissed': issue.isDismissed }" class="report-block-list-issue"> + <li + :class="{ 'is-dismissed': issue.isDismissed }" + class="report-block-list-issue justify-content-center align-items-center" + > <issue-status-icon v-if="showReportSectionStatusIcon" :status="status" :status-icon-size="statusIconSize" - class="append-right-5" + class="append-right-default" /> <component :is="component" v-if="component" :issue="issue" :status="status" :is-new="isNew" /> diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue index 3d576caaf8f..9bc3e6388e3 100644 --- a/app/assets/javascripts/reports/components/report_section.vue +++ b/app/assets/javascripts/reports/components/report_section.vue @@ -165,8 +165,8 @@ export default { <template> <section class="media-section"> <div class="media"> - <status-icon :status="statusIconName" /> - <div class="media-body d-flex flex-align-self-center"> + <status-icon :status="statusIconName" :size="24" /> + <div class="media-body d-flex flex-align-self-center prepend-left-default"> <span class="js-code-text code-text"> {{ headerText }} <slot :name="slotName"></slot> diff --git a/app/assets/javascripts/reports/components/summary_row.vue b/app/assets/javascripts/reports/components/summary_row.vue index 97a68531d29..1caf52431e2 100644 --- a/app/assets/javascripts/reports/components/summary_row.vue +++ b/app/assets/javascripts/reports/components/summary_row.vue @@ -44,10 +44,16 @@ export default { }; </script> <template> - <div class="report-block-list-issue report-block-list-issue-parent"> - <div class="report-block-list-icon append-right-10 prepend-left-5"> - <gl-loading-icon v-if="statusIcon === 'loading'" css-class="report-block-list-loading-icon" /> - <ci-icon v-else :status="iconStatus" /> + <div + class="report-block-list-issue report-block-list-issue-parent justify-content-center align-items-center" + > + <div class="report-block-list-icon append-right-default"> + <gl-loading-icon + v-if="statusIcon === 'loading'" + css-class="report-block-list-loading-icon" + size="md" + /> + <ci-icon v-else :status="iconStatus" :size="24" /> </div> <div class="report-block-list-issue-description"> diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue index 67963dc1923..afb58a60155 100644 --- a/app/assets/javascripts/repository/components/breadcrumbs.vue +++ b/app/assets/javascripts/repository/components/breadcrumbs.vue @@ -1,12 +1,41 @@ <script> +import { GlDropdown, GlDropdownDivider, GlDropdownHeader, GlDropdownItem } from '@gitlab/ui'; +import { __ } from '../../locale'; +import Icon from '../../vue_shared/components/icon.vue'; import getRefMixin from '../mixins/get_ref'; import getProjectShortPath from '../queries/getProjectShortPath.query.graphql'; +import getProjectPath from '../queries/getProjectPath.query.graphql'; +import getPermissions from '../queries/getPermissions.query.graphql'; + +const ROW_TYPES = { + header: 'header', + divider: 'divider', +}; export default { + components: { + GlDropdown, + GlDropdownDivider, + GlDropdownHeader, + GlDropdownItem, + Icon, + }, apollo: { projectShortPath: { query: getProjectShortPath, }, + projectPath: { + query: getProjectPath, + }, + userPermissions: { + query: getPermissions, + variables() { + return { + projectPath: this.projectPath, + }; + }, + update: data => data.project.userPermissions, + }, }, mixins: [getRefMixin], props: { @@ -15,10 +44,52 @@ export default { required: false, default: '/', }, + canCollaborate: { + type: Boolean, + required: false, + default: false, + }, + canEditTree: { + type: Boolean, + required: false, + default: false, + }, + newBranchPath: { + type: String, + required: false, + default: null, + }, + newTagPath: { + type: String, + required: false, + default: null, + }, + newBlobPath: { + type: String, + required: false, + default: null, + }, + forkNewBlobPath: { + type: String, + required: false, + default: null, + }, + forkNewDirectoryPath: { + type: String, + required: false, + default: null, + }, + forkUploadBlobPath: { + type: String, + required: false, + default: null, + }, }, data() { return { projectShortPath: '', + projectPath: '', + userPermissions: {}, }; }, computed: { @@ -39,11 +110,112 @@ export default { [{ name: this.projectShortPath, path: '/', to: `/tree/${this.ref}/` }], ); }, + canCreateMrFromFork() { + return this.userPermissions.forkProject && this.userPermissions.createMergeRequestIn; + }, + dropdownItems() { + const items = []; + + if (this.canEditTree) { + items.push( + { + type: ROW_TYPES.header, + text: __('This directory'), + }, + { + attrs: { + href: this.newBlobPath, + class: 'qa-new-file-option', + }, + text: __('New file'), + }, + { + attrs: { + href: '#modal-upload-blob', + 'data-target': '#modal-upload-blob', + 'data-toggle': 'modal', + }, + text: __('Upload file'), + }, + { + attrs: { + href: '#modal-create-new-dir', + 'data-target': '#modal-create-new-dir', + 'data-toggle': 'modal', + }, + text: __('New directory'), + }, + ); + } else if (this.canCreateMrFromFork) { + items.push( + { + attrs: { + href: this.forkNewBlobPath, + 'data-method': 'post', + }, + text: __('New file'), + }, + { + attrs: { + href: this.forkUploadBlobPath, + 'data-method': 'post', + }, + text: __('Upload file'), + }, + { + attrs: { + href: this.forkNewDirectoryPath, + 'data-method': 'post', + }, + text: __('New directory'), + }, + ); + } + + if (this.userPermissions.pushCode) { + items.push( + { + type: ROW_TYPES.divider, + }, + { + type: ROW_TYPES.header, + text: __('This repository'), + }, + { + attrs: { + href: this.newBranchPath, + }, + text: __('New branch'), + }, + { + attrs: { + href: this.newTagPath, + }, + text: __('New tag'), + }, + ); + } + + return items; + }, + renderAddToTreeDropdown() { + return this.canCollaborate || this.canCreateMrFromFork; + }, }, methods: { isLast(i) { return i === this.pathLinks.length - 1; }, + getComponent(type) { + switch (type) { + case ROW_TYPES.divider: + return 'gl-dropdown-divider'; + case ROW_TYPES.header: + return 'gl-dropdown-header'; + default: + return 'gl-dropdown-item'; + } + }, }, }; </script> @@ -56,6 +228,20 @@ export default { {{ link.name }} </router-link> </li> + <li v-if="renderAddToTreeDropdown" class="breadcrumb-item"> + <gl-dropdown toggle-class="add-to-tree qa-add-to-tree ml-1"> + <template slot="button-content"> + <span class="sr-only">{{ __('Add to tree') }}</span> + <icon name="plus" :size="16" class="float-left" /> + <icon name="arrow-down" :size="16" class="float-left" /> + </template> + <template v-for="(item, i) in dropdownItems"> + <component :is="getComponent(item.type)" :key="i" v-bind="item.attrs"> + {{ item.text }} + </component> + </template> + </gl-dropdown> + </li> </ol> </nav> </template> diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js index ea051eaa414..f9727960040 100644 --- a/app/assets/javascripts/repository/index.js +++ b/app/assets/javascripts/repository/index.js @@ -5,6 +5,7 @@ import Breadcrumbs from './components/breadcrumbs.vue'; import LastCommit from './components/last_commit.vue'; import apolloProvider from './graphql'; import { setTitle } from './utils/title'; +import { parseBoolean } from '../lib/utils/common_utils'; export default function setupVueRepositoryList() { const el = document.getElementById('js-tree-list'); @@ -36,19 +37,42 @@ export default function setupVueRepositoryList() { .forEach(elem => elem.classList.toggle('hidden', !isRoot)); }); - // eslint-disable-next-line no-new - new Vue({ - el: document.getElementById('js-repo-breadcrumb'), - router, - apolloProvider, - render(h) { - return h(Breadcrumbs, { - props: { - currentPath: this.$route.params.pathMatch, - }, - }); - }, - }); + const breadcrumbEl = document.getElementById('js-repo-breadcrumb'); + + if (breadcrumbEl) { + const { + canCollaborate, + canEditTree, + newBranchPath, + newTagPath, + newBlobPath, + forkNewBlobPath, + forkNewDirectoryPath, + forkUploadBlobPath, + } = breadcrumbEl.dataset; + + // eslint-disable-next-line no-new + new Vue({ + el: breadcrumbEl, + router, + apolloProvider, + render(h) { + return h(Breadcrumbs, { + props: { + currentPath: this.$route.params.pathMatch, + canCollaborate: parseBoolean(canCollaborate), + canEditTree: parseBoolean(canEditTree), + newBranchPath, + newTagPath, + newBlobPath, + forkNewBlobPath, + forkNewDirectoryPath, + forkUploadBlobPath, + }, + }); + }, + }); + } // eslint-disable-next-line no-new new Vue({ diff --git a/app/assets/javascripts/repository/queries/getPermissions.query.graphql b/app/assets/javascripts/repository/queries/getPermissions.query.graphql new file mode 100644 index 00000000000..092fa44e2d0 --- /dev/null +++ b/app/assets/javascripts/repository/queries/getPermissions.query.graphql @@ -0,0 +1,9 @@ +query getPermissions($projectPath: ID!) { + project(fullPath: $projectPath) { + userPermissions { + pushCode + forkProject + createMergeRequestIn + } + } +} diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue index 4b57693e8f1..57d4d8b7ae6 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue @@ -14,6 +14,6 @@ export default { <template> <div class="circle-icon-container append-right-default align-self-start align-self-lg-center"> - <icon :name="name" /> + <icon :name="name" :size="24" /> </div> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue index f5fa68308bc..40c095aa954 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue @@ -96,16 +96,14 @@ export default { <template> <div class="ci-widget media js-ci-widget"> <template v-if="!hasPipeline || hasCIError"> - <div - class="add-border ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-default" - > - <icon :size="32" name="status_failed_borderless" /> + <div class="add-border ci-status-icon ci-status-icon-failed ci-error js-ci-error"> + <icon :size="24" name="status_failed_borderless" /> </div> - <div class="media-body" v-html="errorText"></div> + <div class="media-body prepend-left-default" v-html="errorText"></div> </template> <template v-else-if="hasPipeline"> <a :href="status.details_path" class="align-self-start append-right-default"> - <ci-icon :status="status" :size="32" :borderless="true" class="add-border" /> + <ci-icon :status="status" :size="24" :borderless="true" class="add-border" /> </a> <div class="ci-widget-container d-flex"> <div class="ci-widget-content"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue index 392eb6fb425..8dbd9e52cfe 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue @@ -32,8 +32,8 @@ export default { }; </script> <template> - <div class="space-children d-flex append-right-10 widget-status-icon"> - <div v-if="isLoading" class="mr-widget-icon"><gl-loading-icon size="md" /></div> + <div class="d-flex widget-status-icon"> + <div v-if="isLoading" class="mr-widget-icon"><gl-loading-icon size="sm" /></div> <ci-icon v-else :status="statusObj" :size="24" /> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue index 0312b147b62..01524f4b650 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue @@ -83,7 +83,7 @@ export default { <gl-button :aria-label="ariaLabel" variant="blank" - class="commit-edit-toggle square s24 mr-2" + class="commit-edit-toggle square s24 append-right-default" @click.stop="toggle()" > <icon :name="collapseIcon" :size="16" /> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue index 7312b31c01c..4d7d49398eb 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue @@ -18,7 +18,9 @@ export default { <template> <div class="mr-widget-body mr-widget-empty-state"> <div class="row"> - <div class="artwork col-md-5 order-md-last col-12 text-center"> + <div + class="artwork col-md-5 order-md-last col-12 text-center d-flex justify-content-center align-items-center" + > <span v-html="emptyStateSVG"></span> </div> <div class="text col-md-7 order-md-first col-12"> diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index 6bc5632365f..6f5a2e561af 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -104,7 +104,7 @@ } .btn { - @include transition(border-color); + @include transition(background-color, border-color, color, box-shadow); } .dropdown-menu-toggle, diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index e0b6da31261..767832e242c 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -24,11 +24,12 @@ border-radius: $border-radius-default; font-size: $gl-font-size; font-weight: $gl-font-weight-normal; - padding: $gl-bordered-btn-vert-padding $gl-bordered-btn-horz-padding; + padding: $gl-vert-padding $gl-btn-padding; &:focus, &:active { background-color: $btn-active-gray; + box-shadow: $gl-btn-active-background; } } @@ -49,89 +50,77 @@ color: $text; } - &:not(:disabled):not(.disabled) { - &:hover { - box-shadow: inset 0 0 0 1px $hover-border, 0 2px 2px 0 $gl-btn-hover-shadow-light; - } + &:hover, + &:focus { + background-color: $hover-background; + border-color: $hover-border; + color: $hover-text; - &:focus { - box-shadow: inset 0 0 0 1px $hover-border, 0 0 4px 1px $blue-300; + > .icon { + color: $hover-text; } + } - &:hover, - &:focus { - background-color: $hover-background; - border-color: $hover-border; - color: $hover-text; + &:focus { + box-shadow: 0 0 4px 1px $blue-300; + } - > .icon { - color: $hover-text; - } - } + &:active { + background-color: $active-background; + border-color: $active-border; + box-shadow: inset 0 2px 4px 0 rgba($black, 0.2); + color: $active-text; - &:active, - &:active:focus { - background-color: $active-background; - border-color: $active-border; - box-shadow: inset 0 0 0 1px $hover-border, inset 0 2px 4px 0 rgba($black, 0.2); + > .icon { color: $active-text; + } - > .icon { - color: $active-text; - } + &:focus { + box-shadow: inset 0 2px 4px 0 rgba($black, 0.2); } } } -@mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color, $hover-shadow-color: $gl-btn-hover-shadow-dark) { +@mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color) { background-color: $light; border-color: $border-light; color: $color; - &:not(:disabled):not(.disabled) { - &:hover { - box-shadow: inset 0 0 0 1px $border-normal, 0 2px 2px 0 $hover-shadow-color; - } - - &:focus { - box-shadow: inset 0 0 0 1px $border-normal, 0 0 4px 1px $blue-300; - } + &:hover, + &:focus { + background-color: $normal; + border-color: $border-normal; + color: $color; + } - &:hover, - &:focus { - background-color: $normal; - border-color: $border-normal; - color: $color; - } + &:active, + &.active { + box-shadow: $gl-btn-active-background; - &:active, - &.active { - box-shadow: inset 0 2px 4px 0 $gl-btn-hover-shadow-dark; - background-color: $dark; - border-color: $border-dark; - color: $color; - } + background-color: $dark; + border-color: $border-dark; + color: $color; } } @mixin btn-green { - @include btn-color($green-500, $green-600, $green-500, $green-700, $green-600, $green-800, $white-light); + @include btn-color($green-500, $green-600, $green-600, $green-700, $green-700, $green-800, $white-light); } @mixin btn-blue { - @include btn-color($blue-500, $blue-600, $blue-500, $blue-700, $blue-600, $blue-800, $white-light); + @include btn-color($blue-500, $blue-600, $blue-600, $blue-700, $blue-700, $blue-800, $white-light); } @mixin btn-orange { - @include btn-color($orange-500, $orange-600, $orange-500, $orange-700, $orange-600, $orange-800, $white-light); + @include btn-color($orange-500, $orange-600, $orange-600, $orange-700, $orange-700, $orange-800, $white-light); } @mixin btn-red { - @include btn-color($red-500, $red-600, $red-500, $red-700, $red-600, $red-800, $white-light); + @include btn-color($red-500, $red-600, $red-600, $red-700, $red-700, $red-800, $white-light); } @mixin btn-white { - @include btn-color($white-light, $gray-400, $gray-200, $gray-400, $gl-gray-200, $gray-500, $gl-text-color, $gl-btn-hover-shadow-light); + @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-gray-dark, $gl-text-color); } @mixin btn-with-margin { @@ -160,20 +149,21 @@ color: $gl-text-color; white-space: nowrap; - line-height: $gl-btn-line-height; &:focus:active { outline: 0; } - &.btn-xs { - font-size: $gl-btn-xs-font-size; - line-height: $gl-btn-xs-line-height; + &.btn-sm { + padding: 4px 10px; + font-size: $gl-btn-small-font-size; + line-height: $gl-btn-small-line-height; } - &.btn-sm, &.btn-xs { - padding: 3px $gl-bordered-btn-vert-padding; + padding: 2px $gl-btn-padding; + font-size: $gl-btn-xs-font-size; + line-height: $gl-btn-xs-line-height; } &.btn-success, @@ -249,7 +239,7 @@ &.dropdown-toggle { .fa-caret-down { - margin: 0; + margin-left: 3px; } } @@ -282,7 +272,10 @@ } svg { - @include btn-svg; + height: 15px; + width: 15px; + position: relative; + top: 2px; } svg, @@ -337,12 +330,6 @@ &.btn-grouped { @include btn-with-margin; } - - .btn { - border-radius: $border-radius-default; - font-size: $gl-font-size; - line-height: $gl-btn-line-height; - } } .btn-clipboard { @@ -500,25 +487,18 @@ &:active, &:focus { color: $gl-text-color-secondary; - border: 1px solid $border-gray-normal-dashed; background-color: $white-normal; } } -.btn-svg { - padding: $gl-bordered-btn-vert-padding; - - svg { - @include btn-svg; - display: block; - } +.btn-svg svg { + @include btn-svg; } // All disabled buttons, regardless of color, type, etc %disabled { background-color: $gray-light !important; border-color: $gray-200 !important; - box-shadow: none; color: $gl-text-color-disabled !important; opacity: 1 !important; cursor: default !important; diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 05afcecca05..29f63e9578d 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -8,6 +8,12 @@ } } +@mixin chevron-active { + .fa-chevron-down { + color: $gray-darkest; + } +} + @mixin set-visible { transform: translateY(0); display: block; @@ -43,6 +49,7 @@ .dropdown-toggle, .dropdown-menu-toggle { + @include chevron-active; border-color: $gray-darkest; } @@ -58,12 +65,12 @@ .dropdown-toggle, .confidential-merge-request-fork-group .dropdown-toggle { - padding: $gl-bordered-btn-vert-padding $gl-bordered-btn-horz-padding; + padding: 6px 8px 6px 10px; background-color: $white-light; color: $gl-text-color; font-size: 14px; - line-height: $gl-btn-line-height; text-align: left; + border: 1px solid $border-color; border-radius: $border-radius-base; white-space: nowrap; @@ -96,6 +103,10 @@ padding-right: 25px; } + .fa { + color: $gray-darkest; + } + .fa-chevron-down { font-size: $dropdown-chevron-size; position: relative; @@ -104,10 +115,12 @@ } &:hover { + @include chevron-active; border-color: $gray-darkest; } &:focus:active { + @include chevron-active; border-color: $dropdown-toggle-active-border-color; outline: 0; } diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss index 1be5ef276fd..7332c4981d2 100644 --- a/app/assets/stylesheets/framework/icons.scss +++ b/app/assets/stylesheets/framework/icons.scss @@ -88,8 +88,5 @@ display: flex; align-items: center; justify-content: center; - border: $border-size solid $gray-400; - border-radius: 50%; - padding: $gl-padding-8 - $border-size; color: $gray-700; } diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss index cd3d6f8297e..d9c93fed1c4 100644 --- a/app/assets/stylesheets/framework/panels.scss +++ b/app/assets/stylesheets/framework/panels.scss @@ -3,7 +3,6 @@ } .card-slim { - @extend .card; margin-bottom: $gl-vert-padding; } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 047a9799c3f..c108f45622f 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -405,8 +405,6 @@ $tanuki-yellow: #fca326; */ $green-500-focus: rgba($green-500, 0.4); $gl-btn-active-background: rgba(0, 0, 0, 0.16); -$gl-btn-hover-shadow-dark: rgba($black, 0.2); -$gl-btn-hover-shadow-light: rgba($black, 0.1); $gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background; /* @@ -483,8 +481,6 @@ $gl-btn-padding: 10px; $gl-btn-line-height: 16px; $gl-btn-vert-padding: 8px; $gl-btn-horz-padding: 12px; -$gl-bordered-btn-vert-padding: $gl-btn-vert-padding - 1px; -$gl-bordered-btn-horz-padding: $gl-btn-horz-padding - 1px; $gl-btn-small-font-size: 13px; $gl-btn-small-line-height: 18px; $gl-btn-xs-font-size: 13px; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index ffc6e433988..0b0a4e50146 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -167,7 +167,7 @@ min-width: 0; .project-namespace { - color: $gl-text-color-secondary; + color: $gl-text-color-tertiary; } } @@ -214,10 +214,10 @@ .label, .btn { - padding: $gl-bordered-btn-vert-padding $gl-bordered-btn-horz-padding; + padding: $gl-vert-padding $gl-btn-padding; border: 1px $border-color solid; font-size: $gl-font-size; - line-height: $gl-btn-line-height; + line-height: $line-height-base; border-radius: 0; display: flex; align-items: center; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 66ea70e79da..6a0127eb51c 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -929,6 +929,10 @@ margin: 0; } } + + .dropdown-toggle > .icon { + margin: 0 3px; + } } .right-sidebar-collapsed { diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index e51ca44476c..8359a60ec9f 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -267,6 +267,7 @@ ul.related-merge-requests > li { .fa-caret-down { pointer-events: none; color: inherit; + margin-left: 0; } } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 3917937f4af..2780afa11fa 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -10,8 +10,8 @@ float: left; } - > *:not(:last-child) { - margin-right: 10px; + > *:not(:first-child) { + margin-left: 10px; } } @@ -69,7 +69,7 @@ content: ''; border-left: 1px solid $gray-200; position: absolute; - left: 32px; + left: 28px; top: -17px; height: 16px; } @@ -114,7 +114,7 @@ padding: $gl-padding; @include media-breakpoint-up(md) { - padding-left: $gl-padding-50; + padding-left: $gl-padding-8 * 7; } } } @@ -264,6 +264,10 @@ .widget-status-icon { align-self: flex-start; + + button { + margin-left: $gl-padding; + } } .mr-widget-body { @@ -271,8 +275,8 @@ @include clearfix; - &.media > *:first-child { - margin-right: 10px; + button { + margin-left: $gl-padding; } .approve-btn { @@ -312,6 +316,7 @@ .bold { font-weight: $gl-font-weight-bold; color: $gl-gray-light; + margin-left: 10px; } .state-label { @@ -377,9 +382,13 @@ &.mr-widget-empty-state { line-height: 20px; + padding: $gl-padding; .artwork { - margin-bottom: $gl-padding; + + @include media-breakpoint-down(md) { + margin-bottom: $gl-padding; + } } .text { @@ -395,7 +404,7 @@ } .mr-widget-help { - padding: 10px 16px 10px $gl-padding-50; + padding: 10px 16px 10px ($gl-padding-8 * 7); font-style: italic; } @@ -913,7 +922,7 @@ .media-body { min-width: 0; font-size: 12px; - margin-left: 48px; + margin-left: 40px; } &:not(:last-child) { diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 1d57a4a4784..c6bac33e888 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -417,6 +417,7 @@ table { i { color: $white-light; + padding-right: 2px; margin-top: 2px; } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index aa6bbc8e473..ff4fa8aacdc 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -588,8 +588,8 @@ } .ci-status-icon svg { - height: 20px; - width: 20px; + height: 24px; + width: 24px; } .dropdown-menu-toggle { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 09a576c11cb..c80beceae52 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -303,7 +303,7 @@ .count-badge-count, .count-badge-button { - border: 1px solid $gray-400; + border: 1px solid $border-color; line-height: 1; } @@ -429,7 +429,7 @@ padding: 0; background: transparent; border: 0; - line-height: 2; + line-height: 34px; margin: 0; > li + li::before { @@ -792,6 +792,7 @@ .btn { margin-top: $gl-padding; + padding: $gl-btn-vert-padding $gl-btn-padding; line-height: $gl-btn-line-height; .icon { diff --git a/app/assets/stylesheets/pages/reports.scss b/app/assets/stylesheets/pages/reports.scss index 94da72622af..85e9f303dde 100644 --- a/app/assets/stylesheets/pages/reports.scss +++ b/app/assets/stylesheets/pages/reports.scss @@ -57,7 +57,7 @@ .report-block-container { border-top: 1px solid $border-color; - padding: $gl-padding-top; + padding: $gl-padding - 2; background-color: $gray-light; // Clean MR widget CSS @@ -96,17 +96,14 @@ .ci-status-icon { svg { - width: 16px; - height: 16px; - left: -2px; + width: 24px; + height: 24px; } } } .report-block-list-issue { display: flex; - align-items: flex-start; - align-content: flex-start; } .is-dismissed .report-block-list-issue-description, diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 5c732ab0d1f..5664f46484e 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -90,7 +90,7 @@ .add-to-tree { vertical-align: top; - padding: $gl-bordered-btn-vert-padding; + padding: 8px; svg { top: 0; diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb index 353a9806fd1..90528f75ffd 100644 --- a/app/controllers/boards/issues_controller.rb +++ b/app/controllers/boards/issues_controller.rb @@ -58,11 +58,8 @@ module Boards service = Boards::Issues::MoveService.new(board_parent, current_user, move_params(true)) issues = Issue.find(params[:ids]) - if service.execute_multiple(issues) - head :ok - else - head :unprocessable_entity - end + + render json: service.execute_multiple(issues) end def update diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 1bca52106fa..ccd54b369fa 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -160,20 +160,22 @@ class Projects::EnvironmentsController < Projects::ApplicationController end def metrics_dashboard - return render_403 unless Feature.enabled?(:environment_metrics_use_prometheus_endpoint, project) - - if Feature.enabled?(:environment_metrics_show_multiple_dashboards, project) + if Feature.enabled?(:gfm_embedded_metrics, project) && params[:embedded] result = dashboard_finder.find( project, current_user, environment, - dashboard_path: params[:dashboard], embedded: params[:embedded] ) + elsif Feature.enabled?(:environment_metrics_show_multiple_dashboards, project) + result = dashboard_finder.find( + project, + current_user, + environment, + dashboard_path: params[:dashboard] + ) - unless params[:embedded] - result[:all_dashboards] = dashboard_finder.find_all_paths(project) - end + result[:all_dashboards] = dashboard_finder.find_all_paths(project) else result = dashboard_finder.find(project, current_user, environment) end diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index fd94f07cc2c..64c5fae7d96 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -46,7 +46,7 @@ module DropdownsHelper def dropdown_toggle(toggle_text, data_attr, options = {}) default_label = data_attr[:default_label] - content_tag(:button, disabled: options[:disabled], class: "dropdown-menu-toggle btn #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do + content_tag(:button, disabled: options[:disabled], class: "dropdown-menu-toggle #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}") output << icon('chevron-down') output.html_safe diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index a3575462de0..bb1cdcb1b31 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -154,4 +154,36 @@ module TreeHelper "logs-path" => logs_path } end + + def breadcrumb_data_attributes + attrs = { + can_collaborate: can_collaborate_with_project?(@project).to_s, + new_blob_path: project_new_blob_path(@project, @id), + new_branch_path: new_project_branch_path(@project), + new_tag_path: new_project_tag_path(@project), + can_edit_tree: can_edit_tree?.to_s + } + + if can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project) + continue_param = { + to: project_new_blob_path(@project, @id), + notice: edit_in_new_fork_notice, + notice_now: edit_in_new_fork_notice_now + } + + attrs.merge!( + fork_new_blob_path: project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_param), + fork_new_directory_path: project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_param.merge({ + to: request.fullpath, + notice: _("%{edit_in_new_fork_notice} Try to create a new directory again.") % { edit_in_new_fork_notice: edit_in_new_fork_notice } + })), + fork_upload_blob_path: project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_param.merge({ + to: request.fullpath, + notice: _("%{edit_in_new_fork_notice} Try to upload a file again.") % { edit_in_new_fork_notice: edit_in_new_fork_notice } + })) + ) + end + + attrs + end end diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb index ba8cea0cea9..42d4e86fe8d 100644 --- a/app/models/ci/pipeline_schedule.rb +++ b/app/models/ci/pipeline_schedule.rb @@ -4,11 +4,8 @@ module Ci class PipelineSchedule < ApplicationRecord extend Gitlab::Ci::Model include Importable - include IgnorableColumn include StripAttribute - ignore_column :deleted_at - belongs_to :project belongs_to :owner, class_name: 'User' has_one :last_pipeline, -> { order(id: :desc) }, class_name: 'Ci::Pipeline' diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index 8927bb9bc18..8792c5cf98b 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -3,17 +3,15 @@ module Ci class Trigger < ApplicationRecord extend Gitlab::Ci::Model - include IgnorableColumn include Presentable - ignore_column :deleted_at - belongs_to :project belongs_to :owner, class_name: "User" has_many :trigger_requests validates :token, presence: true, uniqueness: true + validates :owner, presence: true, unless: :supports_legacy_tokens? before_validation :set_default_values @@ -37,8 +35,13 @@ module Ci self.owner_id.blank? end + def supports_legacy_tokens? + Feature.enabled?(:use_legacy_pipeline_triggers, project) + end + def can_access_project? - self.owner_id.blank? || Ability.allowed?(self.owner, :create_build, project) + supports_legacy_tokens? && legacy? || + Ability.allowed?(self.owner, :create_build, project) end end end diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb index f0256ff4d41..6ae8c3bd7f3 100644 --- a/app/models/clusters/applications/runner.rb +++ b/app/models/clusters/applications/runner.rb @@ -29,13 +29,6 @@ module Clusters content_values.to_yaml end - # Need to investigate if pipelines run by this runner will stop upon the - # executor pod stopping - # I.e.run a pipeline, and uninstall runner while pipeline is running - def allowed_to_uninstall? - false - end - def install_command Gitlab::Kubernetes::Helm::InstallCommand.new( name: name, @@ -47,6 +40,14 @@ module Clusters ) end + def prepare_uninstall + runner&.update!(active: false) + end + + def post_uninstall + runner.destroy! + end + private def ensure_runner diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb index 4514498b84b..803a65726d3 100644 --- a/app/models/clusters/concerns/application_core.rb +++ b/app/models/clusters/concerns/application_core.rb @@ -46,6 +46,16 @@ module Clusters command.version = version end end + + def prepare_uninstall + # Override if your application needs any action before + # being uninstalled by Helm + end + + def post_uninstall + # Override if your application needs any action after + # being uninstalled by Helm + end end end end diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb index 54a3dda6d75..342d766f723 100644 --- a/app/models/clusters/concerns/application_status.rb +++ b/app/models/clusters/concerns/application_status.rb @@ -59,29 +59,33 @@ module Clusters transition [:scheduled] => :uninstalling end - before_transition any => [:scheduled] do |app_status, _| - app_status.status_reason = nil + before_transition any => [:scheduled] do |application, _| + application.status_reason = nil end - before_transition any => [:errored] do |app_status, transition| + before_transition any => [:errored] do |application, transition| status_reason = transition.args.first - app_status.status_reason = status_reason if status_reason + application.status_reason = status_reason if status_reason end - before_transition any => [:updating] do |app_status, _| - app_status.status_reason = nil + before_transition any => [:updating] do |application, _| + application.status_reason = nil end - before_transition any => [:update_errored, :uninstall_errored] do |app_status, transition| + before_transition any => [:update_errored, :uninstall_errored] do |application, transition| status_reason = transition.args.first - app_status.status_reason = status_reason if status_reason + application.status_reason = status_reason if status_reason end - before_transition any => [:installed, :updated] do |app_status, _| + before_transition any => [:installed, :updated] do |application, _| # When installing any application we are also performing an update # of tiller (see Gitlab::Kubernetes::Helm::ClientCommand) so # therefore we need to reflect that in the database. - app_status.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION) + application.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION) + end + + after_transition any => [:uninstalling], :use_transactions => false do |application, _| + application.prepare_uninstall end end end diff --git a/app/models/concerns/stepable.rb b/app/models/concerns/stepable.rb new file mode 100644 index 00000000000..d00a049a004 --- /dev/null +++ b/app/models/concerns/stepable.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Stepable + extend ActiveSupport::Concern + + def steps + self.class._all_steps + end + + def execute_steps + initial_result = {} + + steps.inject(initial_result) do |previous_result, callback| + result = method(callback).call + + if result[:status] == :error + result[:failed_step] = callback + + break result + end + + previous_result.merge(result) + end + end + + class_methods do + def _all_steps + @_all_steps ||= [] + end + + def steps(*methods) + _all_steps.concat methods + end + end +end diff --git a/app/models/issue.rb b/app/models/issue.rb index 982a94315bd..12d30389910 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -13,11 +13,8 @@ class Issue < ApplicationRecord include RelativePositioning include TimeTrackable include ThrottledTouch - include IgnorableColumn include LabelEventable - ignore_column :assignee_id, :branch_name, :deleted_at - DueDateStruct = Struct.new(:title, :name).freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index ba57fefd8f1..68e6e48fb7d 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -7,7 +7,6 @@ class MergeRequest < ApplicationRecord include Noteable include Referable include Presentable - include IgnorableColumn include TimeTrackable include ManualInverseAssociation include EachBatch @@ -24,10 +23,6 @@ class MergeRequest < ApplicationRecord SORTING_PREFERENCE_FIELD = :merge_requests_sort - ignore_column :locked_at, - :ref_fetched, - :deleted_at - belongs_to :target_project, class_name: "Project" belongs_to :source_project, class_name: "Project" belongs_to :merge_user, class_name: "User" diff --git a/app/models/namespace.rb b/app/models/namespace.rb index b3021fab7f1..b8d7348268a 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -8,13 +8,10 @@ class Namespace < ApplicationRecord include AfterCommitQueue include Storage::LegacyNamespace include Gitlab::SQL::Pattern - include IgnorableColumn include FeatureGate include FromUnion include Gitlab::Utils::StrongMemoize - ignore_column :deleted_at - # Prevent users from creating unreasonably deep level of nesting. # The number 20 was taken based on maximum nesting level of # Android repo (15) + some extra backup. diff --git a/app/models/project.rb b/app/models/project.rb index f6f7d373f91..2906aca75fc 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -31,7 +31,6 @@ class Project < ApplicationRecord include FeatureGate include OptionallySearch include FromUnion - include IgnorableColumn extend Gitlab::Cache::RequestCache extend Gitlab::ConfigHelper @@ -56,8 +55,6 @@ class Project < ApplicationRecord VALID_MIRROR_PORTS = [22, 80, 443].freeze VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze - ignore_column :import_status, :import_jid, :import_error - cache_markdown_field :description, pipeline: :description delegate :feature_available?, :builds_enabled?, :wiki_enabled?, diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index a3b89b2543a..e571700fd02 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -250,7 +250,7 @@ class JiraService < IssueTrackerService end log_info("Successfully posted", client_url: client_url) - "SUCCESS: Successfully posted to http://jira.example.net." + "SUCCESS: Successfully posted to #{client_url}." end end diff --git a/app/policies/ci/trigger_policy.rb b/app/policies/ci/trigger_policy.rb index 209db44539c..578301d7f7e 100644 --- a/app/policies/ci/trigger_policy.rb +++ b/app/policies/ci/trigger_policy.rb @@ -5,7 +5,7 @@ module Ci delegate { @subject.project } with_options scope: :subject, score: 0 - condition(:legacy) { @subject.legacy? } + condition(:legacy) { @subject.supports_legacy_tokens? && @subject.legacy? } with_score 0 condition(:is_owner) { @user && @subject.owner_id == @user.id } diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb index 755d723b9a0..00ce27db7c8 100644 --- a/app/services/boards/issues/move_service.rb +++ b/app/services/boards/issues/move_service.rb @@ -11,26 +11,51 @@ module Boards end def execute_multiple(issues) - return false if issues.empty? + return execute_multiple_empty_result if issues.empty? + handled_issues = [] last_inserted_issue_id = nil - issues.map do |issue| + count = issues.each.inject(0) do |moved_count, issue| issue_modification_params = issue_params(issue) - next if issue_modification_params.empty? + next moved_count if issue_modification_params.empty? if last_inserted_issue_id - issue_modification_params[:move_between_ids] = move_between_ids({ move_after_id: nil, move_before_id: last_inserted_issue_id }) + issue_modification_params[:move_between_ids] = move_below(last_inserted_issue_id) end last_inserted_issue_id = issue.id - move_single_issue(issue, issue_modification_params) - end.all? + handled_issue = move_single_issue(issue, issue_modification_params) + handled_issues << present_issue_entity(handled_issue) if handled_issue + handled_issue && handled_issue.valid? ? moved_count + 1 : moved_count + end + + { + count: count, + success: count == issues.size, + issues: handled_issues + } end private + def present_issue_entity(issue) + ::API::Entities::Issue.represent(issue) + end + + def execute_multiple_empty_result + @execute_multiple_empty_result ||= { + count: 0, + success: false, + issues: [] + } + end + + def move_below(id) + move_between_ids({ move_after_id: nil, move_before_id: id }) + end + def move_single_issue(issue, issue_modification_params) - return false unless can?(current_user, :update_issue, issue) + return unless can?(current_user, :update_issue, issue) update(issue, issue_modification_params) end diff --git a/app/services/clusters/applications/check_uninstall_progress_service.rb b/app/services/clusters/applications/check_uninstall_progress_service.rb index 8786d295d6a..e51d84ef052 100644 --- a/app/services/clusters/applications/check_uninstall_progress_service.rb +++ b/app/services/clusters/applications/check_uninstall_progress_service.rb @@ -23,6 +23,7 @@ module Clusters private def on_success + app.post_uninstall app.destroy! rescue StandardError => e app.make_errored!(_('Application uninstalled but failed to destroy: %{error_message}') % { error_message: e.message }) diff --git a/app/services/self_monitoring/project/create_service.rb b/app/services/self_monitoring/project/create_service.rb new file mode 100644 index 00000000000..e5ef8c15456 --- /dev/null +++ b/app/services/self_monitoring/project/create_service.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +module SelfMonitoring + module Project + class CreateService < ::BaseService + include Stepable + + DEFAULT_VISIBILITY_LEVEL = Gitlab::VisibilityLevel::INTERNAL + DEFAULT_NAME = 'GitLab Instance Administration' + DEFAULT_DESCRIPTION = <<~HEREDOC + This project is automatically generated and will be used to help monitor this GitLab instance. + HEREDOC + + steps :validate_admins, + :create_project, + :add_project_members, + :add_prometheus_manual_configuration + + def initialize + super(nil) + end + + def execute + execute_steps + end + + private + + def validate_admins + unless instance_admins.any? + log_error('No active admin user found') + return error('No active admin user found') + end + + success + end + + def create_project + admin_user = project_owner + @project = ::Projects::CreateService.new(admin_user, create_project_params).execute + + if project.persisted? + success(project: project) + else + log_error("Could not create self-monitoring project. Errors: #{project.errors.full_messages}") + error('Could not create project') + end + end + + def add_project_members + members = project.add_users(project_maintainers, Gitlab::Access::MAINTAINER) + errors = members.flat_map { |member| member.errors.full_messages } + + if errors.any? + log_error("Could not add admins as members to self-monitoring project. Errors: #{errors}") + error('Could not add admins as members') + else + success + end + end + + def add_prometheus_manual_configuration + return success unless prometheus_enabled? + return success unless prometheus_listen_address.present? + + # TODO: Currently, adding the internal prometheus server as a manual configuration + # is only possible if the setting to allow webhooks and services to connect + # to local network is on. + # https://gitlab.com/gitlab-org/gitlab-ce/issues/44496 will add + # a whitelist that will allow connections to certain ips on the local network. + + service = project.find_or_initialize_service('prometheus') + + unless service.update(prometheus_service_attributes) + log_error("Could not save prometheus manual configuration for self-monitoring project. Errors: #{service.errors.full_messages}") + return error('Could not save prometheus manual configuration') + end + + success + end + + def prometheus_enabled? + Gitlab.config.prometheus.enable + rescue Settingslogic::MissingSetting + false + end + + def prometheus_listen_address + Gitlab.config.prometheus.listen_address + rescue Settingslogic::MissingSetting + end + + def instance_admins + @instance_admins ||= User.admins.active + end + + def project_owner + instance_admins.first + end + + def project_maintainers + # Exclude the first so that the project_owner is not added again as a member. + instance_admins - [project_owner] + end + + def create_project_params + { + initialize_with_readme: true, + visibility_level: DEFAULT_VISIBILITY_LEVEL, + name: DEFAULT_NAME, + description: DEFAULT_DESCRIPTION + } + end + + def internal_prometheus_listen_address_uri + if prometheus_listen_address.starts_with?('http') + prometheus_listen_address + else + 'http://' + prometheus_listen_address + end + end + + def prometheus_service_attributes + { + api_url: internal_prometheus_listen_address_uri, + manual_configuration: true, + active: true + } + end + end + end +end diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 034273558bb..074edf645ba 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -7,26 +7,26 @@ = render "devise/shared/error_messages", resource: resource .name.form-group = f.label :name, _('Full name'), class: 'label-bold' - = f.text_field :name, class: "form-control top qa-new-user-name js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length } }, required: true, title: _("This field is required.") + = f.text_field :name, class: "form-control top js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length }, :qa_selector => 'new_user_name_field' }, required: true, title: _("This field is required.") .username.form-group = f.label :username, class: 'label-bold' - = f.text_field :username, class: "form-control middle qa-new-user-username js-block-emoji js-validate-length js-validate-username", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length } }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.") + = f.text_field :username, class: "form-control middle js-block-emoji js-validate-length js-validate-username", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.") %p.validation-error.gl-field-error-ignore.field-validation.hide= _('Username is already taken.') %p.validation-success.gl-field-error-ignore.field-validation.hide= _('Username is available.') %p.validation-pending.gl-field-error-ignore.field-validation.hide= _('Checking username availability...') .form-group = f.label :email, class: 'label-bold' - = f.email_field :email, class: "form-control middle qa-new-user-email", required: true, title: _("Please provide a valid email address.") + = f.email_field :email, class: "form-control middle", data: { qa_selector: 'new_user_email_field' }, required: true, title: _("Please provide a valid email address.") .form-group = f.label :email_confirmation, class: 'label-bold' - = f.email_field :email_confirmation, class: "form-control middle qa-new-user-email-confirmation", required: true, title: _("Please retype the email address.") + = f.email_field :email_confirmation, class: "form-control middle", data: { qa_selector: 'new_user_email_confirmation_field' }, required: true, title: _("Please retype the email address.") .form-group.append-bottom-20#password-strength = f.label :password, class: 'label-bold' - = f.password_field :password, class: "form-control bottom qa-new-user-password", required: true, pattern: ".{#{@minimum_password_length},}", title: _("Minimum length is %{minimum_password_length} characters.") % { minimum_password_length: @minimum_password_length } + = f.password_field :password, class: "form-control bottom", data: { qa_selector: 'new_user_password_field' }, required: true, pattern: ".{#{@minimum_password_length},}", title: _("Minimum length is %{minimum_password_length} characters.") % { minimum_password_length: @minimum_password_length } %p.gl-field-hint.text-secondary= _('Minimum length is %{minimum_password_length} characters') % { minimum_password_length: @minimum_password_length } - if Gitlab::CurrentSettings.current_application_settings.enforce_terms? .form-group - = check_box_tag :terms_opt_in, '1', false, required: true, class: 'qa-new-user-accept-terms' + = check_box_tag :terms_opt_in, '1', false, required: true, data: { qa_selector: 'new_user_accept_terms_checkbox' } = label_tag :terms_opt_in do - terms_link = link_to s_("I accept the|Terms of Service and Privacy Policy"), terms_path, target: "_blank" - accept_terms_label = _("I accept the %{terms_link}") % { terms_link: terms_link } @@ -36,4 +36,4 @@ - if show_recaptcha_sign_up? = recaptcha_tags .submit-container - = f.submit _("Register"), class: "btn-register btn qa-new-user-register-button" + = f.submit _("Register"), class: "btn-register btn", data: { qa_selector: 'new_user_register_button' } diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml index 4cd03be572f..ab8c22532fd 100644 --- a/app/views/devise/shared/_tabs_normal.html.haml +++ b/app/views/devise/shared/_tabs_normal.html.haml @@ -3,4 +3,4 @@ %a.nav-link.qa-sign-in-tab.active{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in - if allow_signup? %li.nav-item{ role: 'presentation' } - %a.nav-link.qa-register-tab{ href: '#register-pane', data: { track_label: 'sign_in_register', track_property: 'sign_in', track_event: 'click_button', track_value: 'register', toggle: 'tab' }, role: 'tab' } Register + %a.nav-link.qa-register-tab{ href: '#register-pane', data: { track_label: 'sign_in_register', track_property: '', track_event: 'click_button', track_value: '', toggle: 'tab' }, role: 'tab' } Register diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml index dae9a7acf6b..5d57337a568 100644 --- a/app/views/doorkeeper/authorizations/new.html.haml +++ b/app/views/doorkeeper/authorizations/new.html.haml @@ -46,4 +46,4 @@ = hidden_field_tag :response_type, @pre_auth.response_type = hidden_field_tag :scope, @pre_auth.scope = hidden_field_tag :nonce, @pre_auth.nonce - = submit_tag _("Authorize"), class: "btn btn-success prepend-left-10" + = submit_tag _("Authorize"), class: "btn btn-success prepend-left-10", data: { qa_selector: 'authorization_button' } diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index a5f57f5893c..c62dce880c0 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -2,7 +2,7 @@ - group_data_attrs = { group_path: j(@group.path), name: j(@group.name), issues_path: issues_group_path(@group), mr_path: merge_requests_group_path(@group) } - if @project && @project.persisted? - project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: project_issues_path(@project), mr_path: project_merge_requests_path(@project), issues_disabled: !@project.issues_enabled? } -.search.search-form{ data: { track_label: "navbar_search", track_event: "activate_form_input" } } +.search.search-form{ data: { track_label: "navbar_search", track_event: "activate_form_input", track_value: "" } } = form_tag search_path, method: :get, class: 'form-inline' do |f| .search-input-container .search-input-wrap diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index fc2dea25c77..89f99472270 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -64,7 +64,7 @@ .dropdown-menu.dropdown-menu-right = render 'layouts/header/help_dropdown' - if header_link?(:user_dropdown) - %li.nav-item.header-user.dropdown{ data: { track_label: "profile_dropdown", track_event: "click_dropdown", qa_selector: 'user_menu' } } + %li.nav-item.header-user.dropdown{ data: { track_label: "profile_dropdown", track_event: "click_dropdown", track_value: "", qa_selector: 'user_menu' } } = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do = image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar" = sprite_icon('angle-down', css_class: 'caret-down') diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml index 1d7a501e5c2..e28efb09be5 100644 --- a/app/views/layouts/header/_new_dropdown.haml +++ b/app/views/layouts/header/_new_dropdown.haml @@ -1,4 +1,4 @@ -%li.header-new.dropdown{ data: { track_label: "new_dropdown", track_event: "click_dropdown" } } +%li.header-new.dropdown{ data: { track_label: "new_dropdown", track_event: "click_dropdown", track_value: "" } } = link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", id: "js-onboarding-new-project-link", title: _("New..."), ref: 'tooltip', aria: { label: _("New...") }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body', display: 'static' } do = sprite_icon('plus-square', size: 16) = sprite_icon('angle-down', css_class: 'caret-down') diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 54028dc8554..cbe713b7468 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -2,7 +2,7 @@ -# https://gitlab.com/gitlab-org/gitlab-ce/issues/49713 for more information. %ul.list-unstyled.navbar-sub-nav - if dashboard_nav_link?(:projects) - = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown", data: { track_label: "projects_dropdown", track_event: "click_dropdown" } }) do + = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown", data: { track_label: "projects_dropdown", track_event: "click_dropdown", track_value: "" } }) do %button.btn{ type: 'button', data: { toggle: "dropdown" } } = _('Projects') = sprite_icon('angle-down', css_class: 'caret-down') @@ -10,7 +10,7 @@ = render "layouts/nav/projects_dropdown/show" - if dashboard_nav_link?(:groups) - = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { id: 'nav-groups-dropdown', class: "home dropdown header-groups qa-groups-dropdown", data: { track_label: "groups_dropdown", track_event: "click_dropdown" } }) do + = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { id: 'nav-groups-dropdown', class: "home dropdown header-groups qa-groups-dropdown", data: { track_label: "groups_dropdown", track_event: "click_dropdown", track_value: "" } }) do %button.btn{ type: 'button', data: { toggle: "dropdown" } } = _('Groups') = sprite_icon('angle-down', css_class: 'caret-down') diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml index 6763513f9ae..95fdad125a7 100644 --- a/app/views/projects/_files.html.haml +++ b/app/views/projects/_files.html.haml @@ -20,6 +20,9 @@ - if vue_file_list_enabled? #js-tree-list{ data: { project_path: @project.full_path, project_short_path: @project.path, ref: ref, full_name: @project.name_with_namespace } } + - if can_edit_tree? + = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post + = render 'projects/blob/new_dir' - if @tree.readme = render "projects/tree/readme", readme: @tree.readme - else diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml index 7541737f79c..5d88be0925e 100644 --- a/app/views/projects/_new_project_fields.html.haml +++ b/app/views/projects/_new_project_fields.html.haml @@ -54,7 +54,7 @@ .form-group.row.initialize-with-readme-setting %div{ :class => "col-sm-12" } .form-check - = check_box_tag 'project[initialize_with_readme]', '1', false, class: 'form-check-input qa-initialize-with-readme-checkbox', data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "init_with_readme" } + = check_box_tag 'project[initialize_with_readme]', '1', false, class: 'form-check-input qa-initialize-with-readme-checkbox', data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "init_with_readme", track_value: "" } = label_tag 'project[initialize_with_readme]', class: 'form-check-label' do .option-title %strong= s_('ProjectsNew|Initialize repository with a README') @@ -62,4 +62,4 @@ = s_('ProjectsNew|Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository.') = f.submit _('Create project'), class: "btn btn-success project-submit", data: { track_label: "#{track_label}", track_event: "click_button", track_property: "create_project", track_value: "" } -= link_to _('Cancel'), dashboard_projects_path, class: 'btn btn-cancel', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "cancel" } += link_to _('Cancel'), dashboard_projects_path, class: 'btn btn-cancel', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "cancel", track_value: "" } diff --git a/app/views/projects/issues/import_csv/_button.html.haml b/app/views/projects/issues/import_csv/_button.html.haml index 8442a53ed61..acc2c50294f 100644 --- a/app/views/projects/issues/import_csv/_button.html.haml +++ b/app/views/projects/issues/import_csv/_button.html.haml @@ -1,6 +1,6 @@ - type = local_assigns.fetch(:type, :icon) -%button.csv-import-button.btn.btn-svg{ title: _('Import CSV'), class: ('has-tooltip' if type == :icon), +%button.csv-import-button.btn{ title: _('Import CSV'), class: ('has-tooltip' if type == :icon), data: { toggle: 'modal', target: '.issues-import-modal' } } - if type == :icon = sprite_icon('upload') diff --git a/app/views/projects/issues/import_csv/_modal.html.haml b/app/views/projects/issues/import_csv/_modal.html.haml index 86bc54786ad..fe4a4236896 100644 --- a/app/views/projects/issues/import_csv/_modal.html.haml +++ b/app/views/projects/issues/import_csv/_modal.html.haml @@ -20,5 +20,5 @@ = _('It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected.') = _('The maximum file size allowed is %{size}.') % { size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes) } .modal-footer - %button{ type: 'submit', class: 'btn btn-success', title: _('Import issues'), data: { track_label: "export_issues_csv", track_event: "click_button"} } + %button{ type: 'submit', class: 'btn btn-success', title: _('Import issues'), data: { track_label: "export_issues_csv", track_event: "click_button", track_value: ""} } = _('Import issues') diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 33de0aa153b..fabe636b05c 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -32,15 +32,15 @@ .col-lg-9.js-toggle-container %ul.nav.nav-tabs.nav-links.gitlab-tabs{ role: 'tablist' } %li.nav-item{ role: 'presentation' } - %a.nav-link.active{ href: '#blank-project-pane', id: 'blank-project-tab', data: { toggle: 'tab', track_label: 'blank_project', track_event: "click_tab" }, role: 'tab' } + %a.nav-link.active{ href: '#blank-project-pane', id: 'blank-project-tab', data: { toggle: 'tab', track_label: 'blank_project', track_event: "click_tab", track_value: "" }, role: 'tab' } %span.d-none.d-sm-block= s_('ProjectsNew|Blank project') %span.d-block.d-sm-none= s_('ProjectsNew|Blank') %li.nav-item{ role: 'presentation' } - %a.nav-link{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab', track_label: 'create_from_template', track_event: "click_tab" }, role: 'tab' } + %a.nav-link{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab', track_label: 'create_from_template', track_event: "click_tab", track_value: "" }, role: 'tab' } %span.d-none.d-sm-block.qa-project-create-from-template-tab= s_('ProjectsNew|Create from template') %span.d-block.d-sm-none= s_('ProjectsNew|Template') %li.nav-item{ role: 'presentation' } - %a.nav-link{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab', track_label: 'import_project', track_event: "click_tab" }, role: 'tab' } + %a.nav-link{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab', track_label: 'import_project', track_event: "click_tab", track_value: "" }, role: 'tab' } %span.d-none.d-sm-block= s_('ProjectsNew|Import project') %span.d-block.d-sm-none= s_('ProjectsNew|Import') = render_if_exists 'projects/new_ci_cd_only_project_tab', active_tab: active_tab @@ -51,7 +51,7 @@ = render 'new_project_fields', f: f, project_name_id: "blank-project-name" #create-from-template-pane.tab-pane.js-toggle-container.px-0.pb-0{ class: active_when(active_tab == 'template'), role: 'tabpanel' } - .card-slim.m-4.p-4 + .card.card-slim.m-4.p-4 %div - contributing_templates_url = 'https://gitlab.com/gitlab-org/project-templates/contributing' - link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: contributing_templates_url } diff --git a/app/views/projects/project_templates/_built_in_templates.html.haml b/app/views/projects/project_templates/_built_in_templates.html.haml index 6159f1c3542..d1c09e83fd3 100644 --- a/app/views/projects/project_templates/_built_in_templates.html.haml +++ b/app/views/projects/project_templates/_built_in_templates.html.haml @@ -9,9 +9,9 @@ .text-muted = template.description .controls.d-flex.align-items-center - %a.btn.btn-default.append-right-10{ href: template.preview, rel: 'noopener noreferrer', target: '_blank', data: { track_label: "create_from_template", track_property: "template_preview", track_event: "click_button", track_value: template.name } } + %a.btn.btn-default.append-right-10{ href: template.preview, rel: 'noopener noreferrer', target: '_blank', data: { track_label: "template_preview", track_property: template.name, track_event: "click_button", track_value: "" } } = _("Preview") %label.btn.btn-success.template-button.choose-template.append-bottom-0{ for: template.name } - %input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name, data: { track_label: "create_from_template", track_property: "template_use", track_event: "click_button" } } + %input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name, data: { track_label: "template_use", track_property: template.name, track_event: "click_button", track_value: "" } } %span = _("Use template") diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index 1d0bc588c9c..41cd044a5b0 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -11,7 +11,7 @@ - addtotree_toggle_attributes = { title: _("You can only add files when you are on a branch"), data: { container: 'body' }, class: 'disabled has-tooltip' } - if vue_file_list_enabled? - #js-repo-breadcrumb + #js-repo-breadcrumb{ data: breadcrumb_data_attributes } - else %ul.breadcrumb.repo-breadcrumb %li.breadcrumb-item diff --git a/app/views/projects/triggers/_content.html.haml b/app/views/projects/triggers/_content.html.haml index 96a41aa066c..e686068657c 100644 --- a/app/views/projects/triggers/_content.html.haml +++ b/app/views/projects/triggers/_content.html.haml @@ -1,8 +1,9 @@ -%p.append-bottom-default - Triggers with the - %span.badge.badge-primary legacy - label do not have an associated user and only have access to the current project. - %br - = succeed '.' do - Learn more in the - = link_to 'triggers documentation', help_page_path('ci/triggers/README'), target: '_blank' +- if Feature.enabled?(:use_legacy_pipeline_triggers, @project) + %p.append-bottom-default + Triggers with the + %span.badge.badge-primary legacy + label do not have an associated user and only have access to the current project. + %br + = succeed '.' do + Learn more in the + = link_to 'triggers documentation', help_page_path('ci/triggers/README'), target: '_blank' diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml index 6f6f1e5e0c5..31a598ccd5e 100644 --- a/app/views/projects/triggers/_trigger.html.haml +++ b/app/views/projects/triggers/_trigger.html.haml @@ -8,8 +8,11 @@ .label-container - if trigger.legacy? - %span.badge.badge-primary.has-tooltip{ title: "Trigger makes use of deprecated functionality" } legacy - - if !trigger.can_access_project? + - if trigger.supports_legacy_tokens? + %span.badge.badge-primary.has-tooltip{ title: "Trigger makes use of deprecated functionality" } legacy + - else + %span.badge.badge-danger.has-tooltip{ title: "Trigger is invalid due to being a legacy trigger. We recommend replacing it with a new trigger" } invalid + - elsif !trigger.can_access_project? %span.badge.badge-danger.has-tooltip{ title: "Trigger user has insufficient permissions to project" } invalid %td diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml index 342fdb20d41..82ffdc9cd13 100644 --- a/app/views/shared/_visibility_radios.html.haml +++ b/app/views/shared/_visibility_radios.html.haml @@ -4,7 +4,7 @@ - next if disallowed || restricted .form-check - = form.radio_button model_method, level, checked: (selected_level == level), class: 'form-check-input', data: { track_label: "blank_project", track_event: "activate_form_input", track_property: "#{model_method}", track_value: "#{level}" } + = form.radio_button model_method, level, checked: (selected_level == level), class: 'form-check-input', data: { track_label: "blank_project", track_event: "activate_form_input", track_property: "#{model_method}_#{level}", track_value: "" } = form.label "#{model_method}_#{level}", class: 'form-check-label' do = visibility_level_icon(level) .option-title diff --git a/app/views/shared/issuable/_feed_buttons.html.haml b/app/views/shared/issuable/_feed_buttons.html.haml index c9506a3295c..83f60fa6fe2 100644 --- a/app/views/shared/issuable/_feed_buttons.html.haml +++ b/app/views/shared/issuable/_feed_buttons.html.haml @@ -1,4 +1,4 @@ -= link_to safe_params.merge(rss_url_options), class: 'btn btn-svg has-tooltip js-rss-button', data: { container: 'body' }, title: _('Subscribe to RSS feed') do - = sprite_icon('rss') -= link_to safe_params.merge(calendar_url_options), class: 'btn btn-svg has-tooltip', data: { container: 'body' }, title: _('Subscribe to calendar') do - = sprite_icon('calendar') += link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: _('Subscribe to RSS feed') do + = icon('rss') += link_to safe_params.merge(calendar_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: _('Subscribe to calendar') do + = custom_icon('icon_calendar') diff --git a/changelogs/unreleased/60666-kubernetes-applications-uninstall-runner.yml b/changelogs/unreleased/60666-kubernetes-applications-uninstall-runner.yml new file mode 100644 index 00000000000..3632c8eec20 --- /dev/null +++ b/changelogs/unreleased/60666-kubernetes-applications-uninstall-runner.yml @@ -0,0 +1,5 @@ +--- +title: Allow GitLab Runner to be uninstalled from the UI +merge_request: 30176 +author: +type: added diff --git a/changelogs/unreleased/61145-fix-button-dimensions.yml b/changelogs/unreleased/61145-fix-button-dimensions.yml deleted file mode 100644 index 8f209ceaa8e..00000000000 --- a/changelogs/unreleased/61145-fix-button-dimensions.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Updating button dimensions according to design spec -merge_request: 28545 -author: -type: fixed diff --git a/changelogs/unreleased/61342-commit-search-result-doesn-t-pass-wcag-color-audit.yml b/changelogs/unreleased/61342-commit-search-result-doesn-t-pass-wcag-color-audit.yml new file mode 100644 index 00000000000..f4ed4551aab --- /dev/null +++ b/changelogs/unreleased/61342-commit-search-result-doesn-t-pass-wcag-color-audit.yml @@ -0,0 +1,5 @@ +--- +title: Change color for namespace in commit search +merge_request: 30312 +author: +type: other diff --git a/changelogs/unreleased/61613-spacing-mr-widgets.yml b/changelogs/unreleased/61613-spacing-mr-widgets.yml new file mode 100644 index 00000000000..7d37ef8da2e --- /dev/null +++ b/changelogs/unreleased/61613-spacing-mr-widgets.yml @@ -0,0 +1,5 @@ +--- +title: Left align mr widget icons and text +merge_request: 28561 +author: +type: fixed diff --git a/changelogs/unreleased/64070-asciidoctor-enable-section-anchors.yml b/changelogs/unreleased/64070-asciidoctor-enable-section-anchors.yml new file mode 100644 index 00000000000..51c1537a159 --- /dev/null +++ b/changelogs/unreleased/64070-asciidoctor-enable-section-anchors.yml @@ -0,0 +1,5 @@ +--- +title: "Enable section anchors in Asciidoctor" +merge_request: 30666 +author: Guillaume Grossetie +type: added
\ No newline at end of file diff --git a/changelogs/unreleased/bjk-fix_prom_example.yml b/changelogs/unreleased/bjk-fix_prom_example.yml new file mode 100644 index 00000000000..2f81bc6196b --- /dev/null +++ b/changelogs/unreleased/bjk-fix_prom_example.yml @@ -0,0 +1,5 @@ +--- +title: Update example Prometheus scrape config +merge_request: 30739 +author: +type: other diff --git a/changelogs/unreleased/button-bug-fixes.yml b/changelogs/unreleased/button-bug-fixes.yml deleted file mode 100644 index b63bfdf24ad..00000000000 --- a/changelogs/unreleased/button-bug-fixes.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix Project Badge Button Styles -merge_request: 30678 -author: -type: fixed diff --git a/changelogs/unreleased/jc-remove-catfile-flag.yml b/changelogs/unreleased/jc-remove-catfile-flag.yml new file mode 100644 index 00000000000..6b72de7bc45 --- /dev/null +++ b/changelogs/unreleased/jc-remove-catfile-flag.yml @@ -0,0 +1,5 @@ +--- +title: Remove catfile cache feature flag +merge_request: 30750 +author: +type: performance diff --git a/changelogs/unreleased/mh-mermaid-linebreaks.yml b/changelogs/unreleased/mh-mermaid-linebreaks.yml new file mode 100644 index 00000000000..e38820d8ce3 --- /dev/null +++ b/changelogs/unreleased/mh-mermaid-linebreaks.yml @@ -0,0 +1,5 @@ +--- +title: Fix linebreak rendering in Mermaid flowcharts +merge_request: 30730 +author: +type: fixed diff --git a/changelogs/unreleased/remove-support-for-legacy-pipeline-triggers.yml b/changelogs/unreleased/remove-support-for-legacy-pipeline-triggers.yml new file mode 100644 index 00000000000..3f4d4bbd432 --- /dev/null +++ b/changelogs/unreleased/remove-support-for-legacy-pipeline-triggers.yml @@ -0,0 +1,5 @@ +--- +title: Remove support for legacy pipeline triggers +merge_request: 30133 +author: +type: removed diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 334c241bcaa..0e78980350f 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -952,6 +952,16 @@ production: &base # address: localhost # port: 3807 + ## Prometheus settings + # Do not modify these settings here. They should be modified in /etc/gitlab/gitlab.rb + # if you installed GitLab via Omnibus. + # If you installed from source, you need to install and configure Prometheus + # yourself, and then update the values here. + # https://docs.gitlab.com/ee/administration/monitoring/prometheus/ + prometheus: + # enable: true + # listen_address: 'localhost:9090' + # # 5. Extra customization # ========================== @@ -1158,6 +1168,9 @@ test: user_filter: '' group_base: 'ou=groups,dc=example,dc=com' admin_group: '' + prometheus: + enable: true + listen_address: 'localhost:9090' staging: <<: *base diff --git a/config/initializers/zz_metrics.rb b/config/initializers/zz_metrics.rb index 5aa6f73c5c5..4f309094447 100644 --- a/config/initializers/zz_metrics.rb +++ b/config/initializers/zz_metrics.rb @@ -53,7 +53,7 @@ def instrument_classes(instrumentation) instrumentation.instrument_methods(Banzai::Querying) instrumentation.instrument_instance_methods(Banzai::ObjectRenderer) - instrumentation.instrument_instance_methods(Banzai::Redactor) + instrumentation.instrument_instance_methods(Banzai::ReferenceRedactor) [Issuable, Mentionable, Participable].each do |klass| instrumentation.instrument_instance_methods(klass) diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile index ec494635f02..0c675cc4c9c 100644 --- a/danger/commit_messages/Dangerfile +++ b/danger/commit_messages/Dangerfile @@ -88,6 +88,19 @@ def lint_commit(commit) # rubocop:disable Metrics/AbcSize # We ignore revert commits as they are well structured by Git already return false if commit.message.start_with?('Revert "') + # Fail if a suggestion commit is used and squash is not enabled + if commit.message.start_with?('Apply suggestion to') + if gitlab.mr_json['squash'] + return false + else + fail_commit( + commit, + 'If you are applying suggestions, enable squash in the merge request and re-run the failed job' + ) + return true + end + end + failures = false subject, separator, details = commit.message.split("\n", 3) @@ -114,16 +127,6 @@ def lint_commit(commit) # rubocop:disable Metrics/AbcSize ) end - # Fail if a suggestion commit is used and squash is not enabled - if commit.message.start_with?('Apply suggestion to') && !gitlab.mr_json['squash'] - fail_commit( - commit, - 'If you are applying suggestions, squash needs to be enabled in the merge request' - ) - - failures = true - end - unless subject_starts_with_capital?(subject) fail_commit(commit, 'The commit subject must start with a capital letter') failures = true diff --git a/doc/administration/auth/README.md b/doc/administration/auth/README.md index d8094587d14..2fc9db0632e 100644 --- a/doc/administration/auth/README.md +++ b/doc/administration/auth/README.md @@ -1,19 +1,34 @@ --- comments: false +type: index --- -# Authentication and Authorization +# GitLab authentication and authorization GitLab integrates with the following external authentication and authorization -providers. +providers: -- [LDAP](ldap.md) Includes Active Directory, Apple Open Directory, Open LDAP, - and 389 Server +- [Auth0](../../integration/auth0.md) +- [Authentiq](authentiq.md) +- [Azure](../../integration/azure.md) +- [Bitbucket Cloud](../../integration/bitbucket.md) +- [CAS](../../integration/cas.md) +- [Crowd](../../integration/crowd.md) +- [Facebook](../../integration/facebook.md) +- [GitHub](../../integration/github.md) +- [GitLab.com](../../integration/gitlab.md) +- [Google](../../integration/google.md) +- [JWT](jwt.md) +- [Kerberos](../../integration/kerberos.md) +- [LDAP](ldap.md): Includes Active Directory, Apple Open Directory, Open LDAP, + and 389 Server. - [LDAP for GitLab EE](ldap-ee.md): LDAP additions to GitLab Enterprise Editions **(STARTER ONLY)** -- [OmniAuth](../../integration/omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google, - Bitbucket, Facebook, Shibboleth, Crowd, Azure, Authentiq ID, and JWT -- [CAS](../../integration/cas.md) Configure GitLab to sign in using CAS -- [SAML](../../integration/saml.md) Configure GitLab as a SAML 2.0 Service Provider -- [Okta](okta.md) Configure GitLab to sign in using Okta -- [Authentiq](authentiq.md): Enable the Authentiq OmniAuth provider for passwordless authentication -- [Smartcard](smartcard.md) Smartcard authentication **(PREMIUM ONLY)** + - [Google Secure LDAP](google_secure_ldap.md) +- [Okta](okta.md) +- [Salesforce](../../integration/salesforce.md) +- [SAML](../../integration/saml.md) +- [SAML for GitLab.com groups](../../user/group/saml_sso/index.md) **(SILVER ONLY)** +- [Shibboleth](../../integration/shibboleth.md) +- [Smartcard](smartcard.md) **(PREMIUM ONLY)** +- [Twitter](../../integration/twitter.md) +- [UltraAuth](../../integration/ultra_auth.md) diff --git a/doc/administration/auth/authentiq.md b/doc/administration/auth/authentiq.md index 835c97c0288..b84eca4ef0d 100644 --- a/doc/administration/auth/authentiq.md +++ b/doc/administration/auth/authentiq.md @@ -1,3 +1,7 @@ +--- +type: reference +--- + # Authentiq OmniAuth Provider To enable the Authentiq OmniAuth provider for passwordless authentication you must register an application with Authentiq. @@ -66,3 +70,15 @@ On the sign in page there should now be an Authentiq icon below the regular sign - If not they will be prompted to download the app and then follow the procedure above. If everything goes right, the user will be returned to GitLab and will be signed in. + +<!-- ## Troubleshooting + +Include any troubleshooting steps that you can foresee. If you know beforehand what issues +one might have when setting this up, or when something is changed, or on upgrading, it's +important to describe those, too. Think of things that may go wrong and include them here. +This is important to minimize requests for support, and to avoid doc comments with +questions that you know someone might ask. + +Each scenario can be a third-level heading, e.g. `### Getting error message X`. +If you have none to add when creating a doc, leave this section in place +but commented out to help encourage others to add to it in the future. --> diff --git a/doc/administration/auth/crowd.md b/doc/administration/auth/crowd.md index 86c7bad2ebf..ac63b4f2b97 100644 --- a/doc/administration/auth/crowd.md +++ b/doc/administration/auth/crowd.md @@ -1,5 +1,11 @@ +--- +type: reference +--- + # Atlassian Crowd OmniAuth Provider +Authenticate to GitLab using the Atlassian Crowd OmniAuth provider. + ## Configure a new Crowd application 1. Choose 'Applications' in the top menu, then 'Add application'. diff --git a/doc/administration/auth/google_secure_ldap.md b/doc/administration/auth/google_secure_ldap.md index 0e6d7ff1df1..55e6f53622c 100644 --- a/doc/administration/auth/google_secure_ldap.md +++ b/doc/administration/auth/google_secure_ldap.md @@ -1,3 +1,7 @@ +--- +type: reference +--- + # Google Secure LDAP **(CORE ONLY)** > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/46391) in GitLab 11.9. @@ -204,3 +208,15 @@ values obtained during the LDAP client configuration earlier: [reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure [restart]: ../restart_gitlab.md#installations-from-source + +<!-- ## Troubleshooting + +Include any troubleshooting steps that you can foresee. If you know beforehand what issues +one might have when setting this up, or when something is changed, or on upgrading, it's +important to describe those, too. Think of things that may go wrong and include them here. +This is important to minimize requests for support, and to avoid doc comments with +questions that you know someone might ask. + +Each scenario can be a third-level heading, e.g. `### Getting error message X`. +If you have none to add when creating a doc, leave this section in place +but commented out to help encourage others to add to it in the future. --> diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md index 320a65b665d..86dd398343b 100644 --- a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md +++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md @@ -1,15 +1,9 @@ --- -author: Chris Wilson -author_gitlab: MrChrisW -level: intermediary -article_type: admin guide -date: 2017-05-03 +type: howto --- # How to configure LDAP with GitLab CE -## Introduction - Managing a large number of users in GitLab can become a burden for system administrators. As an organization grows so do user accounts. Keeping these user accounts in sync across multiple enterprise applications often becomes a time consuming task. In this guide we will focus on configuring GitLab with Active Directory. [Active Directory](https://en.wikipedia.org/wiki/Active_Directory) is a popular LDAP compatible directory service provided by Microsoft, included in all modern Windows Server operating systems. @@ -268,3 +262,15 @@ have extended functionalities with LDAP, such as: - Multiple LDAP servers Read through the article on [LDAP for GitLab EE](../how_to_configure_ldap_gitlab_ee/index.md) **(STARTER ONLY)** for an overview. + +<!-- ## Troubleshooting + +Include any troubleshooting steps that you can foresee. If you know beforehand what issues +one might have when setting this up, or when something is changed, or on upgrading, it's +important to describe those, too. Think of things that may go wrong and include them here. +This is important to minimize requests for support, and to avoid doc comments with +questions that you know someone might ask. + +Each scenario can be a third-level heading, e.g. `### Getting error message X`. +If you have none to add when creating a doc, leave this section in place +but commented out to help encourage others to add to it in the future. --> diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ee/index.md b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/index.md index 2683950f143..366acb9ed3e 100644 --- a/doc/administration/auth/how_to_configure_ldap_gitlab_ee/index.md +++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/index.md @@ -1,16 +1,10 @@ --- -author: Chris Wilson -author_gitlab: MrChrisW -level: intermediary -article_type: admin guide -date: 2017-05-03 +type: howto --- # How to configure LDAP with GitLab EE **(STARTER ONLY)** -## Introduction - -The present article follows [How to Configure LDAP with GitLab CE](../how_to_configure_ldap_gitlab_ce/index.md). Make sure to read through it before moving forward. +This article expands on [How to Configure LDAP with GitLab CE](../how_to_configure_ldap_gitlab_ce/index.md). Make sure to read through it before moving forward. ## GitLab Enterprise Edition - LDAP features @@ -117,3 +111,15 @@ Integration of GitLab with Active Directory (LDAP) reduces the complexity of use It has the advantage of improving user permission controls, whilst easing the deployment of GitLab into an existing [IT environment](https://www.techopedia.com/definition/29199/it-infrastructure). GitLab EE offers advanced group management and multiple LDAP servers. With the assistance of the [GitLab Support](https://about.gitlab.com/support) team, setting up GitLab with an existing AD/LDAP solution will be a smooth and painless process. + +<!-- ## Troubleshooting + +Include any troubleshooting steps that you can foresee. If you know beforehand what issues +one might have when setting this up, or when something is changed, or on upgrading, it's +important to describe those, too. Think of things that may go wrong and include them here. +This is important to minimize requests for support, and to avoid doc comments with +questions that you know someone might ask. + +Each scenario can be a third-level heading, e.g. `### Getting error message X`. +If you have none to add when creating a doc, leave this section in place +but commented out to help encourage others to add to it in the future. --> diff --git a/doc/administration/auth/jwt.md b/doc/administration/auth/jwt.md index 7db22bdd5df..e6b3287ce60 100644 --- a/doc/administration/auth/jwt.md +++ b/doc/administration/auth/jwt.md @@ -1,3 +1,7 @@ +--- +type: reference +--- + # JWT OmniAuth provider To enable the JWT OmniAuth provider, you must register your application with JWT. @@ -70,3 +74,15 @@ will be redirected to GitLab and will be signed in. [reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure [restart GitLab]: ../restart_gitlab.md#installations-from-source + +<!-- ## Troubleshooting + +Include any troubleshooting steps that you can foresee. If you know beforehand what issues +one might have when setting this up, or when something is changed, or on upgrading, it's +important to describe those, too. Think of things that may go wrong and include them here. +This is important to minimize requests for support, and to avoid doc comments with +questions that you know someone might ask. + +Each scenario can be a third-level heading, e.g. `### Getting error message X`. +If you have none to add when creating a doc, leave this section in place +but commented out to help encourage others to add to it in the future. --> diff --git a/doc/administration/auth/ldap-ee.md b/doc/administration/auth/ldap-ee.md index 2afac23c20c..2f2ee8a27d3 100644 --- a/doc/administration/auth/ldap-ee.md +++ b/doc/administration/auth/ldap-ee.md @@ -1,3 +1,7 @@ +--- +type: reference +--- + # LDAP Additions in GitLab EE **(STARTER ONLY)** This is a continuation of the main [LDAP documentation](ldap.md), detailing LDAP diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md index 86e6be5f4fa..be05a4d63a7 100644 --- a/doc/administration/auth/ldap.md +++ b/doc/administration/auth/ldap.md @@ -1,3 +1,7 @@ +--- +type: reference +--- + <!-- If the change is EE-specific, put it in `ldap-ee.md`, NOT here. --> # LDAP @@ -494,6 +498,13 @@ be mandatory and clients cannot be authenticated with the TLS protocol. ## Troubleshooting +If a user account is blocked or unblocked due to the LDAP configuration, a +message will be logged to `application.log`. + +If there is an unexpected error during an LDAP lookup (configuration error, +timeout), the login is rejected and a message will be logged to +`production.log`. + ### Debug LDAP user filter with ldapsearch This example uses ldapsearch and assumes you are using ActiveDirectory. The @@ -527,18 +538,9 @@ ldapsearch -H ldaps://$host:$port -D "$bind_dn" -y bind_dn_password.txt -b "$ba sudo -u git -H bundle exec rake gitlab:ldap:check RAILS_ENV=production ``` -### Connection Refused +### Connection refused If you are getting 'Connection Refused' errors when trying to connect to the LDAP server please double-check the LDAP `port` and `encryption` settings used by GitLab. Common combinations are `encryption: 'plain'` and `port: 389`, OR `encryption: 'simple_tls'` and `port: 636`. - -### Troubleshooting - -If a user account is blocked or unblocked due to the LDAP configuration, a -message will be logged to `application.log`. - -If there is an unexpected error during an LDAP lookup (configuration error, -timeout), the login is rejected and a message will be logged to -`production.log`. diff --git a/doc/administration/auth/oidc.md b/doc/administration/auth/oidc.md index 758501629af..78d040cda99 100644 --- a/doc/administration/auth/oidc.md +++ b/doc/administration/auth/oidc.md @@ -1,3 +1,7 @@ +--- +type: reference +--- + # OpenID Connect OmniAuth provider GitLab can use [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) as an OmniAuth provider. @@ -146,7 +150,7 @@ for more details: } ``` -### Troubleshooting +## Troubleshooting If you're having trouble, here are some tips: diff --git a/doc/administration/auth/okta.md b/doc/administration/auth/okta.md index 566003ba708..5524c3ba092 100644 --- a/doc/administration/auth/okta.md +++ b/doc/administration/auth/okta.md @@ -1,3 +1,7 @@ +--- +type: reference +--- + # Okta SSO provider Okta is a [Single Sign-on provider](https://www.okta.com/products/single-sign-on/) that can be used to authenticate @@ -157,3 +161,15 @@ Make sure the groups exist and are assigned to the Okta app. You can take a look of the [SAML documentation](../../integration/saml.md#marking-users-as-external-based-on-saml-groups) on external groups since it works the same. + +<!-- ## Troubleshooting + +Include any troubleshooting steps that you can foresee. If you know beforehand what issues +one might have when setting this up, or when something is changed, or on upgrading, it's +important to describe those, too. Think of things that may go wrong and include them here. +This is important to minimize requests for support, and to avoid doc comments with +questions that you know someone might ask. + +Each scenario can be a third-level heading, e.g. `### Getting error message X`. +If you have none to add when creating a doc, leave this section in place +but commented out to help encourage others to add to it in the future. --> diff --git a/doc/administration/auth/smartcard.md b/doc/administration/auth/smartcard.md index e47751e0cc5..4f236d1afb8 100644 --- a/doc/administration/auth/smartcard.md +++ b/doc/administration/auth/smartcard.md @@ -1,3 +1,7 @@ +--- +type: reference +--- + # Smartcard authentication **(PREMIUM ONLY)** GitLab supports authentication using smartcards. @@ -22,7 +26,7 @@ To use a smartcard with an X.509 certificate to authenticate against a local database with GitLab, `CN` and `emailAddress` must be defined in the certificate. For example: -``` +```text Certificate: Data: Version: 1 (0x0) @@ -212,3 +216,15 @@ attribute. As a prerequisite, you must use an LDAP server that: 1. Save the file and [restart](../restart_gitlab.md#installations-from-source) GitLab for the changes to take effect. + +<!-- ## Troubleshooting + +Include any troubleshooting steps that you can foresee. If you know beforehand what issues +one might have when setting this up, or when something is changed, or on upgrading, it's +important to describe those, too. Think of things that may go wrong and include them here. +This is important to minimize requests for support, and to avoid doc comments with +questions that you know someone might ask. + +Each scenario can be a third-level heading, e.g. `### Getting error message X`. +If you have none to add when creating a doc, leave this section in place +but commented out to help encourage others to add to it in the future. --> diff --git a/doc/administration/geo/disaster_recovery/background_verification.md b/doc/administration/geo/disaster_recovery/background_verification.md index 8eee9427b56..27866b7536e 100644 --- a/doc/administration/geo/disaster_recovery/background_verification.md +++ b/doc/administration/geo/disaster_recovery/background_verification.md @@ -171,14 +171,21 @@ If the **primary** and **secondary** nodes have a checksum verification mismatch ## Current limitations -Until [issue #5064][ee-5064] is completed, background verification doesn't cover -CI job artifacts and traces, LFS objects, or user uploads in file storage. -Verify their integrity manually by following [these instructions][foreground-verification] -on both nodes, and comparing the output between them. +Automatic background verification doesn't cover attachments, LFS objects, +job artifacts, and user uploads in file storage. You can keep track of the +progress to include them in [ee-1430]. For now, you can verify their integrity +manually by following [these instructions][foreground-verification] on both +nodes, and comparing the output between them. + +In GitLab EE 12.1, Geo calculates checksums for attachments, LFS objects and +archived traces on secondary nodes after the transfer, compares it with the +stored checksums, and rejects transfers if mismatched. Please note that Geo +currently does not support an automatic way to verify these data if they have +been synced before GitLab EE 12.1. Data in object storage is **not verified**, as the object store is responsible for ensuring the integrity of the data. [reset-verification]: background_verification.md#reset-verification-for-projects-where-verification-has-failed [foreground-verification]: ../../raketasks/check.md -[ee-5064]: https://gitlab.com/gitlab-org/gitlab-ee/issues/5064 +[ee-1430]: https://gitlab.com/groups/gitlab-org/-/epics/1430 diff --git a/doc/administration/high_availability/README.md b/doc/administration/high_availability/README.md index 4317d14ba68..41ef68f5b57 100644 --- a/doc/administration/high_availability/README.md +++ b/doc/administration/high_availability/README.md @@ -1,3 +1,7 @@ +--- +type: reference, concepts +--- + # Scaling and High Availability GitLab supports several different types of clustering and high-availability. diff --git a/doc/administration/high_availability/alpha_database.md b/doc/administration/high_availability/alpha_database.md index 7bf20be60e6..7afd739f44c 100644 --- a/doc/administration/high_availability/alpha_database.md +++ b/doc/administration/high_availability/alpha_database.md @@ -2,5 +2,4 @@ redirect_to: 'database.md' --- -This documentation has been moved to the main -[database documentation](database.md#configure_using_omnibus_for_high_availability). +This document was moved to [another location](database.md). diff --git a/doc/administration/high_availability/consul.md b/doc/administration/high_availability/consul.md index 1f93c8130d3..b02a61b9256 100644 --- a/doc/administration/high_availability/consul.md +++ b/doc/administration/high_availability/consul.md @@ -1,6 +1,8 @@ -# Working with the bundled Consul service **(PREMIUM ONLY)** +--- +type: reference +--- -## Overview +# Working with the bundled Consul service **(PREMIUM ONLY)** As part of its High Availability stack, GitLab Premium includes a bundled version of [Consul](https://www.consul.io/) that can be managed through `/etc/gitlab/gitlab.rb`. diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md index 1702a731647..f7a1f425b40 100644 --- a/doc/administration/high_availability/database.md +++ b/doc/administration/high_availability/database.md @@ -1,5 +1,12 @@ +--- +type: reference +--- + # Configuring PostgreSQL for Scaling and High Availability +In this section, you'll be guided through configuring a PostgreSQL database +to be used with GitLab in a highly available environment. + ## Provide your own PostgreSQL instance **(CORE ONLY)** If you're hosting GitLab on a cloud provider, you can optionally use a diff --git a/doc/administration/high_availability/gitaly.md b/doc/administration/high_availability/gitaly.md index b7eaa4ce105..739d1ae35fb 100644 --- a/doc/administration/high_availability/gitaly.md +++ b/doc/administration/high_availability/gitaly.md @@ -1,3 +1,7 @@ +--- +type: reference +--- + # Configuring Gitaly for Scaled and High Availability Gitaly does not yet support full high availability. However, Gitaly is quite @@ -46,3 +50,15 @@ Continue configuration of other components by going back to: ``` 1. Run `sudo gitlab-ctl reconfigure` to compile the configuration. + +<!-- ## Troubleshooting + +Include any troubleshooting steps that you can foresee. If you know beforehand what issues +one might have when setting this up, or when something is changed, or on upgrading, it's +important to describe those, too. Think of things that may go wrong and include them here. +This is important to minimize requests for support, and to avoid doc comments with +questions that you know someone might ask. + +Each scenario can be a third-level heading, e.g. `### Getting error message X`. +If you have none to add when creating a doc, leave this section in place +but commented out to help encourage others to add to it in the future. --> diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md index 83838928519..8818a9606de 100644 --- a/doc/administration/high_availability/gitlab.md +++ b/doc/administration/high_availability/gitlab.md @@ -1,4 +1,8 @@ -# Configuring GitLab Scaling and High Availability +--- +type: reference +--- + +# Configuring GitLab for Scaling and High Availability > **Note:** There is some additional configuration near the bottom for additional GitLab application servers. It's important to read and understand diff --git a/doc/administration/high_availability/load_balancer.md b/doc/administration/high_availability/load_balancer.md index 28b226cacd5..9e9f604317a 100644 --- a/doc/administration/high_availability/load_balancer.md +++ b/doc/administration/high_availability/load_balancer.md @@ -1,3 +1,7 @@ +--- +type: reference +--- + # Load Balancer for GitLab HA In an active/active GitLab configuration, you will need a load balancer to route @@ -114,3 +118,15 @@ Read more on high-availability configuration: if SSL was terminated at the load balancer. [gitlab-pages]: ../pages/index.md + +<!-- ## Troubleshooting + +Include any troubleshooting steps that you can foresee. If you know beforehand what issues +one might have when setting this up, or when something is changed, or on upgrading, it's +important to describe those, too. Think of things that may go wrong and include them here. +This is important to minimize requests for support, and to avoid doc comments with +questions that you know someone might ask. + +Each scenario can be a third-level heading, e.g. `### Getting error message X`. +If you have none to add when creating a doc, leave this section in place +but commented out to help encourage others to add to it in the future. --> diff --git a/doc/administration/high_availability/monitoring_node.md b/doc/administration/high_availability/monitoring_node.md index cbc1d4bcd52..b91a994d01e 100644 --- a/doc/administration/high_availability/monitoring_node.md +++ b/doc/administration/high_availability/monitoring_node.md @@ -1,7 +1,13 @@ +--- +type: reference +--- + # Configuring a Monitoring node for Scaling and High Availability > [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/3786) in GitLab 12.0. +You can configure a Prometheus node to monitor GitLab. + ## Standalone Monitoring node using GitLab Omnibus The GitLab Omnibus package can be used to configure a standalone Monitoring node running [Prometheus](../monitoring/prometheus/index.md) and [Grafana](../monitoring/performance/grafana_configuration.md). @@ -67,3 +73,15 @@ Once monitoring using Service Discovery is enabled with `consul['monitoring_serv ensure that `prometheus['scrape_configs']` is not set in `/etc/gitlab/gitlab.rb`. Setting both `consul['monitoring_service_discovery'] = true` and `prometheus['scrape_configs']` in `/etc/gitlab/gitlab.rb` will result in errors. + +<!-- ## Troubleshooting + +Include any troubleshooting steps that you can foresee. If you know beforehand what issues +one might have when setting this up, or when something is changed, or on upgrading, it's +important to describe those, too. Think of things that may go wrong and include them here. +This is important to minimize requests for support, and to avoid doc comments with +questions that you know someone might ask. + +Each scenario can be a third-level heading, e.g. `### Getting error message X`. +If you have none to add when creating a doc, leave this section in place +but commented out to help encourage others to add to it in the future. --> diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md index 6ab6b8bed30..294f0e969d5 100644 --- a/doc/administration/high_availability/nfs.md +++ b/doc/administration/high_availability/nfs.md @@ -1,3 +1,7 @@ +--- +type: reference +--- + # NFS You can view information and options set for each of the mounted NFS file @@ -47,29 +51,15 @@ management between systems: ### Improving NFS performance with GitLab -NOTE: **Note:** This is only available starting in certain versions of GitLab: 11.5.11, -11.6.11, 11.7.12, 11.8.8, 11.9.0 and up (e.g. 11.10, 11.11, etc.) +NOTE: **Note:** From GitLab 12.1, it will automatically be detected if Rugged can and should be used per storage. -If you are using NFS to share Git data, we recommend that you enable a -number of feature flags that will allow GitLab application processes to -access Git data directly instead of going through the [Gitaly -service](../gitaly/index.md). Depending on your workload and disk -performance, these flags may help improve performance. See [the -issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/57317) for more -details. - -To do this, run the Rake task: +If you previously enabled Rugged using the feature flag, you will need to unset the feature flag by using: ```sh -sudo gitlab-rake gitlab:features:enable_rugged +sudo gitlab-rake gitlab:features:unset_rugged ``` -If you need to undo this setting for some reason such as switching to [Gitaly without NFS](gitaly.md) -(recommended), run: - -```sh -sudo gitlab-rake gitlab:features:disable_rugged -``` +If the Rugged feature flag is explicitly set to either true or false, GitLab will use the value explicitly set. ### Known issues @@ -236,3 +226,15 @@ Read more on high-availability configuration: 1. [Configure the load balancers](load_balancer.md) [udp-log-shipping]: https://docs.gitlab.com/omnibus/settings/logs.html#udp-log-shipping-gitlab-enterprise-edition-only "UDP log shipping" + +<!-- ## Troubleshooting + +Include any troubleshooting steps that you can foresee. If you know beforehand what issues +one might have when setting this up, or when something is changed, or on upgrading, it's +important to describe those, too. Think of things that may go wrong and include them here. +This is important to minimize requests for support, and to avoid doc comments with +questions that you know someone might ask. + +Each scenario can be a third-level heading, e.g. `### Getting error message X`. +If you have none to add when creating a doc, leave this section in place +but commented out to help encourage others to add to it in the future. --> diff --git a/doc/administration/high_availability/nfs_host_client_setup.md b/doc/administration/high_availability/nfs_host_client_setup.md index a8d69b9ab0a..9b0e085fe25 100644 --- a/doc/administration/high_availability/nfs_host_client_setup.md +++ b/doc/administration/high_availability/nfs_host_client_setup.md @@ -1,3 +1,7 @@ +--- +type: reference +--- + # Configuring NFS for GitLab HA Setting up NFS for a GitLab HA setup allows all applications nodes in a cluster @@ -133,3 +137,15 @@ client with the command below. ```sh sudo ufw allow from <client-ip-address> to any port nfs ``` + +<!-- ## Troubleshooting + +Include any troubleshooting steps that you can foresee. If you know beforehand what issues +one might have when setting this up, or when something is changed, or on upgrading, it's +important to describe those, too. Think of things that may go wrong and include them here. +This is important to minimize requests for support, and to avoid doc comments with +questions that you know someone might ask. + +Each scenario can be a third-level heading, e.g. `### Getting error message X`. +If you have none to add when creating a doc, leave this section in place +but commented out to help encourage others to add to it in the future. --> diff --git a/doc/administration/high_availability/pgbouncer.md b/doc/administration/high_availability/pgbouncer.md index 6890b0f7db7..0b945bc6244 100644 --- a/doc/administration/high_availability/pgbouncer.md +++ b/doc/administration/high_availability/pgbouncer.md @@ -1,6 +1,8 @@ -# Working with the bundle Pgbouncer service +--- +type: reference +--- -## Overview +# Working with the bundle Pgbouncer service As part of its High Availability stack, GitLab Premium includes a bundled version of [Pgbouncer](https://pgbouncer.github.io/) that can be managed through `/etc/gitlab/gitlab.rb`. diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md index c29514ed9f6..1b79dde9476 100644 --- a/doc/administration/high_availability/redis.md +++ b/doc/administration/high_availability/redis.md @@ -1,3 +1,7 @@ +--- +type: reference +--- + # Configuring Redis for Scaling and High Availability ## Provide your own Redis instance **(CORE ONLY)** diff --git a/doc/administration/high_availability/redis_source.md b/doc/administration/high_availability/redis_source.md index a5463e5128c..63915e5d96c 100644 --- a/doc/administration/high_availability/redis_source.md +++ b/doc/administration/high_availability/redis_source.md @@ -1,3 +1,7 @@ +--- +type: reference +--- + # Configuring non-Omnibus Redis for GitLab HA This is the documentation for configuring a Highly Available Redis setup when diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index 341ea3330d7..c8968c51393 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -134,17 +134,57 @@ To use an external Prometheus server: ```yaml scrape_configs: - - job_name: 'gitlab_exporters' + - job_name: nginx static_configs: - - targets: ['1.1.1.1:9168', '1.1.1.1:9236', '1.1.1.1:9236', '1.1.1.1:9100', '1.1.1.1:9121', '1.1.1.1:9187'] - - - job_name: 'gitlab_metrics' - metrics_path: /-/metrics + - targets: + - 1.1.1.1:8060 + - job_name: redis + static_configs: + - targets: + - 1.1.1.1:9121 + - job_name: postgres + static_configs: + - targets: + - 1.1.1.1:9187 + - job_name: node + static_configs: + - targets: + - 1.1.1.1:9100 + - job_name: gitlab-workhorse + static_configs: + - targets: + - 1.1.1.1:9229 + - job_name: gitlab-rails + metrics_path: "/-/metrics" + static_configs: + - targets: + - 1.1.1.1:8080 + - job_name: gitlab-sidekiq + static_configs: + - targets: + - 1.1.1.1:8082 + - job_name: gitlab_monitor_database + metrics_path: "/database" + static_configs: + - targets: + - 1.1.1.1:9168 + - job_name: gitlab_monitor_sidekiq + metrics_path: "/sidekiq" + static_configs: + - targets: + - 1.1.1.1:9168 + - job_name: gitlab_monitor_process + metrics_path: "/process" + static_configs: + - targets: + - 1.1.1.1:9168 + - job_name: gitaly static_configs: - - targets: ['1.1.1.1:443'] + - targets: + - 1.1.1.1:9236 ``` -1. Restart the Prometheus server. +1. Reload the Prometheus server. ## Viewing performance metrics diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md index 2b31233d429..8d0b5b42515 100644 --- a/doc/administration/raketasks/maintenance.md +++ b/doc/administration/raketasks/maintenance.md @@ -251,3 +251,22 @@ sudo gitlab-rake gitlab:exclusive_lease:clear[project_housekeeping:*] # to clear a lease for repository garbage collection in a specific project: (id=4) sudo gitlab-rake gitlab:exclusive_lease:clear[project_housekeeping:4] ``` + +## Display status of database migrations + +To check the status of migrations, you can use the following rake task: + +```bash +sudo gitlab-rake db:migrate:status +``` + +This will output a table with a `Status` of `up` or `down` for +each Migration ID. + +```bash +database: gitlabhq_production + + Status Migration ID Migration Name +-------------------------------------------------- + up migration_id migration_name +```
\ No newline at end of file diff --git a/doc/api/README.md b/doc/api/README.md index 9d90677e2bb..8e60d1c61df 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -29,6 +29,7 @@ The following API resources are available in the project context: | [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` | | [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` | | [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) | +| [Dependencies](dependencies.md) **[ULTIMATE]** | `/projects/:id/dependencies` | [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) | | [Deployments](deployments.md) | `/projects/:id/deployments` | | [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` (also available for groups) | diff --git a/doc/api/dependencies.md b/doc/api/dependencies.md new file mode 100644 index 00000000000..ed5ebdade19 --- /dev/null +++ b/doc/api/dependencies.md @@ -0,0 +1,50 @@ +# Dependencies API **(ULTIMATE)** + +CAUTION: **Caution:** +This API is in an alpha stage and considered unstable. +The response payload may be subject to change or breakage +across GitLab releases. + +Every call to this endpoint requires authentication. To perform this call, user should be authorized to read +[Project Security Dashboard](../user/application_security/security_dashboard/index.md#project-security-dashboard). + +## List project dependencies + +Get a list of project dependencies. This API partially mirroring +[Dependency List](../user/application_security/dependency_scanning/index.md#dependency-list) feature. +This list can be generated only for [languages and package managers](../user/application_security/dependency_scanning/index.md#supported-languages-and-package-managers) +supported by Gemnasium. + +``` +GET /projects/:id/dependencies +GET /projects/:id/vulnerabilities?package_manger=maven +GET /projects/:id/vulnerabilities?package_manger=yarn,bundler +``` + +| Attribute | Type | Required | Description | +| ------------- | -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). | +| `package_manager` | string array | no | Returns dependencies belonging to specified package manager. Valid values: `bundler`, `composer`, `maven`, `npm`, `pip` or `yarn`. | + +```bash +curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/dependencies +``` + +Example response: + +```json +[ + { + "name": "rails", + "version": "5.0.1", + "package_manager": "bundler", + "dependency_file_path": "Gemfile.lock" + }, + { + "name": "hanami", + "version": "1.3.1", + "package_manager": "bundler", + "dependency_file_path": "Gemfile.lock" + } +] +``` diff --git a/doc/api/jobs.md b/doc/api/jobs.md index 1add5f432ac..0e45ee1a583 100644 --- a/doc/api/jobs.md +++ b/doc/api/jobs.md @@ -409,10 +409,10 @@ Possible response status codes: > - The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced][ee-2346] > in [GitLab Premium][ee] 9.5. -Download the artifacts zipped archive from the given reference name and job, -provided the job finished successfully. This is the same as -[getting the job's artifacts](#get-job-artifacts), but by defining the job's -name instead of its ID. +Download the artifacts zipped archive from the latest successful pipeline for +the given reference name and job, provided the job finished successfully. This +is the same as [getting the job's artifacts](#get-job-artifacts), but by +defining the job's name instead of its ID. ``` GET /projects/:id/jobs/artifacts/:ref_name/download?job=name @@ -506,9 +506,9 @@ Possible response status codes: > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23538) in GitLab 11.5. -Download a single artifact file from a specific tag or branch from within the -job's artifacts archive. The file is extracted from the archive and streamed to -the client. +Download a single artifact file for a specific job of the latest successful +pipeline for the given reference name from within the job's artifacts archive. +The file is extracted from the archive and streamed to the client. ``` GET /projects/:id/jobs/artifacts/:ref_name/raw/*artifact_path?job=name diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 662a4b3e424..1ade46efb1c 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -44,7 +44,7 @@ Parameters: | `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead. | | `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me` | | `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. | -| `approver_ids` **(STARTER)** | Array[integer] | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. | +| `approver_ids` **(STARTER)** | integer array | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. | | `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ | | `source_branch` | string | no | Return merge requests with the given source branch | | `target_branch` | string | no | Return merge requests with the given target branch | @@ -206,7 +206,7 @@ Parameters: | `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13060] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ | | `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. _([Introduced][ce-13060] in GitLab 9.5)_ | -| `approver_ids` **(STARTER)** | Array[integer] | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. | +| `approver_ids` **(STARTER)** | integer array | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. | | `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ | | `source_branch` | string | no | Return merge requests with the given source branch | | `target_branch` | string | no | Return merge requests with the given target branch | @@ -358,7 +358,7 @@ Parameters: | `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> | | `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ | | `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. _([Introduced][ce-13060] in GitLab 9.5)_ | -| `approver_ids` **(STARTER)** | Array[integer] | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. | +| `approver_ids` **(STARTER)** | integer array | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. | | `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ | | `source_branch` | string | no | Return merge requests with the given source branch | | `target_branch` | string | no | Return merge requests with the given target branch | diff --git a/doc/api/releases/img/upcoming_release_v12_1.png b/doc/api/releases/img/upcoming_release_v12_1.png Binary files differnew file mode 100644 index 00000000000..8bd8573ce84 --- /dev/null +++ b/doc/api/releases/img/upcoming_release_v12_1.png diff --git a/doc/api/releases/index.md b/doc/api/releases/index.md index e7f79a0d359..e74b35fd959 100644 --- a/doc/api/releases/index.md +++ b/doc/api/releases/index.md @@ -6,7 +6,7 @@ ## List Releases -Paginated list of Releases, sorted by `created_at`. +Paginated list of Releases, sorted by `released_at`. ``` GET /projects/:id/releases @@ -32,6 +32,7 @@ Example response: "name":"Awesome app v0.2 beta", "description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eEscape label and milestone titles to prevent XSS in GFM autocomplete. !2740\u003c/li\u003e\n\u003cli\u003ePrevent private snippets from being embeddable.\u003c/li\u003e\n\u003cli\u003eAdd subresources removal to member destroy service.\u003c/li\u003e\n\u003c/ul\u003e", "created_at":"2019-01-03T01:56:19.539Z", + "released_at":"2019-01-03T01:56:19.539Z", "author":{ "id":1, "name":"Administrator", @@ -98,6 +99,7 @@ Example response: "name":"Awesome app v0.1 alpha", "description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e", "created_at":"2019-01-03T01:55:18.203Z", + "released_at":"2019-01-03T01:55:18.203Z", "author":{ "id":1, "name":"Administrator", @@ -178,6 +180,7 @@ Example response: "name":"Awesome app v0.1 alpha", "description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e", "created_at":"2019-01-03T01:55:18.203Z", + "released_at":"2019-01-03T01:55:18.203Z", "author":{ "id":1, "name":"Administrator", @@ -247,6 +250,7 @@ POST /projects/:id/releases | `assets:links`| array of hash | no | An array of assets links. | | `assets:links:name`| string | no (if `assets:links` specified, it's required) | The name of the link. | | `assets:links:url`| string | no (if `assets:links` specified, it's required) | The url of the link. | +| `released_at` | datetime | no | The date when the release will be/was ready. Defaults to the current time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). | Example request: @@ -265,6 +269,7 @@ Example response: "name":"New release", "description_html":"\u003cp dir=\"auto\"\u003eSuper nice release\u003c/p\u003e", "created_at":"2019-01-03T02:22:45.118Z", + "released_at":"2019-01-03T02:22:45.118Z", "author":{ "id":1, "name":"Administrator", @@ -335,6 +340,7 @@ PUT /projects/:id/releases/:tag_name | `tag_name` | string | yes | The tag where the release will be created from. | | `name` | string | no | The release name. | | `description` | string | no | The description of the release. You can use [markdown](../../user/markdown.md). | +| `released_at` | datetime | no | The date when the release will be/was ready. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). | Example request: @@ -351,6 +357,7 @@ Example response: "name":"new name", "description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e", "created_at":"2019-01-03T01:55:18.203Z", + "released_at":"2019-01-03T01:55:18.203Z", "author":{ "id":1, "name":"Administrator", @@ -430,6 +437,7 @@ Example response: "name":"new name", "description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e", "created_at":"2019-01-03T01:55:18.203Z", + "released_at":"2019-01-03T01:55:18.203Z", "author":{ "id":1, "name":"Administrator", @@ -480,3 +488,11 @@ Example response: } } ``` + +## Upcoming Releases + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/38105) in GitLab 12.1. + +A release with a `released_at` attribute set to a future date will be labeled an **Upcoming Release** in the UI: + +![Upcoming release](img/upcoming_release_v12_1.png) diff --git a/doc/api/users.md b/doc/api/users.md index 54641f4c862..fdc84826680 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -147,6 +147,21 @@ GET /users ] ``` +Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, and `note` parameters. + +```json +[ + { + "id": 1, + ... + "shared_runners_minutes_limit": 133, + "extra_shared_runners_minutes_limit": 133, + "note": "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123" + ... + } +] +``` + Users on GitLab [Silver or higher](https://about.gitlab.com/pricing/) will also see the `group_saml` provider option: @@ -284,14 +299,15 @@ Example Responses: ``` Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see -the `shared_runners_minutes_limit` and `extra_shared_runners_minutes_limit` parameters. +the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, and `note` parameters. ```json { "id": 1, "username": "john_smith", "shared_runners_minutes_limit": 133, - "extra_shared_runners_minutes_limit": 133 + "extra_shared_runners_minutes_limit": 133, + "note": "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123" ... } ``` @@ -304,7 +320,8 @@ see the `group_saml` option: "id": 1, "username": "john_smith", "shared_runners_minutes_limit": 133, - "extra_shared_runners_minutes_limit": 133 + "extra_shared_runners_minutes_limit": 133, + "note": "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123" "identities": [ {"provider": "github", "extern_uid": "2435223452345"}, {"provider": "bitbucket", "extern_uid": "john.smith"}, @@ -399,6 +416,7 @@ Parameters: - `private_profile` (optional) - User's profile is private - true or false (default) - `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user **(STARTER)** - `extra_shared_runners_minutes_limit` (optional) - Extra pipeline minutes quota for this user **(STARTER)** +- `note` (optional) - Admin notes for this user **(STARTER)** On password update, user will be forced to change it upon next login. Note, at the moment this method does only return a `404` error, diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index f9dcd6417e0..f3896c5232c 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -193,13 +193,14 @@ You can simply define an image that will be used for all jobs and a list of services that you want to use during build time: ```yaml -image: ruby:2.2 +default: + image: ruby:2.2 -services: - - postgres:9.3 + services: + - postgres:9.3 -before_script: - - bundle install + before_script: + - bundle install test: script: @@ -209,8 +210,9 @@ test: It is also possible to define different images and services per job: ```yaml -before_script: - - bundle install +default: + before_script: + - bundle install test:2.1: image: ruby:2.1 @@ -231,18 +233,19 @@ Or you can pass some [extended configuration options](#extended-docker-configura for `image` and `services`: ```yaml -image: - name: ruby:2.2 - entrypoint: ["/bin/bash"] +default: + image: + name: ruby:2.2 + entrypoint: ["/bin/bash"] -services: -- name: my-postgres:9.4 - alias: db-postgres - entrypoint: ["/usr/local/bin/db-postgres"] - command: ["start"] + services: + - name: my-postgres:9.4 + alias: db-postgres + entrypoint: ["/usr/local/bin/db-postgres"] + command: ["start"] -before_script: -- bundle install + before_script: + - bundle install test: script: diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index 5a302392c54..9295dcfd4e0 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -37,6 +37,7 @@ The following table lists examples with step-by-step tutorials that are containe | Python on Heroku | [Test and deploy a Python application with GitLab CI/CD](test-and-deploy-python-application-to-heroku.md). | | Ruby on Heroku | [Test and deploy a Ruby application with GitLab CI/CD](test-and-deploy-ruby-application-to-heroku.md). | | Scala on Heroku | [Test and deploy a Scala application to Heroku](test-scala-application.md). | +| Parallel testing Ruby & JS | [GitLab CI parallel jobs testing for Ruby & JavaScript projects](https://docs.knapsackpro.com/2019/how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing). | ### Contributing examples diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index c2ef58acf15..001f951ebb8 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -119,6 +119,35 @@ The following table lists available parameters for jobs: NOTE: **Note:** Parameters `types` and `type` are [deprecated](#deprecated-parameters). +## Setting default parameters + +Some parameters can be set globally as the default for all jobs using the +`default:` keyword. Default parameters can then be overridden by job-specific +configuration. + +The following job parameters can be defined inside a `default:` block: + +- [`image`](#image) +- [`services`](#services) +- [`before_script`](#before_script-and-after_script) +- [`after_script`](#before_script-and-after_script) +- [`cache`](#cache) + +In the following example, the `ruby:2.5` image is set as the default for all +jobs except the `rspec 2.6` job, which uses the `ruby:2.6` image: + +```yaml +default: + image: ruby:2.5 + +rspec: + script: bundle exec rspec + +rspec 2.6: + image: ruby:2.6 + script: bundle exec rspec +``` + ## Parameter details The following are detailed explanations for parameters used to configure CI/CD pipelines. @@ -239,8 +268,9 @@ It's possible to overwrite the globally defined `before_script` and `after_scrip if you set it per-job: ```yaml -before_script: - - global before script +default: + before_script: + - global before script job: before_script: @@ -974,7 +1004,7 @@ review_app: stop_review_app: stage: deploy variables: - GIT_STRATEGY: none + GIT_STRATEGY: none script: make delete-app when: manual environment: @@ -1749,6 +1779,10 @@ test: parallel: 5 ``` +TIP: **Tip:** +Parallelize tests suites across parallel jobs. +Different languages have different tools to facilitate this. + ### `trigger` **(PREMIUM)** > [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8997) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8. @@ -2221,10 +2255,10 @@ spinach: script: rake spinach ``` -It's also possible to use multiple parents for `extends`. -The algorithm used for merge is "closest scope wins", so keys -from the last member will always shadow anything defined on other levels. -For example: +In GitLab 12.0 and later, it's also possible to use multiple parents for +`extends`. The algorithm used for merge is "closest scope wins", so +keys from the last member will always shadow anything defined on other +levels. For example: ```yaml .only-important: @@ -2550,18 +2584,39 @@ You can set it globally or per-job in the [`variables`](#variables) section. The following parameters are deprecated. -### `types` +### Globally-defined `types` CAUTION: **Deprecated:** `types` is deprecated, and could be removed in a future release. Use [`stages`](#stages) instead. -### `type` +### Job-defined `type` CAUTION: **Deprecated:** `type` is deprecated, and could be removed in one of the future releases. Use [`stage`](#stage) instead. +### Globally-defined `image`, `services`, `cache`, `before_script`, `after_script` + +Defining `image`, `services`, `cache`, `before_script`, and +`after_script` globally is deprecated. Support could be removed +from a future release. + +Use [`default:`](#setting-default-parameters) instead. For example: + +```yaml +default: + image: ruby:2.5 + services: + - docker:dind + cache: + paths: [vendor/] + before_script: + - bundle install --path vendor/ + after_script: + - rm -rf tmp/ +``` + ## Custom build directories > [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1267) in Gitlab Runner 11.10 @@ -2644,7 +2699,7 @@ variables: The value of `GIT_CLONE_PATH` is expanded once into `$CI_BUILDS_DIR/go/src/namespace/project`, and results in failure -because `$CI_BUILDS_DIR` is not expanded. +because `$CI_BUILDS_DIR` is not expanded. ## Special YAML features diff --git a/doc/development/fe_guide/event_tracking.md b/doc/development/fe_guide/event_tracking.md index 6ab3fa4acf3..716f6ad7f92 100644 --- a/doc/development/fe_guide/event_tracking.md +++ b/doc/development/fe_guide/event_tracking.md @@ -47,7 +47,7 @@ There's a more convenient solution to this problem. When working with HAML templ Below is an example of `data-track-*` attributes assigned to a button in HAML: ```ruby -%button.btn{ data: { track_label: "create_from_template", track_property: "template_preview", track_event: "click_button", track_value: "my-template" } } +%button.btn{ data: { track_label: "template_preview", track_property: "my-template", track_event: "click_button", track_value: "" } } ``` By calling `bindTrackableContainer('.my-container')`, click handlers get bound to all elements located in `.my-container` provided that they have the necessary `data-track-*` attributes assigned to them. diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md index fff06254da7..20caac4cb74 100644 --- a/doc/integration/elasticsearch.md +++ b/doc/integration/elasticsearch.md @@ -42,7 +42,11 @@ use the packages that are available for your OS. In order to improve elasticsearch indexing performance, GitLab has made available a [new indexer written in Go](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer). This will replace the included Ruby indexer in the future but should be considered beta software for now, so there may be some bugs. -If you would like to use it, please follow the instructions below. +The Elasticsearch Go indexer is included in Omnibus for GitLab 11.8 and newer. + +To use the new Elasticsearch indexer included in Omnibus, check the box "Use the new repository indexer (beta)" when [enabling the Elasticsearch integration](#enabling-elasticsearch). + +If you would like to use the Elasticsearch Go indexer with a source installation or an older version of GitLab, please follow the instructions below. ### Installation diff --git a/doc/security/information_exclusivity.md b/doc/security/information_exclusivity.md index 62a20d3f257..749ccf924b5 100644 --- a/doc/security/information_exclusivity.md +++ b/doc/security/information_exclusivity.md @@ -1,6 +1,7 @@ --- type: concepts --- + # Information exclusivity Git is a distributed version control system (DVCS). This means that everyone diff --git a/doc/security/password_length_limits.md b/doc/security/password_length_limits.md index d78293c75c6..9909ef4a8e4 100644 --- a/doc/security/password_length_limits.md +++ b/doc/security/password_length_limits.md @@ -1,19 +1,31 @@ --- type: reference, howto --- + # Custom password length limits -If you want to enforce longer user passwords you can create an extra Devise -initializer with the steps below. +The user password length is set to a minimum of 8 characters by default. +To change that for installations from source: + +1. Edit `devise_password_length.rb`: + + ```sh + cd /home/git/gitlab + sudo -u git -H cp config/initializers/devise_password_length.rb.example config/initializers/devise_password_length.rb + sudo -u git -H editor config/initializers/devise_password_length.rb + ``` + +1. Change the new password length limits: + + ```ruby + config.password_length = 12..128 + ``` -If you do not use the `devise_password_length.rb` initializer the password -length is set to a minimum of 8 characters in `config/initializers/devise.rb`. + In this example, the minimum length is 12 characters, and the maximum length + is 128 characters. -```bash -cd /home/git/gitlab -sudo -u git -H cp config/initializers/devise_password_length.rb.example config/initializers/devise_password_length.rb -sudo -u git -H editor config/initializers/devise_password_length.rb # inspect and edit the new password length limits -``` +1. [Restart GitLab](../administration/restart_gitlab.md#installations-from-source) + for the changes to take effect. <!-- ## Troubleshooting diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md index 1b75798013d..1e5678ec47c 100644 --- a/doc/security/rack_attack.md +++ b/doc/security/rack_attack.md @@ -1,6 +1,7 @@ --- type: reference, howto --- + # Rack Attack [Rack Attack](https://github.com/kickstarter/rack-attack), also known as Rack::Attack, is a Ruby gem diff --git a/doc/security/reset_root_password.md b/doc/security/reset_root_password.md index a58d70f0ff2..6a6c5262179 100644 --- a/doc/security/reset_root_password.md +++ b/doc/security/reset_root_password.md @@ -1,6 +1,7 @@ --- type: howto --- + # How to reset your root password To reset your root password, first log into your server with root privileges. diff --git a/doc/security/ssh_keys_restrictions.md b/doc/security/ssh_keys_restrictions.md index ae4cc44519e..4c60daf77f4 100644 --- a/doc/security/ssh_keys_restrictions.md +++ b/doc/security/ssh_keys_restrictions.md @@ -1,6 +1,7 @@ --- type: reference, howto --- + # Restrict allowed SSH key technologies and minimum length `ssh-keygen` allows users to create RSA keys with as few as 768 bits, which diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md index 6251f8e2f66..b08d9ffa26e 100644 --- a/doc/security/two_factor_authentication.md +++ b/doc/security/two_factor_authentication.md @@ -1,6 +1,7 @@ --- type: howto --- + # Enforce Two-factor Authentication (2FA) Two-factor Authentication (2FA) provides an additional level of security to your diff --git a/doc/security/unlock_user.md b/doc/security/unlock_user.md index 2e14e631d68..d34826c853c 100644 --- a/doc/security/unlock_user.md +++ b/doc/security/unlock_user.md @@ -2,37 +2,44 @@ type: howto --- -# How to unlock a locked user +# How to unlock a locked user from the command line -To unlock a locked user, first log into your server with root privileges. +After six failed login attempts a user gets in a locked state. -Start a Ruby on Rails console with this command: +To unlock a locked user: -```bash -gitlab-rails console production -``` +1. SSH into your GitLab server. +1. Start a Ruby on Rails console: -Wait until the console has loaded. + ```sh + ## For Omnibus GitLab + sudo gitlab-rails console production -There are multiple ways to find your user. You can search for email or username. + ## For installations from source + sudo -u git -H bundle exec rails console RAILS_ENV=production + ``` -```bash -user = User.where(id: 1).first -``` +1. Find the user to unlock. You can search by email or ID. -or + ```ruby + user = User.find_by(email: 'admin@local.host') + ``` -```bash -user = User.find_by(email: 'admin@local.host') -``` + or -Unlock the user: + ```ruby + user = User.where(id: 1).first + ``` -```bash -user.unlock_access! -``` +1. Unlock the user: -Exit the console, the user should now be able to log in again. + ```ruby + user.unlock_access! + ``` + +1. Exit the console with <kbd>Ctrl</kbd>+<kbd>d</kbd> + +The user should now be able to log in. <!-- ## Troubleshooting diff --git a/doc/security/user_email_confirmation.md b/doc/security/user_email_confirmation.md index f0af0a7ac6a..7ba50acbb06 100644 --- a/doc/security/user_email_confirmation.md +++ b/doc/security/user_email_confirmation.md @@ -1,6 +1,7 @@ --- type: howto --- + # User email confirmation at sign-up GitLab can be configured to require confirmation of a user's email address when diff --git a/doc/security/user_file_uploads.md b/doc/security/user_file_uploads.md index f34528a6e05..9fc8f7ec985 100644 --- a/doc/security/user_file_uploads.md +++ b/doc/security/user_file_uploads.md @@ -1,6 +1,7 @@ --- type: reference --- + # User File Uploads Images that are attached to issues, merge requests, or comments diff --git a/doc/security/webhooks.md b/doc/security/webhooks.md index d4fa088cb15..1194234a295 100644 --- a/doc/security/webhooks.md +++ b/doc/security/webhooks.md @@ -1,6 +1,7 @@ --- type: concepts, reference, howto --- + # Webhooks and insecure internal web services If you have non-GitLab web services running on your GitLab server or within its diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 6dfe42f68cf..f9ad952aaad 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -222,7 +222,7 @@ full use of Auto DevOps are available. If this is your fist time, we recommend y [quick start guide](quick_start_guide.md). GitLab.com users can enable/disable Auto DevOps at the project-level only. Self-managed users -can enable/disable Auto DevOps at either the project-level or instance-level. +can enable/disable Auto DevOps at the project-level, group-level or instance-level. ### Enabling/disabling Auto DevOps at the instance-level (Administrators only) diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md index 0fb7338a688..e56acac527f 100644 --- a/doc/user/admin_area/settings/continuous_integration.md +++ b/doc/user/admin_area/settings/continuous_integration.md @@ -164,3 +164,23 @@ questions that you know someone might ask. Each scenario can be a third-level heading, e.g. `### Getting error message X`. If you have none to add when creating a doc, leave this section in place but commented out to help encourage others to add to it in the future. --> + +## Required pipeline configuration **(PREMIUM ONLY)** + +GitLab administrators can force a pipeline configuration to run on every +pipeline. + +The configuration applies to all pipelines for a GitLab instance and is +sourced from: + +- The [instance template repository](instance_template_repository.md). +- GitLab-supplied configuration. + +To set required pipeline configuration: + +1. Go to **Admin area > Settings > CI/CD**. +1. Expand the **Required pipeline configuration** section. +1. Select the required configuration from the provided dropdown. +1. Click **Save changes**. + +![Required pipeline](img/admin_required_pipeline.png) diff --git a/doc/user/admin_area/settings/img/admin_required_pipeline.png b/doc/user/admin_area/settings/img/admin_required_pipeline.png Binary files differnew file mode 100644 index 00000000000..58488674d51 --- /dev/null +++ b/doc/user/admin_area/settings/img/admin_required_pipeline.png diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md index 0dd0fd3f136..09bd306363c 100644 --- a/doc/user/application_security/dependency_scanning/index.md +++ b/doc/user/application_security/dependency_scanning/index.md @@ -149,6 +149,8 @@ using environment variables. | `DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | | `DS_PULL_ANALYZER_IMAGE_TIMEOUT` | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | | `DS_RUN_ANALYZER_TIMEOUT` | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | +| `PIP_INDEX_URL` | Base URL of Python Package Index (default https://pypi.org/simple). | +| `PIP_EXTRA_INDEX_URL` | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma separated. | ## Reports JSON format diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md index 2246ea8ed5a..6956086c382 100644 --- a/doc/user/clusters/applications.md +++ b/doc/user/clusters/applications.md @@ -251,6 +251,7 @@ The applications below can be uninstalled. | Application | GitLab version | Notes | | ----------- | -------------- | ----- | +| GitLab Runner | 12.2+ | Any running pipelines will be canceled. | | Ingress | 12.1+ | The associated load balancer and IP will be deleted and cannot be restored. Furthermore, it can only be uninstalled if JupyterHub is not installed. | | JupyterHub | 12.1+ | All data not committed to GitLab will be deleted and cannot be restored. | | Prometheus | 11.11+ | All data will be deleted and cannot be restored. | diff --git a/doc/user/project/autocomplete_characters.md b/doc/user/project/autocomplete_characters.md new file mode 100644 index 00000000000..9ebf7f821a1 --- /dev/null +++ b/doc/user/project/autocomplete_characters.md @@ -0,0 +1,48 @@ +# Autocomplete characters + +The autocomplete characters provide a quick way of entering field values into +Markdown fields. When you start typing a word in a Markdown field with one of +the following characters, GitLab progressively autocompletes against a set of +matching values. The string matching is not case sensitive. + +| Character | Autocompletes | +| :-------- | :------------ | +| `~` | Labels | +| `%` | Milestones | +| `@` | Users and groups | +| `#` | Issues | +| `!` | Merge requests | +| `&` | Epics | +| `$` | Snippets | +| `:` | Emoji | +| `/` | Quick Actions | + +Up to 5 of the most relevant matches are displayed in a popup list. When you +select an item from the list, the value is entered in the field. The more +characters you enter, the more precise the matches are. + +Autocomplete characters are useful when combined with [Quick Actions](quick_actions.md). + +## Example + +Assume your GitLab instance includes the following users: + +| Username | Name | +| :-------------- | :--- | +| alessandra | Rosy Grant | +| lawrence.white | Kelsey Kerluke | +| leanna | Rosemarie Rogahn | +| logan_gutkowski | Lee Wuckert | +| shelba | Josefine Haley | + +In an Issue comment, entering `@l` results in the following popup list +appearing. Note that user `shelba` is not included, because the list includes +only the 5 users most relevant to the Issue. + +![Popup list which includes users whose username or name contains the letter `l`](img/autocomplete_characters_example1_v12_0.png) + +If you continue to type, `@le`, the popup list changes to the following. The +popup now only includes users where `le` appears in their username, or a word in +their name. + +![Popup list which includes users whose username or name contains the string `le`](img/autocomplete_characters_example2_v12_0.png) diff --git a/doc/user/project/img/autocomplete_characters_example1_v12_0.png b/doc/user/project/img/autocomplete_characters_example1_v12_0.png Binary files differnew file mode 100755 index 00000000000..9c6fa923b80 --- /dev/null +++ b/doc/user/project/img/autocomplete_characters_example1_v12_0.png diff --git a/doc/user/project/img/autocomplete_characters_example2_v12_0.png b/doc/user/project/img/autocomplete_characters_example2_v12_0.png Binary files differnew file mode 100755 index 00000000000..b2e8a782a0b --- /dev/null +++ b/doc/user/project/img/autocomplete_characters_example2_v12_0.png diff --git a/doc/user/project/index.md b/doc/user/project/index.md index 0ffa69b6b78..7307c5b8991 100644 --- a/doc/user/project/index.md +++ b/doc/user/project/index.md @@ -52,6 +52,9 @@ When you create a project in GitLab, you'll have access to a large number of templates for issue and merge request description fields for your project - [Slash commands (quick actions)](quick_actions.md): Textual shortcuts for common actions on issues or merge requests +- [Autocomplete characters](autocomplete_characters.md): Autocomplete + references to users, groups, issues, merge requests, and other GitLab + elements. - [Web IDE](web_ide/index.md) **GitLab CI/CD:** diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index 69fefbf3891..72f12972596 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -121,68 +121,91 @@ GitLab supports a limited set of [CI variables](../../../ci/variables/README.htm To specify a variable in a query, enclose it in curly braces with a leading percent. For example: `%{ci_environment_slug}`. -### Defining Dashboards for Prometheus Metrics per Project +### Defining custom dashboards per project -All projects include a GitLab-defined system dashboard, which includes a few key metrics. Optionally, additional dashboards can also be defined by including configuration files in the project repository under `.gitlab/dashboards`. Configuration files nested under subdirectories will not be available in the UI. Each file should define the layout of the dashboard and the prometheus queries used to populate data. Dashboards can be selected from the dropdown in the UI. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/59974) in GitLab 12.1. -#### Relationship to Custom Metrics +By default, all projects include a GitLab-defined Prometheus dashboard, which +includes a few key metrics, but you can also define your own custom dashboards. -[Custom Metrics](#adding-additional-metrics-premium) are defined through the UI and, at this point, are unique from metrics defined in dashboard configuration files. Custom Metrics will appear on the system dashboard, as well as support alerting, whereas metrics defined in configuration files do not yet support alerts. +NOTE: **Note:** +The custom metrics as defined below do not support alerts, unlike +[additional metrics](#adding-additional-metrics-premium). -#### Dashboard Configuration +Dashboards have several components: -Dashboards have several components. A dashboard has many panel groups, which are comprised of panels, which support one or more metrics. The dashboard should be saved with the `.yml` extension. +- Panel groups, which comprise panels. +- Panels, which support one or more metrics. -Sample YML Configuration -``` -dashboard: 'Dashboard Title' -priority: 2 -panel_groups: - - group: 'Group Title' - panels: - - type: area-chart - title: "Chart Title" - y_label: "Y-Axis" - metrics: - - id: metric_of_ages - query_range: 'http_requests_total' - label: "Metric of Ages" - unit: "count" -``` +To configure a custom dashboard: + +1. Create a YAML file with the `.yml` extension under your repository's root + directory inside `.gitlab/dashboards/`. For example, create + `.gitlab/dashboards/prom_alerts.yml` with the following contents: + + ```yaml + dashboard: 'Dashboard Title' + panel_groups: + - group: 'Group Title' + panels: + - type: area-chart + title: "Chart Title" + y_label: "Y-Axis" + metrics: + - id: metric_of_ages + query_range: 'http_requests_total' + label: "Metric of Ages" + unit: "count" + ``` + + The above sample dashboard would display a single area chart. Each file should + define the layout of the dashboard and the Prometheus queries used to populate + data. + +1. Save the file, commit, and push to your repository. +1. Navigate to your project's **Operations > Metrics** and choose the custom + dashboard from the dropdown. -The above sample dashboard would display a single area chart. The following sections outline the details of expected properties. +NOTE: **Note:** +Configuration files nested under subdirectories of `.gitlab/dashboards` are not +supported and will not be available in the UI. -##### Dashboard Properties -| Property | Type | Required? | Meaning | +The following tables outline the details of expected properties. + +**Dashboard properties:** + +| Property | Type | Required | Description | | ------ | ------ | ------ | ------ | -| `dashboard` | string | required | Heading for the dashboard. Only one dashboard should be defined per file. | -| `priority` | number | optional, default to definition order | Order to appear in dashboard dropdown, higher priority should be higher in the dropdown. Numbers do not need to be consecutive. | -| `panel_groups` | array | required | The panel groups which should be on the dashboard. | +| `dashboard` | string | yes | Heading for the dashboard. Only one dashboard should be defined per file. | +| `panel_groups` | array | yes | The panel groups which should be on the dashboard. | + +**Panel group (`panel_groups`) properties:** -##### Panel Group Properties -| Property | Type | Required? | Meaning | +| Property | Type | Required | Description | | ------ | ------ | ------ | ------ | | `group` | string | required | Heading for the panel group. | -| `priority` | number | optional, defaults to order in file | Order to appear on the dashboard, higher priority will be higher on the page. Numbers do not need to be consecutive. | +| `priority` | number | optional, defaults to order in file | Order to appear on the dashboard. Higher number means higher priority, which will be higher on the page. Numbers do not need to be consecutive. | | `panels` | array | required | The panels which should be in the panel group. | -##### Panel Properties -| Property | Type | Required? | Meaning | +**Panel (`panels`) properties:** + +| Property | Type | Required | Description | | ------ | ------ | ------ | ------- | -| `type` | enum | optional, defaults to `area-chart` | Specifies the chart type to use. Only `area-chart` is currently supported. | -| `title` | string | required | Heading for the panel. | -| `y_label` | string | optional, but highly encouraged | Y-Axis label for the panel. | -| `weight` | number | optional, defaults to order in file | Order to appear within the grouping, higher priority will be higher on the page. Numbers do not need to be consecutive. | -| `metrics` | array | required | The metrics which should be displayed in the panel. | - -##### Metric Properties -| Property | Type | Required? | Meaning | +| `type` | enum | no, defaults to `area-chart` | Specifies the chart type to use. Only `area-chart` is currently supported. | +| `title` | string | yes | Heading for the panel. | +| `y_label` | string | no, but highly encouraged | Y-Axis label for the panel. | +| `weight` | number | no, defaults to order in file | Order to appear within the grouping. Lower number means higher priority, which will be higher on the page. Numbers do not need to be consecutive. | +| `metrics` | array | yes | The metrics which should be displayed in the panel. | + +**Metrics (`metrics`) properties:** + +| Property | Type | Required | Description | | ------ | ------ | ------ | ------ | -| `id` | string | optional | Used for associating dashboard metrics with database records. Must be unique across dashboard configuration files. Required for [alerting](#setting-up-alerts-for-prometheus-metrics-ultimate) (support not yet enabled, see [relevant issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/60319)). | -| `unit` | string | required | Defines the unit of the query's return data. | -| `label` | string | optional, but highly encouraged | Defines the legend-label for the query. Should be unique within the panel's metrics. | -| `query` | string | required unless `query_range` is defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. | -| `query_range` | string | required unless `query` is defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query_range` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. | +| `id` | string | no | Used for associating dashboard metrics with database records. Must be unique across dashboard configuration files. Required for [alerting](#setting-up-alerts-for-prometheus-metrics-ultimate) (support not yet enabled, see [relevant issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/60319)). | +| `unit` | string | yes | Defines the unit of the query's return data. | +| `label` | string | no, but highly encouraged | Defines the legend-label for the query. Should be unique within the panel's metrics. | +| `query` | string | yes if `query_range` is not defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. | +| `query_range` | string | yes if `query` is not defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query_range` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. | ### Setting up alerts for Prometheus metrics **(ULTIMATE)** diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md index 6c0d3e9e9d3..219e141d72e 100644 --- a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md +++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md @@ -179,15 +179,26 @@ From that page, you can view, add, and remove them. ### Redirecting `www.domain.com` to `domain.com` with Cloudflare -If you use Cloudflare, you can redirect `www` to `domain.com` without adding both -`www.domain.com` and `domain.com` to GitLab. This happens due to a [Cloudflare feature that creates -a 301 redirect as a "page rule"](https://gitlab.com/gitlab-org/gitlab-ce/issues/48848#note_87314849) for redirecting `www.domain.com` to `domain.com`. In this case, -you can use the following setup: +If you use Cloudflare, you can redirect `www` to `domain.com` +without adding both `www.domain.com` and `domain.com` to GitLab. + +To do so, you can use Cloudflare's page rules associated to a +CNAME record to redirect `www.domain.com` to `domain.com`. You +can use the following setup: 1. In Cloudflare, create a DNS `A` record pointing `domain.com` to `35.185.44.232`. -1. In GitLab, add the domain to GitLab Pages. +1. In GitLab, add the domain to GitLab Pages and get the verification code. 1. In Cloudflare, create a DNS `TXT` record to verify your domain. +1. In GitLab, verify your domain. 1. In Cloudflare, create a DNS `CNAME` record pointing `www` to `domain.com`. +1. In Cloudflare, add a Page Rule pointing `www.domain,com` to `domain.com`: + - Navigate to your domain's dashboard and click **Page Rules** + on the top nav. + - Click **Create Page Rule**. + - Enter the domain `www.domain.com` and click **+ Add a Setting**. + - From the dropdown menu, choose **Forwarding URL**, then select the + status code **301 - Permanent Redirect**. + - Enter the destination URL `https://domain.com`. ## Adding an SSL/TLS certificate to Pages diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md index e60da6a3e59..df82daa3da3 100644 --- a/doc/user/project/pipelines/settings.md +++ b/doc/user/project/pipelines/settings.md @@ -89,6 +89,22 @@ in the jobs table. A few examples of known coverage tools for a variety of languages can be found in the pipelines settings page. +### Removing color codes + +Some test coverage tools output with ANSI color codes that won't be +parsed correctly by the regular expression and will cause coverage +parsing to fail. + +If your coverage tool doesn't provide an option to disable color +codes in the output, you can pipe the output of the coverage tool through a +small one line script that will strip the color codes off. + +For example: + +```bash +lein cloverage | perl -pe 's/\e\[?.*?[\@-~]//g' +``` + ## Visibility of pipelines Access to pipelines and job details (including output of logs and artifacts) diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index c6e945ef113..948ac91a886 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -35,18 +35,18 @@ discussions, and descriptions: | `/label ~label1 ~label2` | Add label(s). Label names can also start without ~ but mixed syntax is not supported. | ✓ | ✓ | | `/unlabel ~label1 ~label2` | Remove all or specific label(s)| ✓ | ✓ | | `/relabel ~label1 ~label2` | Replace existing label(s) with those specified | ✓ | ✓ | -| <code>/copy_metadata <#issue | !merge_request></code> | Copy labels and milestone from other issue or merge request in the project | ✓ | ✓ | -| <code>/estimate <1w 3d 2h 14m></code> | Set time estimate | ✓ | ✓ | +| `/copy_metadata <#issue | !merge_request>` | Copy labels and milestone from other issue or merge request in the project | ✓ | ✓ | +| `/estimate <1w 3d 2h 14m>` | Set time estimate | ✓ | ✓ | | `/remove_estimate` | Remove time estimate | ✓ | ✓ | -| <code>/spend <time(1h 30m | -1h 5m)> <date(YYYY-MM-DD)></code> | Add or subtract spent time; optionally, specify the date that time was spent on | ✓ | ✓ | +| `/spend <time(1h 30m | -1h 5m)> <date(YYYY-MM-DD)>` | Add or subtract spent time; optionally, specify the date that time was spent on | ✓ | ✓ | | `/remove_time_spent` | Remove time spent | ✓ | ✓ | | `/lock` | Lock the thread | ✓ | ✓ | | `/unlock` | Unlock the thread | ✓ | ✓ | -| <code>/due <in 2 days | this Friday | December 31st></code>| Set due date | ✓ | | +| `/due <in 2 days | this Friday | December 31st>`| Set due date | ✓ | | | `/remove_due_date` | Remove due date | ✓ | | -| <code>/weight <0 | 1 | 2 | ...></code> | Set weight **(STARTER)** | ✓ | | +| `/weight <0 | 1 | 2 | ...>` | Set weight **(STARTER)** | ✓ | | | `/clear_weight` | Clears weight **(STARTER)** | ✓ | | -| <code>/epic <&epic | group&epic | Epic URL></code> | Add to epic **(ULTIMATE)** | ✓ | | +| `/epic <&epic | group&epic | Epic URL>` | Add to epic **(ULTIMATE)** | ✓ | | | `/remove_epic` | Removes from epic **(ULTIMATE)** | ✓ | | | `/promote` | Promote issue to epic **(ULTIMATE)** | ✓ | | | `/confidential` | Make confidential | ✓ | | @@ -86,7 +86,7 @@ The following quick actions are applicable for epics threads and description: | `/label ~label1 ~label2` | Add label(s) | | `/unlabel ~label1 ~label2` | Remove all or specific label(s) | | `/relabel ~label1 ~label2` | Replace existing label(s) with those specified | -| <code>/child_epic <&epic | group&epic | Epic URL></code> | Adds child epic to epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) | -| <code>/remove_child_epic <&epic | group&epic | Epic URL></code> | Removes child epic from epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) | -| <code>/parent_epic <&epic | group&epic | Epic URL></code> | Sets parent epic to epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) | -| <code>/remove_parent_epic | Removes parent epic from epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) | +| `/child_epic <&epic | group&epic | Epic URL>` | Adds child epic to epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) | +| `/remove_child_epic <&epic | group&epic | Epic URL>` | Removes child epic from epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) | +| `/parent_epic <&epic | group&epic | Epic URL>` | Sets parent epic to epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) | +| `/remove_parent_epic` | Removes parent epic from epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) | diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md index 4286a3625a2..b55c6b2e3df 100644 --- a/doc/workflow/time_tracking.md +++ b/doc/workflow/time_tracking.md @@ -75,7 +75,7 @@ Default conversion rates are 1mo = 4w, 1w = 5d and 1d = 8h. ### Limit displayed units to hours -> Introduced in GitLab 12.0. +> Introduced in GitLab 12.1. The display of time units can be limited to hours through the option in **Admin Area > Settings > Preferences** under 'Localization'. diff --git a/lib/api/commits.rb b/lib/api/commits.rb index eebded87ebc..c414ad75d9d 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -126,7 +126,7 @@ module API if result[:status] == :success commit_detail = user_project.repository.commit(result[:result]) - Gitlab::WebIdeCommitsCounter.increment if find_user_from_warden + Gitlab::UsageDataCounters::WebIdeCommitsCounter.increment if find_user_from_warden present commit_detail, with: Entities::CommitDetail, stats: params[:stats] else diff --git a/lib/api/releases.rb b/lib/api/releases.rb index fdd8406388e..7a3d804c30c 100644 --- a/lib/api/releases.rb +++ b/lib/api/releases.rb @@ -78,7 +78,7 @@ module API requires :tag_name, type: String, desc: 'The name of the tag', as: :tag optional :name, type: String, desc: 'The name of the release' optional :description, type: String, desc: 'Release notes with markdown support' - optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready. Defaults to the current time.' + optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready.' end put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do authorize_update_release! diff --git a/lib/api/users.rb b/lib/api/users.rb index 30a278fdff1..a4ac5b629b8 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -148,7 +148,7 @@ module API end desc 'Create a user. Available only for admins.' do - success Entities::UserPublic + success Entities::UserWithAdmin end params do requires :email, type: String, desc: 'The email of the user' @@ -168,7 +168,7 @@ module API user = ::Users::CreateService.new(current_user, params).execute(skip_authorization: true) if user.persisted? - present user, with: Entities::UserPublic, current_user: current_user + present user, with: Entities::UserWithAdmin, current_user: current_user else conflict!('Email has already been taken') if User .by_any_email(user.email.downcase) @@ -183,7 +183,7 @@ module API end desc 'Update a user. Available only for admins.' do - success Entities::UserPublic + success Entities::UserWithAdmin end params do requires :id, type: Integer, desc: 'The ID of the user' @@ -215,7 +215,7 @@ module API result = ::Users::UpdateService.new(current_user, user_params.merge(user: user)).execute if result[:status] == :success - present user, with: Entities::UserPublic, current_user: current_user + present user, with: Entities::UserWithAdmin, current_user: current_user else render_validation_error!(user) end diff --git a/lib/banzai/filter/ascii_doc_sanitization_filter.rb b/lib/banzai/filter/ascii_doc_sanitization_filter.rb index a78bb60103c..d8d63075752 100644 --- a/lib/banzai/filter/ascii_doc_sanitization_filter.rb +++ b/lib/banzai/filter/ascii_doc_sanitization_filter.rb @@ -6,6 +6,9 @@ module Banzai # # Extends Banzai::Filter::BaseSanitizationFilter with specific rules. class AsciiDocSanitizationFilter < Banzai::Filter::BaseSanitizationFilter + # Section anchor link pattern + SECTION_LINK_REF_PATTERN = /\A#{Gitlab::Asciidoc::DEFAULT_ADOC_ATTRS['idprefix']}(:?[[:alnum:]]|-|_)+\z/.freeze + # Classes used by Asciidoctor to style components ADMONITION_CLASSES = %w(fa icon-note icon-tip icon-warning icon-caution icon-important).freeze CALLOUT_CLASSES = ['conum'].freeze @@ -19,14 +22,17 @@ module Banzai td: ['icon'].freeze, i: ADMONITION_CLASSES + CALLOUT_CLASSES + CHECKLIST_CLASSES, ul: LIST_CLASSES, - ol: LIST_CLASSES + ol: LIST_CLASSES, + a: ['anchor'].freeze }.freeze + ALLOWED_HEADERS = %w(h2 h3 h4 h5 h6).freeze + def customize_whitelist(whitelist) # Allow marks whitelist[:elements].push('mark') - # Allow any classes in `span`, `i`, `div`, `td`, `ul` and `ol` elements + # Allow any classes in `span`, `i`, `div`, `td`, `ul`, `ol` and `a` elements # but then remove any unknown classes whitelist[:attributes]['span'] = %w(class) whitelist[:attributes]['div'].push('class') @@ -34,12 +40,32 @@ module Banzai whitelist[:attributes]['i'] = %w(class) whitelist[:attributes]['ul'] = %w(class) whitelist[:attributes]['ol'] = %w(class) + whitelist[:attributes]['a'].push('class') whitelist[:transformers].push(self.class.remove_element_classes) + # Allow `id` in heading elements for section anchors + ALLOWED_HEADERS.each do |header| + whitelist[:attributes][header] = %w(id) + end + whitelist[:transformers].push(self.class.remove_non_heading_ids) + whitelist end class << self + def remove_non_heading_ids + lambda do |env| + node = env[:node] + + return unless ALLOWED_HEADERS.any?(node.name) + return unless node.has_attribute?('id') + + return if node['id'] =~ SECTION_LINK_REF_PATTERN + + node.remove_attribute('id') + end + end + def remove_element_classes lambda do |env| node = env[:node] diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/reference_redactor_filter.rb index 1f091f594f8..485d3fd5fc7 100644 --- a/lib/banzai/filter/redactor_filter.rb +++ b/lib/banzai/filter/reference_redactor_filter.rb @@ -7,12 +7,12 @@ module Banzai # # Expected to be run in its own post-processing pipeline. # - class RedactorFilter < HTML::Pipeline::Filter + class ReferenceRedactorFilter < HTML::Pipeline::Filter def call unless context[:skip_redaction] context = RenderContext.new(project, current_user) - Redactor.new(context).redact([doc]) + ReferenceRedactor.new(context).redact([doc]) end doc diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb index 75661ffa233..d6d29f4bfab 100644 --- a/lib/banzai/object_renderer.rb +++ b/lib/banzai/object_renderer.rb @@ -72,7 +72,7 @@ module Banzai # # Returns an Array containing the redacted documents. def redact_documents(documents) - redactor = Redactor.new(context) + redactor = ReferenceRedactor.new(context) redactor.redact(documents) end diff --git a/lib/banzai/pipeline/post_process_pipeline.rb b/lib/banzai/pipeline/post_process_pipeline.rb index 5c199453638..54af26b41be 100644 --- a/lib/banzai/pipeline/post_process_pipeline.rb +++ b/lib/banzai/pipeline/post_process_pipeline.rb @@ -12,7 +12,7 @@ module Banzai def self.internal_link_filters [ - Filter::RedactorFilter, + Filter::ReferenceRedactorFilter, Filter::InlineMetricsRedactorFilter, Filter::RelativeLinkFilter, Filter::IssuableStateFilter, diff --git a/lib/banzai/redactor.rb b/lib/banzai/reference_redactor.rb index c2da7fec7cc..eb5c35da375 100644 --- a/lib/banzai/redactor.rb +++ b/lib/banzai/reference_redactor.rb @@ -3,7 +3,7 @@ module Banzai # Class for removing Markdown references a certain user is not allowed to # view. - class Redactor + class ReferenceRedactor attr_reader :context # context - An instance of `Banzai::RenderContext`. diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index 81f32ef5bcf..3cb9ec21e8f 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -134,7 +134,7 @@ module Banzai # # This method is used to perform state-dependent changes to a String of # HTML, such as removing references that the current user doesn't have - # permission to make (`RedactorFilter`). + # permission to make (`ReferenceRedactorFilter`). # # html - String to process # context - Hash of options to customize output diff --git a/lib/feature/gitaly.rb b/lib/feature/gitaly.rb index 67c0b902c0c..edfd2fb17f3 100644 --- a/lib/feature/gitaly.rb +++ b/lib/feature/gitaly.rb @@ -5,16 +5,12 @@ require 'set' class Feature class Gitaly # Server feature flags should use '_' to separate words. - # CATFILE_CACHE sets an incorrect example - CATFILE_CACHE = 'catfile-cache'.freeze - SERVER_FEATURE_FLAGS = [ - CATFILE_CACHE, 'get_commit_signatures'.freeze ].freeze - DEFAULT_ON_FLAGS = Set.new([CATFILE_CACHE]).freeze + DEFAULT_ON_FLAGS = Set.new([]).freeze class << self def enabled?(feature_flag) diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb index 00c87cce7b6..da65caa6c9c 100644 --- a/lib/gitlab/asciidoc.rb +++ b/lib/gitlab/asciidoc.rb @@ -13,6 +13,7 @@ module Gitlab MAX_INCLUDE_DEPTH = 5 DEFAULT_ADOC_ATTRS = { 'showtitle' => true, + 'sectanchors' => true, 'idprefix' => 'user-content-', 'idseparator' => '-', 'env' => 'gitlab', diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb index c911bfa7ff6..afad391e8e0 100644 --- a/lib/gitlab/ci/pipeline/chain/command.rb +++ b/lib/gitlab/ci/pipeline/chain/command.rb @@ -20,6 +20,12 @@ module Gitlab end end + def uses_unsupported_legacy_trigger? + trigger_request.present? && + trigger_request.trigger.legacy? && + !trigger_request.trigger.supports_legacy_tokens? + end + def branch_exists? strong_memoize(:is_branch) do project.repository.branch_exists?(ref) diff --git a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb index aaa3daddcc5..357a1d55b3b 100644 --- a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb +++ b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb @@ -14,6 +14,10 @@ module Gitlab return error('Pipelines are disabled!') end + if @command.uses_unsupported_legacy_trigger? + return error('Trigger token is invalid because is not owned by any user') + end + unless allowed_to_trigger_pipeline? if can?(current_user, :create_pipeline, project) return error("Insufficient permissions for protected ref '#{command.ref}'") diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb index e4cf360a1c1..0212fa9d661 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb @@ -8,11 +8,10 @@ module Gitlab require_dependency 're2' class Pattern < Lexeme::Value - PATTERN = %r{^/.+/[ismU]*$}.freeze - NEW_PATTERN = %r{^\/([^\/]|\\/)+[^\\]\/[ismU]*}.freeze + PATTERN = %r{^\/([^\/]|\\/)+[^\\]\/[ismU]*}.freeze def initialize(regexp) - @value = self.class.eager_matching_with_escape_characters? ? regexp.gsub(/\\\//, '/') : regexp + @value = regexp.gsub(/\\\//, '/') unless Gitlab::UntrustedRegexp::RubySyntax.valid?(@value) raise Lexer::SyntaxError, 'Invalid regular expression!' @@ -26,16 +25,12 @@ module Gitlab end def self.pattern - eager_matching_with_escape_characters? ? NEW_PATTERN : PATTERN + PATTERN end def self.build(string) new(string) end - - def self.eager_matching_with_escape_characters? - Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true) - end end end end diff --git a/lib/gitlab/ci/pipeline/expression/lexer.rb b/lib/gitlab/ci/pipeline/expression/lexer.rb index 22c210ae26b..7d7582612f9 100644 --- a/lib/gitlab/ci/pipeline/expression/lexer.rb +++ b/lib/gitlab/ci/pipeline/expression/lexer.rb @@ -17,17 +17,6 @@ module Gitlab Expression::Lexeme::Equals, Expression::Lexeme::Matches, Expression::Lexeme::NotEquals, - Expression::Lexeme::NotMatches - ].freeze - - NEW_LEXEMES = [ - Expression::Lexeme::Variable, - Expression::Lexeme::String, - Expression::Lexeme::Pattern, - Expression::Lexeme::Null, - Expression::Lexeme::Equals, - Expression::Lexeme::Matches, - Expression::Lexeme::NotEquals, Expression::Lexeme::NotMatches, Expression::Lexeme::And, Expression::Lexeme::Or @@ -58,7 +47,7 @@ module Gitlab return tokens if @scanner.eos? - lexeme = available_lexemes.find do |type| + lexeme = LEXEMES.find do |type| type.scan(@scanner).tap do |token| tokens.push(token) if token.present? end @@ -71,10 +60,6 @@ module Gitlab raise Lexer::SyntaxError, 'Too many tokens!' end - - def available_lexemes - Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true) ? NEW_LEXEMES : LEXEMES - end end end end diff --git a/lib/gitlab/ci/pipeline/expression/parser.rb b/lib/gitlab/ci/pipeline/expression/parser.rb index 589bf32a4d7..edb55edf356 100644 --- a/lib/gitlab/ci/pipeline/expression/parser.rb +++ b/lib/gitlab/ci/pipeline/expression/parser.rb @@ -13,39 +13,6 @@ module Gitlab end def tree - if Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true) - rpn_parse_tree - else - reverse_descent_parse_tree - end - end - - def self.seed(statement) - new(Expression::Lexer.new(statement).tokens) - end - - private - - # This produces a reverse descent parse tree. - # It does not support precedence of operators. - def reverse_descent_parse_tree - while token = @tokens.next - case token.type - when :operator - token.build(@nodes.pop, tree).tap do |node| - @nodes.push(node) - end - when :value - token.build.tap do |leaf| - @nodes.push(leaf) - end - end - end - rescue StopIteration - @nodes.last || Lexeme::Null.new - end - - def rpn_parse_tree results = [] tokens_rpn.each do |token| @@ -70,6 +37,12 @@ module Gitlab results.pop end + def self.seed(statement) + new(Expression::Lexer.new(statement).tokens) + end + + private + # Parse the expression into Reverse Polish Notation # (See: Shunting-yard algorithm) def tokens_rpn diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml index f176771775e..89eccce69f6 100644 --- a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml @@ -41,6 +41,8 @@ dependency_scanning: DS_PULL_ANALYZER_IMAGE_TIMEOUT \ DS_RUN_ANALYZER_TIMEOUT \ DS_PYTHON_VERSION \ + PIP_INDEX_URL \ + PIP_EXTRA_INDEX_URL \ ) \ --volume "$PWD:/code" \ --volume /var/run/docker.sock:/var/run/docker.sock \ diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index 091351e5cb2..0b6321c66ab 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -211,8 +211,7 @@ module Gitlab metadata['call_site'] = feature.to_s if feature metadata['gitaly-servers'] = address_metadata(remote_storage) if remote_storage metadata['x-gitlab-correlation-id'] = Labkit::Correlation::CorrelationId.current_id if Labkit::Correlation::CorrelationId.current_id - metadata['gitaly-session-id'] = session_id if Feature::Gitaly.enabled?(Feature::Gitaly::CATFILE_CACHE) - + metadata['gitaly-session-id'] = session_id metadata.merge!(Feature::Gitaly.server_feature_flags) result = { metadata: metadata } diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index d8e9dccb644..ca3e5b51ecc 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -5,7 +5,7 @@ module Gitlab class RepositoryService include Gitlab::EncodingHelper - MAX_MSG_SIZE = 128.kilobytes.freeze + MAX_MSG_SIZE = 128.kilobytes def initialize(repository) @repository = repository diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index efd3f550a22..1b545b1d049 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -28,7 +28,7 @@ module Gitlab links: 'Releases::Link', metrics_setting: 'ProjectMetricsSetting' }.freeze - USER_REFERENCES = %w[author_id assignee_id updated_by_id merged_by_id latest_closed_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id closed_by_id].freeze + USER_REFERENCES = %w[author_id assignee_id updated_by_id merged_by_id latest_closed_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id closed_by_id owner_id].freeze PROJECT_REFERENCES = %w[project_id source_project_id target_project_id].freeze @@ -78,6 +78,9 @@ module Gitlab def create return if unknown_service? + # Do not import legacy triggers + return if !Feature.enabled?(:use_legacy_pipeline_triggers, @project) && legacy_trigger? + setup_models generate_imported_object @@ -278,6 +281,10 @@ module Gitlab !Object.const_defined?(parsed_relation_hash['type']) end + def legacy_trigger? + @relation_name == 'Ci::Trigger' && @relation_hash['owner_id'].nil? + end + def find_or_create_object! return relation_class.find_or_create_by(project_id: @project.id) if @relation_name == :project_feature diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 0180fe7fa71..055e01a9399 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -130,7 +130,7 @@ module Gitlab def usage_counters { - web_ide_commits: Gitlab::WebIdeCommitsCounter.total_count + web_ide_commits: Gitlab::UsageDataCounters::WebIdeCommitsCounter.total_count } end diff --git a/lib/gitlab/usage_data_counters/redis_counter.rb b/lib/gitlab/usage_data_counters/redis_counter.rb new file mode 100644 index 00000000000..123b8e1bef1 --- /dev/null +++ b/lib/gitlab/usage_data_counters/redis_counter.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Gitlab + module UsageDataCounters + module RedisCounter + def increment + Gitlab::Redis::SharedState.with { |redis| redis.incr(redis_counter_key) } + end + + def total_count + Gitlab::Redis::SharedState.with { |redis| redis.get(redis_counter_key).to_i } + end + + def redis_counter_key + raise NotImplementedError + end + end + end +end diff --git a/lib/gitlab/usage_data_counters/web_ide_commits_counter.rb b/lib/gitlab/usage_data_counters/web_ide_commits_counter.rb new file mode 100644 index 00000000000..62236fa07a3 --- /dev/null +++ b/lib/gitlab/usage_data_counters/web_ide_commits_counter.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Gitlab + module UsageDataCounters + class WebIdeCommitsCounter + extend RedisCounter + + def self.redis_counter_key + 'WEB_IDE_COMMITS_COUNT' + end + end + end +end diff --git a/lib/gitlab/web_ide_commits_counter.rb b/lib/gitlab/web_ide_commits_counter.rb deleted file mode 100644 index 1cd9b5295b9..00000000000 --- a/lib/gitlab/web_ide_commits_counter.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module WebIdeCommitsCounter - WEB_IDE_COMMITS_KEY = "WEB_IDE_COMMITS_COUNT".freeze - - class << self - def increment - Gitlab::Redis::SharedState.with { |redis| redis.incr(WEB_IDE_COMMITS_KEY) } - end - - def total_count - Gitlab::Redis::SharedState.with { |redis| redis.get(WEB_IDE_COMMITS_KEY).to_i } - end - end - end -end diff --git a/lib/tasks/gitlab/features.rake b/lib/tasks/gitlab/features.rake index d88bcca0819..9cf568c07fe 100644 --- a/lib/tasks/gitlab/features.rake +++ b/lib/tasks/gitlab/features.rake @@ -10,14 +10,22 @@ namespace :gitlab do set_rugged_feature_flags(false) puts 'All Rugged feature flags were disabled.' end + + task unset_rugged: :environment do + set_rugged_feature_flags(nil) + puts 'All Rugged feature flags were unset.' + end end def set_rugged_feature_flags(status) Gitlab::Git::RuggedImpl::Repository::FEATURE_FLAGS.each do |flag| - if status - Feature.enable(flag) - else + case status + when nil Feature.get(flag).remove + when true + Feature.enable(flag) + when false + Feature.disable(flag) end end end diff --git a/lib/tasks/gitlab/seed.rake b/lib/tasks/gitlab/seed.rake index 155ba979b36..d76e38b73b5 100644 --- a/lib/tasks/gitlab/seed.rake +++ b/lib/tasks/gitlab/seed.rake @@ -1,7 +1,9 @@ namespace :gitlab do namespace :seed do desc "GitLab | Seed | Seeds issues" - task :issues, [:project_full_path] => :environment do |t, args| + task :issues, [:project_full_path, :backfill_weeks, :average_issues_per_week] => :environment do |t, args| + args.with_defaults(backfill_weeks: 5, average_issues_per_week: 2) + projects = if args.project_full_path project = Project.find_by_full_path(args.project_full_path) @@ -26,7 +28,8 @@ namespace :gitlab do projects.each do |project| puts "\nSeeding issues for the '#{project.full_path}' project" seeder = Quality::Seeders::Issues.new(project: project) - issues_created = seeder.seed(backfill_weeks: 5, average_issues_per_week: 2) + issues_created = seeder.seed(backfill_weeks: args.backfill_weeks.to_i, + average_issues_per_week: args.average_issues_per_week.to_i) puts "\n#{issues_created} issues created!" end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f498961c8d1..5e8d1ac206a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -138,9 +138,15 @@ msgstr[1] "" msgid "%{edit_in_new_fork_notice} Try to cherry-pick this commit again." msgstr "" +msgid "%{edit_in_new_fork_notice} Try to create a new directory again." +msgstr "" + msgid "%{edit_in_new_fork_notice} Try to revert this commit again." msgstr "" +msgid "%{edit_in_new_fork_notice} Try to upload a file again." +msgstr "" + msgid "%{filePath} deleted" msgstr "" @@ -704,6 +710,9 @@ msgstr "" msgid "Add to review" msgstr "" +msgid "Add to tree" +msgstr "" + msgid "Add user(s) to the group:" msgstr "" diff --git a/qa/qa/page/main/oauth.rb b/qa/qa/page/main/oauth.rb index 5f6ddb9a114..2b1a9ab2b6a 100644 --- a/qa/qa/page/main/oauth.rb +++ b/qa/qa/page/main/oauth.rb @@ -5,7 +5,7 @@ module QA module Main class OAuth < Page::Base view 'app/views/doorkeeper/authorizations/new.html.haml' do - element :authorization_button, 'submit_tag _("Authorize")' # rubocop:disable QA/ElementWithPattern + element :authorization_button end def needs_authorization? @@ -13,7 +13,7 @@ module QA end def authorize! - click_button 'Authorize' + click_element :authorization_button end end end diff --git a/qa/qa/page/main/sign_up.rb b/qa/qa/page/main/sign_up.rb index 46a105003d0..c47d2ce9c74 100644 --- a/qa/qa/page/main/sign_up.rb +++ b/qa/qa/page/main/sign_up.rb @@ -5,28 +5,28 @@ module QA module Main class SignUp < Page::Base view 'app/views/devise/shared/_signup_box.html.haml' do - element :new_user_name - element :new_user_username - element :new_user_email - element :new_user_email_confirmation - element :new_user_password + element :new_user_name_field + element :new_user_username_field + element :new_user_email_field + element :new_user_email_confirmation_field + element :new_user_password_field element :new_user_register_button - element :new_user_accept_terms + element :new_user_accept_terms_checkbox end def sign_up!(user) - fill_element :new_user_name, user.name - fill_element :new_user_username, user.username - fill_element :new_user_email, user.email - fill_element :new_user_email_confirmation, user.email - fill_element :new_user_password, user.password + fill_element :new_user_name_field, user.name + fill_element :new_user_username_field, user.username + fill_element :new_user_email_field, user.email + fill_element :new_user_email_confirmation_field, user.email + fill_element :new_user_password_field, user.password - check_element :new_user_accept_terms if has_element?(:new_user_accept_terms) + check_element :new_user_accept_terms_checkbox if has_element?(:new_user_accept_terms_checkbox) signed_in = retry_until do click_element :new_user_register_button - Page::Main::Menu.act { has_personal_area? } + Page::Main::Menu.perform(&:has_personal_area?) end raise "Failed to register and sign in" unless signed_in diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb index 4f625c5f0f0..eb30e0ea02a 100644 --- a/qa/qa/page/project/operations/kubernetes/show.rb +++ b/qa/qa/page/project/operations/kubernetes/show.rb @@ -28,16 +28,12 @@ module QA end end - def await_installed(application_name, button_text: 'Installed') + def await_installed(application_name) within(".js-cluster-application-row-#{application_name}") do - page.has_text?(button_text, wait: 300) + page.has_text?(/Installed|Uninstall/, wait: 300) end end - def await_uninstallable(application_name) - await_installed(application_name, button_text: 'Uninstall') - end - def ingress_ip # We need to wait longer since it can take some time before the # ip address is assigned for the ingress controller diff --git a/qa/qa/page/project/sub_menus/common.rb b/qa/qa/page/project/sub_menus/common.rb index c94e1e85256..3c9e8085748 100644 --- a/qa/qa/page/project/sub_menus/common.rb +++ b/qa/qa/page/project/sub_menus/common.rb @@ -12,7 +12,11 @@ module QA end def within_submenu - within('.fly-out-list') do + if has_css?('.fly-out-list') + within('.fly-out-list') do + yield + end + else yield end end diff --git a/qa/qa/resource/kubernetes_cluster.rb b/qa/qa/resource/kubernetes_cluster.rb index 1dd93dd5b88..27ab7b60211 100644 --- a/qa/qa/resource/kubernetes_cluster.rb +++ b/qa/qa/resource/kubernetes_cluster.rb @@ -47,7 +47,7 @@ module QA page.install!(:runner) if @install_runner page.await_installed(:ingress) if @install_ingress - page.await_uninstallable(:prometheus) if @install_prometheus + page.await_installed(:prometheus) if @install_prometheus page.await_installed(:runner) if @install_runner if @install_ingress diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb index 45cb317e0eb..7969de726e4 100644 --- a/qa/qa/resource/merge_request.rb +++ b/qa/qa/resource/merge_request.rb @@ -9,6 +9,7 @@ module QA :description, :source_branch, :target_branch, + :target_new_branch, :assignee, :milestone, :labels, @@ -27,6 +28,7 @@ module QA Repository::ProjectPush.fabricate! do |resource| resource.project = project resource.branch_name = 'master' + resource.new_branch = @target_new_branch resource.remote_branch = target_branch end end @@ -52,6 +54,7 @@ module QA @labels = [] @file_name = "added_file.txt" @file_content = "File Added" + @target_new_branch = true end def fabricate! diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh index 2055ce7f09d..8c9b8b9fb02 100755 --- a/scripts/lint-doc.sh +++ b/scripts/lint-doc.sh @@ -45,7 +45,7 @@ then then echo echo ' ✖ ERROR: New README.md file(s) detected, prefer index.md over README.md.' >&2 - echo ' https://docs.gitlab.com/ee/development/writing_documentation.html#location-and-naming-documents' + echo ' https://docs.gitlab.com/ee/development/documentation/styleguide.html#working-with-directories-and-files' echo exit 1 fi @@ -55,7 +55,7 @@ then then echo echo ' ✖ ERROR: New README.md file(s) detected, prefer index.md over README.md.' >&2 - echo ' https://docs.gitlab.com/ee/development/writing_documentation.html#location-and-naming-documents' + echo ' https://docs.gitlab.com/ee/development/documentation/styleguide.html#working-with-directories-and-files' echo exit 1 fi diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index 5ad5f9cdeea..4eb0545eb6c 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -41,7 +41,7 @@ describe Admin::ApplicationSettingsController do it 'returns JSON data' do get :usage_data, format: :json - body = JSON.parse(response.body) + body = json_response expect(body["version"]).to eq(Gitlab::VERSION) expect(body).to include('counts') expect(response.status).to eq(200) diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 447a12b2fac..84bbbac39b0 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -63,8 +63,6 @@ describe ApplicationController do sign_in user end - let(:json_response) { JSON.parse(response.body) } - controller(described_class) do def index render json: Gon.all_variables diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb index 6cad060d888..0db58fbefc1 100644 --- a/spec/controllers/boards/issues_controller_spec.rb +++ b/spec/controllers/boards/issues_controller_spec.rb @@ -52,10 +52,8 @@ describe Boards::IssuesController do list_issues user: user, board: board, list: list2 - parsed_response = JSON.parse(response.body) - expect(response).to match_response_schema('entities/issue_boards') - expect(parsed_response['issues'].length).to eq 2 + expect(json_response['issues'].length).to eq 2 expect(development.issues.map(&:relative_position)).not_to include(nil) end @@ -123,10 +121,8 @@ describe Boards::IssuesController do list_issues user: user, board: board - parsed_response = JSON.parse(response.body) - expect(response).to match_response_schema('entities/issue_boards') - expect(parsed_response['issues'].length).to eq 2 + expect(json_response['issues'].length).to eq 2 end end @@ -164,7 +160,7 @@ describe Boards::IssuesController do end end - describe 'PUT move_multiple' do + describe 'PUT bulk_move' do let(:todo) { create(:group_label, group: group, name: 'Todo') } let(:development) { create(:group_label, group: group, name: 'Development') } let(:user) { create(:group_member, :maintainer, user: create(:user), group: group ).user } @@ -200,6 +196,20 @@ describe Boards::IssuesController do sign_in(signed_in_user) end + it 'responds as expected' do + put :bulk_move, params: move_issues_params + expect(response).to have_gitlab_http_status(expected_status) + + if expected_status == 200 + expect(json_response).to include( + 'count' => move_issues_params[:ids].size, + 'success' => true + ) + + expect(json_response['issues'].pluck('id')).to match_array(move_issues_params[:ids]) + end + end + it 'moves issues as expected' do put :bulk_move, params: move_issues_params expect(response).to have_gitlab_http_status(expected_status) diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb index e1f75fa3395..418ca6f3210 100644 --- a/spec/controllers/boards/lists_controller_spec.rb +++ b/spec/controllers/boards/lists_controller_spec.rb @@ -26,10 +26,8 @@ describe Boards::ListsController do read_board_list user: user, board: board - parsed_response = JSON.parse(response.body) - expect(response).to match_response_schema('lists') - expect(parsed_response.length).to eq 3 + expect(json_response.length).to eq 3 end context 'with unauthorized user' do diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb index 5e0f64ccca4..e4232c2c1ab 100644 --- a/spec/controllers/groups/boards_controller_spec.rb +++ b/spec/controllers/groups/boards_controller_spec.rb @@ -63,10 +63,8 @@ describe Groups::BoardsController do list_boards format: :json - parsed_response = JSON.parse(response.body) - expect(response).to match_response_schema('boards') - expect(parsed_response.length).to eq 1 + expect(json_response.length).to eq 1 end context 'with unauthorized user' do diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index 19b18091aef..bf164aeed38 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -73,7 +73,7 @@ describe Groups::MilestonesController do it 'lists legacy group milestones and group milestones' do get :index, params: { group_id: group.to_param }, format: :json - milestones = JSON.parse(response.body) + milestones = json_response expect(milestones.count).to eq(2) expect(milestones.first["title"]).to eq("group milestone") diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb index 19d739fcf4f..92f005faf4a 100644 --- a/spec/controllers/health_check_controller_spec.rb +++ b/spec/controllers/health_check_controller_spec.rb @@ -5,7 +5,6 @@ require 'spec_helper' describe HealthCheckController do include StubENV - let(:json_response) { JSON.parse(response.body) } let(:xml_response) { Hash.from_xml(response.body)['hash'] } let(:token) { Gitlab::CurrentSettings.health_check_access_token } let(:whitelisted_ip) { '127.0.0.1' } diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb index fc62a8310aa..e82dcfcdb64 100644 --- a/spec/controllers/health_controller_spec.rb +++ b/spec/controllers/health_controller_spec.rb @@ -5,7 +5,6 @@ require 'spec_helper' describe HealthController do include StubENV - let(:json_response) { JSON.parse(response.body) } let(:token) { Gitlab::CurrentSettings.health_check_access_token } let(:whitelisted_ip) { '127.0.0.1' } let(:not_whitelisted_ip) { '127.0.0.2' } diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb index ee454a7818c..84027119491 100644 --- a/spec/controllers/metrics_controller_spec.rb +++ b/spec/controllers/metrics_controller_spec.rb @@ -5,7 +5,6 @@ require 'spec_helper' describe MetricsController do include StubENV - let(:json_response) { JSON.parse(response.body) } let(:metrics_multiproc_dir) { Dir.mktmpdir } let(:whitelisted_ip) { '127.0.0.1' } let(:whitelisted_ip_range) { '10.0.0.0/24' } diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 44500d3cde3..45aebd1554c 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -160,7 +160,7 @@ describe Projects::BlobController do it 'renders diff context lines Gitlab::Diff::Line array' do do_get(since: 1, to: 2, offset: 0, from_merge_request: true) - lines = JSON.parse(response.body) + lines = json_response expect(lines.size).to eq(diff_lines.size) lines.each do |line| @@ -173,7 +173,7 @@ describe Projects::BlobController do it 'handles full being true' do do_get(full: true, from_merge_request: true) - lines = JSON.parse(response.body) + lines = json_response expect(lines.size).to eq(diff_lines.size) end diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index c07afc57aea..543479d8dd5 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -69,10 +69,8 @@ describe Projects::BoardsController do list_boards format: :json - parsed_response = JSON.parse(response.body) - expect(response).to match_response_schema('boards') - expect(parsed_response.length).to eq 2 + expect(json_response.length).to eq 2 end context 'with unauthorized user' do diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index b30966e70a7..f5bcea4a097 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -495,10 +495,8 @@ describe Projects::BranchesController do search: 'master' } - parsed_response = JSON.parse(response.body) - - expect(parsed_response.length).to eq 1 - expect(parsed_response.first).to eq 'master' + expect(json_response.length).to eq 1 + expect(json_response.first).to eq 'master' end end @@ -591,8 +589,7 @@ describe Projects::BranchesController do end it 'returns the commit counts behind and ahead of default branch' do - parsed_response = JSON.parse(response.body) - expect(parsed_response).to eq( + expect(json_response).to eq( "fix" => { "behind" => 29, "ahead" => 2 }, "branch-merged" => { "behind" => 1, "ahead" => 0 }, "add-pdf-file" => { "behind" => 0, "ahead" => 3 } diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index b5c6382a26d..58a1d96d010 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -378,8 +378,8 @@ describe Projects::CommitController do get_pipelines(id: commit.id, format: :json) expect(response).to be_ok - expect(JSON.parse(response.body)['pipelines']).not_to be_empty - expect(JSON.parse(response.body)['count']['all']).to eq 1 + expect(json_response['pipelines']).not_to be_empty + expect(json_response['count']['all']).to eq 1 expect(response).to include_pagination_headers end end diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index 92380a2bf09..48a92a772dc 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -302,8 +302,7 @@ describe Projects::CompareController do signatures_request expect(response).to have_gitlab_http_status(200) - parsed_body = JSON.parse(response.body) - signatures = parsed_body['signatures'] + signatures = json_response['signatures'] expect(signatures.size).to eq(1) expect(signatures.first['commit_sha']).to eq(signature_commit.sha) @@ -332,8 +331,7 @@ describe Projects::CompareController do signatures_request expect(response).to have_gitlab_http_status(200) - parsed_body = JSON.parse(response.body) - expect(parsed_body['signatures']).to be_empty + expect(json_response['signatures']).to be_empty end end @@ -345,8 +343,7 @@ describe Projects::CompareController do signatures_request expect(response).to have_gitlab_http_status(200) - parsed_body = JSON.parse(response.body) - expect(parsed_body['signatures']).to be_empty + expect(json_response['signatures']).to be_empty end end end diff --git a/spec/controllers/projects/deploy_keys_controller_spec.rb b/spec/controllers/projects/deploy_keys_controller_spec.rb index fcd14f13863..ccad76eaddd 100644 --- a/spec/controllers/projects/deploy_keys_controller_spec.rb +++ b/spec/controllers/projects/deploy_keys_controller_spec.rb @@ -52,12 +52,10 @@ describe Projects::DeployKeysController do it 'returns json in a correct format' do get :index, params: params.merge(format: :json) - json = JSON.parse(response.body) - - expect(json.keys).to match_array(%w(enabled_keys available_project_keys public_keys)) - expect(json['enabled_keys'].count).to eq(1) - expect(json['available_project_keys'].count).to eq(1) - expect(json['public_keys'].count).to eq(1) + expect(json_response.keys).to match_array(%w(enabled_keys available_project_keys public_keys)) + expect(json_response['enabled_keys'].count).to eq(1) + expect(json_response['available_project_keys'].count).to eq(1) + expect(json_response['public_keys'].count).to eq(1) end end end diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb index 4c29162cd0f..e30b28a4bd5 100644 --- a/spec/controllers/projects/discussions_controller_spec.rb +++ b/spec/controllers/projects/discussions_controller_spec.rb @@ -112,7 +112,7 @@ describe Projects::DiscussionsController do it "returns the name of the resolving user" do post :resolve, params: request_params - expect(JSON.parse(response.body)['resolved_by']['name']).to eq(user.name) + expect(json_response['resolved_by']['name']).to eq(user.name) end it "returns status 200" do @@ -135,7 +135,7 @@ describe Projects::DiscussionsController do it "returns truncated diff lines" do post :resolve, params: request_params - expect(JSON.parse(response.body)['truncated_diff_lines']).to be_present + expect(json_response['truncated_diff_lines']).to be_present end end end diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index 4c2c6160c62..ebbbebf1bc0 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe Projects::EnvironmentsController do + include MetricsDashboardHelpers + set(:user) { create(:user) } set(:project) { create(:project) } @@ -445,131 +447,186 @@ describe Projects::EnvironmentsController do end end - describe 'metrics_dashboard' do - context 'when prometheus endpoint is disabled' do - before do - stub_feature_flags(environment_metrics_use_prometheus_endpoint: false) - end + describe 'GET #metrics_dashboard' do + shared_examples_for 'correctly formatted response' do |status_code| + it 'returns a json object with the correct keys' do + get :metrics_dashboard, params: environment_params(dashboard_params) - it 'responds with status code 403' do - get :metrics_dashboard, params: environment_params(format: :json) + # Exlcude `all_dashboards` to handle separately. + found_keys = json_response.keys - ['all_dashboards'] - expect(response).to have_gitlab_http_status(:forbidden) + expect(response).to have_gitlab_http_status(status_code) + expect(found_keys).to contain_exactly(*expected_keys) end end - shared_examples_for '200 response' do |contains_all_dashboards: false| + shared_examples_for '200 response' do let(:expected_keys) { %w(dashboard status) } - before do - expected_keys << 'all_dashboards' if contains_all_dashboards - end - - it 'returns a json representation of the environment dashboard' do - get :metrics_dashboard, params: environment_params(dashboard_params) - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response.keys).to contain_exactly(*expected_keys) - expect(json_response['dashboard']).to be_an_instance_of(Hash) - end + it_behaves_like 'correctly formatted response', :ok end - shared_examples_for 'error response' do |status_code, contains_all_dashboards: false| + shared_examples_for 'error response' do |status_code| let(:expected_keys) { %w(message status) } - before do - expected_keys << 'all_dashboards' if contains_all_dashboards - end + it_behaves_like 'correctly formatted response', status_code + end - it 'returns an error response' do + shared_examples_for 'includes all dashboards' do + it 'includes info for all findable dashboard' do get :metrics_dashboard, params: environment_params(dashboard_params) - expect(response).to have_gitlab_http_status(status_code) - expect(json_response.keys).to contain_exactly(*expected_keys) + expect(json_response).to have_key('all_dashboards') + expect(json_response['all_dashboards']).to be_an_instance_of(Array) + expect(json_response['all_dashboards']).to all( include('path', 'default', 'display_name') ) end end - shared_examples_for 'has all dashboards' do - it 'includes an index of all available dashboards' do + shared_examples_for 'the default dashboard' do + all_dashboards = Feature.enabled?(:environment_metrics_show_multiple_dashboards) + + it_behaves_like '200 response' + it_behaves_like 'includes all dashboards' if all_dashboards + + it 'is the default dashboard' do get :metrics_dashboard, params: environment_params(dashboard_params) - expect(json_response.keys).to include('all_dashboards') - expect(json_response['all_dashboards']).to be_an_instance_of(Array) - expect(json_response['all_dashboards']).to all( include('path', 'default') ) + expect(json_response['dashboard']['dashboard']).to eq('Environment metrics') end end - context 'when multiple dashboards is disabled' do - before do - stub_feature_flags(environment_metrics_show_multiple_dashboards: false) - end + shared_examples_for 'the specified dashboard' do |expected_dashboard| + it_behaves_like '200 response' + it_behaves_like 'includes all dashboards' - let(:dashboard_params) { { format: :json } } + it 'has the correct name' do + get :metrics_dashboard, params: environment_params(dashboard_params) - it_behaves_like '200 response' + dashboard_name = json_response['dashboard']['dashboard'] - context 'when the dashboard could not be provided' do + # 'Environment metrics' is the default dashboard. + expect(dashboard_name).not_to eq('Environment metrics') + expect(dashboard_name).to eq(expected_dashboard) + end + + context 'when the dashboard cannot not be processed' do before do allow(YAML).to receive(:safe_load).and_return({}) end it_behaves_like 'error response', :unprocessable_entity end - - context 'when a dashboard param is specified' do - let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/not_there_dashboard.yml' } } - - it_behaves_like '200 response' - end end - context 'when multiple dashboards is enabled' do - let(:dashboard_params) { { format: :json } } + shared_examples_for 'the default dynamic dashboard' do + it_behaves_like '200 response' - it_behaves_like '200 response', contains_all_dashboards: true - it_behaves_like 'has all dashboards' + it 'contains only the Memory and CPU charts' do + get :metrics_dashboard, params: environment_params(dashboard_params) - context 'when a dashboard could not be provided' do - before do - allow(YAML).to receive(:safe_load).and_return({}) - end + dashboard = json_response['dashboard'] + panel_group = dashboard['panel_groups'].first + titles = panel_group['panels'].map { |panel| panel['title'] } - it_behaves_like 'error response', :unprocessable_entity, contains_all_dashboards: true - it_behaves_like 'has all dashboards' + expect(dashboard['dashboard']).to be_nil + expect(dashboard['panel_groups'].length).to eq 1 + expect(panel_group['group']).to be_nil + expect(titles).to eq ['Memory Usage (Total)', 'Core Usage (Total)'] end + end - context 'when a dashboard param is specified' do - let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml' } } + shared_examples_for 'dashboard can be specified' do + context 'when dashboard is specified' do + let(:dashboard_path) { '.gitlab/dashboards/test.yml' } + let(:dashboard_params) { { format: :json, dashboard: dashboard_path } } + + it_behaves_like 'error response', :not_found - context 'when the dashboard is available' do + context 'when the project dashboard is available' do let(:dashboard_yml) { fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') } - let(:dashboard_file) { { '.gitlab/dashboards/test.yml' => dashboard_yml } } - let(:project) { create(:project, :custom_repo, files: dashboard_file) } + let(:project) { project_with_dashboard(dashboard_path, dashboard_yml) } let(:environment) { create(:environment, name: 'production', project: project) } - it_behaves_like '200 response', contains_all_dashboards: true - it_behaves_like 'has all dashboards' + it_behaves_like 'the specified dashboard', 'Test Dashboard' end - context 'when the dashboard does not exist' do - it_behaves_like 'error response', :not_found, contains_all_dashboards: true - it_behaves_like 'has all dashboards' + context 'when the specified dashboard is the default dashboard' do + let(:dashboard_path) { Gitlab::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH } + + it_behaves_like 'the default dashboard' end end + end - context 'when the dashboard is intended for embedding' do + shared_examples_for 'dashboard can be embedded' do + context 'when the embedded flag is included' do let(:dashboard_params) { { format: :json, embedded: true } } - it_behaves_like '200 response' + it_behaves_like 'the default dynamic dashboard' - context 'when a dashboard path is provided' do - let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml', embedded: true } } + context 'when the dashboard is specified' do + let(:dashboard_params) { { format: :json, embedded: true, dashboard: '.gitlab/dashboards/fake.yml' } } - # The dashboard path should simple be ignored. - it_behaves_like '200 response' + # The dashboard param should be ignored. + it_behaves_like 'the default dynamic dashboard' end end end + + shared_examples_for 'dashboard cannot be specified' do + context 'when dashboard is specified' do + let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml' } } + + it_behaves_like 'the default dashboard' + end + end + + shared_examples_for 'dashboard cannot be embedded' do + context 'when the embedded flag is included' do + let(:dashboard_params) { { format: :json, embedded: true } } + + it_behaves_like 'the default dashboard' + end + end + + let(:dashboard_params) { { format: :json } } + + it_behaves_like 'the default dashboard' + it_behaves_like 'dashboard can be specified' + it_behaves_like 'dashboard can be embedded' + + context 'when multiple dashboards is enabled and embedding metrics is disabled' do + before do + stub_feature_flags(gfm_embedded_metrics: false) + end + + it_behaves_like 'the default dashboard' + it_behaves_like 'dashboard can be specified' + it_behaves_like 'dashboard cannot be embedded' + end + + context 'when multiple dashboards is disabled and embedding metrics is enabled' do + before do + stub_feature_flags(environment_metrics_show_multiple_dashboards: false) + end + + it_behaves_like 'the default dashboard' + it_behaves_like 'dashboard cannot be specified' + it_behaves_like 'dashboard can be embedded' + end + + context 'when multiple dashboards and embedding metrics are disabled' do + before do + stub_feature_flags( + environment_metrics_show_multiple_dashboards: false, + gfm_embedded_metrics: false + ) + end + + it_behaves_like 'the default dashboard' + it_behaves_like 'dashboard cannot be specified' + it_behaves_like 'dashboard cannot be embedded' + end end describe 'GET #search' do diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb index 538dbb5ad0b..a493985f8a0 100644 --- a/spec/controllers/projects/find_file_controller_spec.rb +++ b/spec/controllers/projects/find_file_controller_spec.rb @@ -53,10 +53,9 @@ describe Projects::FindFileController do it 'returns an array of file path list' do go - json = JSON.parse(response.body) is_expected.to respond_with(:success) - expect(json).not_to eq(nil) - expect(json.length).to be >= 0 + expect(json_response).not_to eq(nil) + expect(json_response.length).to be >= 0 end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index bc5e0b4671e..32d14dce936 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -444,7 +444,7 @@ describe Projects::IssuesController do it 'renders json with recaptcha_html' do subject - expect(JSON.parse(response.body)).to have_key('recaptcha_html') + expect(json_response).to have_key('recaptcha_html') end end end @@ -484,10 +484,8 @@ describe Projects::IssuesController do it 'returns last edited time' do go(id: issue.iid) - data = JSON.parse(response.body) - - expect(data).to include('updated_at') - expect(data['updated_at']).to eq(issue.last_edited_at.to_time.iso8601) + expect(json_response).to include('updated_at') + expect(json_response['updated_at']).to eq(issue.last_edited_at.to_time.iso8601) end end @@ -520,10 +518,8 @@ describe Projects::IssuesController do it 'returns the necessary data' do go(id: issue.iid) - data = JSON.parse(response.body) - - expect(data).to include('title_text', 'description', 'description_text') - expect(data).to include('task_status', 'lock_version') + expect(json_response).to include('title_text', 'description', 'description_text') + expect(json_response).to include('task_status', 'lock_version') end end end @@ -692,9 +688,7 @@ describe Projects::IssuesController do update_issue(issue_params: { assignee_ids: [assignee.id] }) - body = JSON.parse(response.body) - - expect(body['assignees'].first.keys) + expect(json_response['assignees'].first.keys) .to match_array(%w(id name username avatar_url state web_url)) end end @@ -1314,7 +1308,7 @@ describe Projects::IssuesController do it 'filters notes that the user should not see' do get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } - expect(JSON.parse(response.body).count).to eq(1) + expect(json_response.count).to eq(1) end it 'does not result in N+1 queries' do diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb index 13a28b738ca..d940d226176 100644 --- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb @@ -112,7 +112,7 @@ describe Projects::MergeRequests::DiffsController do it 'only renders the diffs for the path given' do diff_for_path(old_path: existing_path, new_path: existing_path) - paths = JSON.parse(response.body)["diff_files"].map { |file| file['new_path'] } + paths = json_response["diff_files"].map { |file| file['new_path'] } expect(paths).to include(existing_path) end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index cc6adc0a6c6..f11880122b1 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -242,9 +242,7 @@ describe Projects::MergeRequestsController do update_merge_request({ assignee_ids: [assignee.id] }, format: :json) - body = JSON.parse(response.body) - - expect(body['assignees']).to all(include(*%w(name username avatar_url id state web_url))) + expect(json_response['assignees']).to all(include(*%w(name username avatar_url id state web_url))) end end diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index 1db1963476c..98aea9056dc 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -29,7 +29,7 @@ describe Projects::NotesController do } end - let(:parsed_response) { JSON.parse(response.body).with_indifferent_access } + let(:parsed_response) { json_response.with_indifferent_access } let(:note_json) { parsed_response[:notes].first } before do @@ -614,7 +614,7 @@ describe Projects::NotesController do it "returns the name of the resolving user" do post :resolve, params: request_params.merge(html: true) - expect(JSON.parse(response.body)["resolved_by"]).to eq(user.name) + expect(json_response["resolved_by"]).to eq(user.name) end it "returns status 200" do diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb index 9e7d34b10c0..d5ef2b0e114 100644 --- a/spec/controllers/projects/templates_controller_spec.rb +++ b/spec/controllers/projects/templates_controller_spec.rb @@ -7,7 +7,6 @@ describe Projects::TemplatesController do let(:user) { create(:user) } let(:file_path_1) { '.gitlab/issue_templates/issue_template.md' } let(:file_path_2) { '.gitlab/merge_request_templates/merge_request_template.md' } - let(:body) { JSON.parse(response.body) } let!(:file_1) { project.repository.create_file(user, file_path_1, 'issue content', message: 'message', branch_name: 'master') } let!(:file_2) { project.repository.create_file(user, file_path_2, 'merge request content', message: 'message', branch_name: 'master') } @@ -17,8 +16,8 @@ describe Projects::TemplatesController do get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template', project_id: project }, format: :json) expect(response.status).to eq(200) - expect(body['name']).to eq('issue_template') - expect(body['content']).to eq('issue content') + expect(json_response['name']).to eq('issue_template') + expect(json_response['content']).to eq('issue content') end end @@ -27,8 +26,8 @@ describe Projects::TemplatesController do get(:show, params: { namespace_id: project.namespace, template_type: 'merge_request', key: 'merge_request_template', project_id: project }, format: :json) expect(response.status).to eq(200) - expect(body['name']).to eq('merge_request_template') - expect(body['content']).to eq('merge request content') + expect(json_response['name']).to eq('merge_request_template') + expect(json_response['content']).to eq('merge request content') end end diff --git a/spec/controllers/projects/wikis_controller_spec.rb b/spec/controllers/projects/wikis_controller_spec.rb index f2e0b5e5c1d..a7e5a79b51d 100644 --- a/spec/controllers/projects/wikis_controller_spec.rb +++ b/spec/controllers/projects/wikis_controller_spec.rb @@ -103,7 +103,7 @@ describe Projects::WikisController do it 'renders json in a correct format' do post :preview_markdown, params: { namespace_id: project.namespace, project_id: project, id: 'page/path', text: '*Markdown* text' } - expect(JSON.parse(response.body).keys).to match_array(%w(body references)) + expect(json_response.keys).to match_array(%w(body references)) end end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 4e1cac67d23..083a1c1383a 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -740,20 +740,18 @@ describe ProjectsController do it 'gets a list of branches and tags' do get :refs, params: { namespace_id: project.namespace, id: project, sort: 'updated_desc' } - parsed_body = JSON.parse(response.body) - expect(parsed_body['Branches']).to include('master') - expect(parsed_body['Tags'].first).to eq('v1.1.0') - expect(parsed_body['Tags'].last).to eq('v1.0.0') - expect(parsed_body['Commits']).to be_nil + expect(json_response['Branches']).to include('master') + expect(json_response['Tags'].first).to eq('v1.1.0') + expect(json_response['Tags'].last).to eq('v1.0.0') + expect(json_response['Commits']).to be_nil end it "gets a list of branches, tags and commits" do get :refs, params: { namespace_id: project.namespace, id: project, ref: "123456" } - parsed_body = JSON.parse(response.body) - expect(parsed_body["Branches"]).to include("master") - expect(parsed_body["Tags"]).to include("v1.0.0") - expect(parsed_body["Commits"]).to include("123456") + expect(json_response["Branches"]).to include("master") + expect(json_response["Tags"]).to include("v1.0.0") + expect(json_response["Commits"]).to include("123456") end context "when preferred language is Japanese" do @@ -765,10 +763,9 @@ describe ProjectsController do it "gets a list of branches, tags and commits" do get :refs, params: { namespace_id: project.namespace, id: project, ref: "123456" } - parsed_body = JSON.parse(response.body) - expect(parsed_body["Branches"]).to include("master") - expect(parsed_body["Tags"]).to include("v1.0.0") - expect(parsed_body["Commits"]).to include("123456") + expect(json_response["Branches"]).to include("master") + expect(json_response["Tags"]).to include("v1.0.0") + expect(json_response["Commits"]).to include("123456") end end @@ -797,7 +794,7 @@ describe ProjectsController do it 'renders json in a correct format' do post :preview_markdown, params: { namespace_id: public_project.namespace, id: public_project, text: '*Markdown* text' } - expect(JSON.parse(response.body).keys).to match_array(%w(body references)) + expect(json_response.keys).to match_array(%w(body references)) end context 'when not authorized' do @@ -821,8 +818,6 @@ describe ProjectsController do text: issue.to_reference } - json_response = JSON.parse(response.body) - expect(json_response['body']).to match(/\##{issue.iid} \(closed\)/) end @@ -833,8 +828,6 @@ describe ProjectsController do text: merge_request.to_reference } - json_response = JSON.parse(response.body) - expect(json_response['body']).to match(/\!#{merge_request.iid} \(closed\)/) end end diff --git a/spec/controllers/snippets/notes_controller_spec.rb b/spec/controllers/snippets/notes_controller_spec.rb index 586d59c2d09..652533ac49f 100644 --- a/spec/controllers/snippets/notes_controller_spec.rb +++ b/spec/controllers/snippets/notes_controller_spec.rb @@ -26,7 +26,7 @@ describe Snippets::NotesController do end it "returns not empty array of notes" do - expect(JSON.parse(response.body)["notes"].empty?).to be_falsey + expect(json_response["notes"].empty?).to be_falsey end end @@ -97,7 +97,7 @@ describe Snippets::NotesController do it "returns 1 note" do get :index, params: { snippet_id: private_snippet } - expect(JSON.parse(response.body)['notes'].count).to eq(1) + expect(json_response['notes'].count).to eq(1) end end end @@ -114,7 +114,7 @@ describe Snippets::NotesController do it "does not return any note" do get :index, params: { snippet_id: public_snippet } - expect(JSON.parse(response.body)['notes'].count).to eq(0) + expect(json_response['notes'].count).to eq(0) end end end diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index 3aba02bf3ff..b0092bc8994 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -622,7 +622,7 @@ describe SnippetsController do post :preview_markdown, params: { id: snippet, text: '*Markdown* text' } - expect(JSON.parse(response.body).keys).to match_array(%w(body references)) + expect(json_response.keys).to match_array(%w(body references)) end end end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index c3d6ea9cbcd..8b8d4c57000 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -291,7 +291,7 @@ describe UsersController do it 'response with snippets json data' do get :snippets, params: { username: user.username }, format: :json expect(response).to have_gitlab_http_status(200) - expect(JSON.parse(response.body)).to have_key('html') + expect(json_response).to have_key('html') end end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 743ec322885..7d7738a30c8 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -306,34 +306,18 @@ FactoryBot.define do factory :redmine_project, parent: :project do has_external_issue_tracker true - after :create do |project| - project.create_redmine_service( - active: true, - properties: { - 'project_url' => 'http://redmine/projects/project_name_in_redmine', - 'issues_url' => 'http://redmine/projects/project_name_in_redmine/issues/:id', - 'new_issue_url' => 'http://redmine/projects/project_name_in_redmine/issues/new' - } - ) - end + redmine_service end factory :youtrack_project, parent: :project do has_external_issue_tracker true - after :create do |project| - project.create_youtrack_service( - active: true, - properties: { - 'project_url' => 'http://youtrack/projects/project_guid_in_youtrack', - 'issues_url' => 'http://youtrack/issues/:id' - } - ) - end + youtrack_service end factory :jira_project, parent: :project do has_external_issue_tracker true + jira_service end diff --git a/spec/factories/services.rb b/spec/factories/services.rb index cd1d2c33373..daf842e3075 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -79,14 +79,12 @@ FactoryBot.define do trait :issue_tracker do properties( project_url: 'http://issue-tracker.example.com', - issues_url: 'http://issue-tracker.example.com', + issues_url: 'http://issue-tracker.example.com/issues/:id', new_issue_url: 'http://issue-tracker.example.com' ) end - factory :jira_cloud_service, class: JiraService do - project - active true + trait :jira_cloud_service do properties( url: 'https://mysite.atlassian.net', username: 'jira_user', diff --git a/spec/factories/services_data.rb b/spec/factories/services_data.rb index 387e130a743..5a3639895b6 100644 --- a/spec/factories/services_data.rb +++ b/spec/factories/services_data.rb @@ -1,18 +1,12 @@ # frozen_string_literal: true +# these factories should never be called directly, they are used when creating services FactoryBot.define do factory :jira_tracker_data do service - url 'http://jira.example.com' - api_url 'http://api-jira.example.com' - username 'jira_username' - password 'jira_password' end factory :issue_tracker_data do service - project_url 'http://issuetracker.example.com' - issues_url 'http://issues.example.com' - new_issue_url 'http://new-issue.example.com' end end diff --git a/spec/features/markdown/mermaid_spec.rb b/spec/features/markdown/mermaid_spec.rb index 7008b361394..e3bcaca737e 100644 --- a/spec/features/markdown/mermaid_spec.rb +++ b/spec/features/markdown/mermaid_spec.rb @@ -21,4 +21,22 @@ describe 'Mermaid rendering', :js do expect(page).to have_selector('svg text', text: label) end end + + it 'renders linebreaks in Mermaid diagrams' do + description = <<~MERMAID + ```mermaid + graph TD; + A(Line 1<br>Line 2)-->B(Line 1<br/>Line 2); + C(Line 1<br />Line 2)-->D(Line 1<br />Line 2); + ``` + MERMAID + + project = create(:project, :public) + issue = create(:issue, project: project, description: description) + + visit project_issue_path(project, issue) + + expected = '<text><tspan xml:space="preserve" dy="1em" x="1">Line 1</tspan><tspan xml:space="preserve" dy="1em" x="1">Line 2</tspan></text>' + expect(page.html.scan(expected).count).to be(4) + end end diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb index 919859c145a..7c44680e9f7 100644 --- a/spec/features/triggers_spec.rb +++ b/spec/features/triggers_spec.rb @@ -66,7 +66,7 @@ describe 'Triggers', :js do it 'edit "legacy" trigger and save' do # Create new trigger without owner association, i.e. Legacy trigger - create(:ci_trigger, owner: nil, project: @project) + create(:ci_trigger, owner: user, project: @project).update_attribute(:owner, nil) visit project_settings_ci_cd_path(@project) # See if the trigger can be edited and description is blank @@ -127,17 +127,19 @@ describe 'Triggers', :js do end describe 'show triggers workflow' do + before do + stub_feature_flags(use_legacy_pipeline_triggers: false) + end + it 'contains trigger description placeholder' do expect(page.find('#trigger_description')['placeholder']).to eq 'Trigger description' end - it 'show "legacy" badge for legacy trigger' do - create(:ci_trigger, owner: nil, project: @project) + it 'show "invalid" badge for legacy trigger' do + create(:ci_trigger, owner: user, project: @project).update_attribute(:owner, nil) visit project_settings_ci_cd_path(@project) - # See if trigger without owner (i.e. legacy) shows "legacy" badge and is editable - expect(page.find('.triggers-list')).to have_content 'legacy' - expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]') + expect(page.find('.triggers-list')).to have_content 'invalid' end it 'show "invalid" badge for trigger with owner having insufficient permissions' do @@ -149,6 +151,19 @@ describe 'Triggers', :js do expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]') end + it 'do not show "Edit" or full token for legacy trigger' do + create(:ci_trigger, owner: user, project: @project, description: trigger_title) + .update_attribute(:owner, nil) + visit project_settings_ci_cd_path(@project) + + # See if trigger not owned shows only first few token chars and doesn't have copy-to-clipboard button + expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3]) + expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard') + + # See if trigger is non-editable + expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]') + end + it 'do not show "Edit" or full token for not owned trigger' do # Create trigger with user different from current_user create(:ci_trigger, owner: user2, project: @project, description: trigger_title) @@ -175,5 +190,56 @@ describe 'Triggers', :js do expect(page.find('.triggers-list .trigger-owner')).to have_content user.name expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]') end + + context 'when :use_legacy_pipeline_triggers feature flag is enabled' do + before do + stub_feature_flags(use_legacy_pipeline_triggers: true) + end + + it 'show "legacy" badge for legacy trigger' do + create(:ci_trigger, owner: nil, project: @project) + visit project_settings_ci_cd_path(@project) + + # See if trigger without owner (i.e. legacy) shows "legacy" badge and is editable + expect(page.find('.triggers-list')).to have_content 'legacy' + expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]') + end + + it 'show "invalid" badge for trigger with owner having insufficient permissions' do + create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title) + visit project_settings_ci_cd_path(@project) + + # See if trigger without owner (i.e. legacy) shows "legacy" badge and is non-editable + expect(page.find('.triggers-list')).to have_content 'invalid' + expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]') + end + + it 'do not show "Edit" or full token for not owned trigger' do + # Create trigger with user different from current_user + create(:ci_trigger, owner: user2, project: @project, description: trigger_title) + visit project_settings_ci_cd_path(@project) + + # See if trigger not owned by current_user shows only first few token chars and doesn't have copy-to-clipboard button + expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3]) + expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard') + + # See if trigger owner name doesn't match with current_user and trigger is non-editable + expect(page.find('.triggers-list .trigger-owner')).not_to have_content user.name + expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]') + end + + it 'show "Edit" and full token for owned trigger' do + create(:ci_trigger, owner: user, project: @project, description: trigger_title) + visit project_settings_ci_cd_path(@project) + + # See if trigger shows full token and has copy-to-clipboard button + expect(page.find('.triggers-list')).to have_content @project.triggers.first.token + expect(page.find('.triggers-list')).to have_selector('button.btn-clipboard') + + # See if trigger owner name matches with current_user and is editable + expect(page.find('.triggers-list .trigger-owner')).to have_content user.name + expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]') + end + end end end diff --git a/spec/frontend/create_merge_request_dropdown_spec.js b/spec/frontend/create_merge_request_dropdown_spec.js index 6e41fdabdce..dcc6fa96d18 100644 --- a/spec/frontend/create_merge_request_dropdown_spec.js +++ b/spec/frontend/create_merge_request_dropdown_spec.js @@ -1,6 +1,7 @@ import axios from '~/lib/utils/axios_utils'; import MockAdapter from 'axios-mock-adapter'; import CreateMergeRequestDropdown from '~/create_merge_request_dropdown'; +import confidentialState from '~/confidential_merge_request/state'; import { TEST_HOST } from './helpers/test_constants'; describe('CreateMergeRequestDropdown', () => { @@ -66,4 +67,37 @@ describe('CreateMergeRequestDropdown', () => { ); }); }); + + describe('enable', () => { + beforeEach(() => { + dropdown.createMergeRequestButton.classList.add('disabled'); + }); + + afterEach(() => { + confidentialState.selectedProject = {}; + }); + + it('enables button when not confidential issue', () => { + dropdown.enable(); + + expect(dropdown.createMergeRequestButton.classList).not.toContain('disabled'); + }); + + it('enables when can create confidential issue', () => { + document.querySelector('.js-create-mr').setAttribute('data-is-confidential', 'true'); + confidentialState.selectedProject = { name: 'test' }; + + dropdown.enable(); + + expect(dropdown.createMergeRequestButton.classList).not.toContain('disabled'); + }); + + it('does not enable when can not create confidential issue', () => { + document.querySelector('.js-create-mr').setAttribute('data-is-confidential', 'true'); + + dropdown.enable(); + + expect(dropdown.createMergeRequestButton.classList).toContain('disabled'); + }); + }); }); diff --git a/spec/frontend/repository/components/breadcrumbs_spec.js b/spec/frontend/repository/components/breadcrumbs_spec.js index 068fa317a87..707eae34793 100644 --- a/spec/frontend/repository/components/breadcrumbs_spec.js +++ b/spec/frontend/repository/components/breadcrumbs_spec.js @@ -1,12 +1,14 @@ import { shallowMount, RouterLinkStub } from '@vue/test-utils'; +import { GlDropdown } from '@gitlab/ui'; import Breadcrumbs from '~/repository/components/breadcrumbs.vue'; let vm; -function factory(currentPath) { +function factory(currentPath, extraProps = {}) { vm = shallowMount(Breadcrumbs, { propsData: { currentPath, + ...extraProps, }, stubs: { RouterLink: RouterLinkStub, @@ -41,4 +43,20 @@ describe('Repository breadcrumbs component', () => { .attributes('aria-current'), ).toEqual('page'); }); + + it('does not render add to tree dropdown when permissions are false', () => { + factory('/', { canCollaborate: false }); + + vm.setData({ userPermissions: { forkProject: false, createMergeRequestIn: false } }); + + expect(vm.find(GlDropdown).exists()).toBe(false); + }); + + it('renders add to tree dropdown when permissions are true', () => { + factory('/', { canCollaborate: true }); + + vm.setData({ userPermissions: { forkProject: true, createMergeRequestIn: true } }); + + expect(vm.find(GlDropdown).exists()).toBe(true); + }); }); diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb index 919825a6102..e87440895e0 100644 --- a/spec/lib/banzai/filter/redactor_filter_spec.rb +++ b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Banzai::Filter::RedactorFilter do +describe Banzai::Filter::ReferenceRedactorFilter do include ActionView::Helpers::UrlHelper include FilterSpecHelper diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index 7b855251a74..e3e6e22568c 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -22,8 +22,8 @@ describe Banzai::ObjectRenderer do expect(object.user_visible_reference_count).to eq 0 end - it 'calls Banzai::Redactor to perform redaction' do - expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original + it 'calls Banzai::ReferenceRedactor to perform redaction' do + expect_any_instance_of(Banzai::ReferenceRedactor).to receive(:redact).and_call_original renderer.render([object], :note) end @@ -82,8 +82,8 @@ describe Banzai::ObjectRenderer do expect(cacheless_thing.redacted_title_html).to eq("Merge branch 'branch-merged' into 'master'") end - it 'calls Banzai::Redactor to perform redaction' do - expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original + it 'calls Banzai::ReferenceRedactor to perform redaction' do + expect_any_instance_of(Banzai::ReferenceRedactor).to receive(:redact).and_call_original renderer.render([cacheless_thing], :title) end diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb index 7119c826bca..469692f7b5a 100644 --- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb @@ -32,7 +32,7 @@ describe Banzai::Pipeline::GfmPipeline do result = described_class.call(markdown, project: project)[:output] link = result.css('a').first - expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12' + expect(link['href']).to eq 'http://issue-tracker.example.com/issues/12' end it 'parses cross-project references to regular issues' do @@ -61,7 +61,7 @@ describe Banzai::Pipeline::GfmPipeline do result = described_class.call(markdown, project: project)[:output] link = result.css('a').first - expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12' + expect(link['href']).to eq 'http://issue-tracker.example.com/issues/12' end it 'allows to use long external reference syntax for Redmine' do @@ -70,7 +70,7 @@ describe Banzai::Pipeline::GfmPipeline do result = described_class.call(markdown, project: project)[:output] link = result.css('a').first - expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12' + expect(link['href']).to eq 'http://issue-tracker.example.com/issues/12' end it 'parses cross-project references to regular issues' do diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/reference_redactor_spec.rb index 718649e0e10..a3b47c4d826 100644 --- a/spec/lib/banzai/redactor_spec.rb +++ b/spec/lib/banzai/reference_redactor_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Banzai::Redactor do +describe Banzai::ReferenceRedactor do let(:user) { create(:user) } let(:project) { build(:project) } let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) } diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index ff002acbd35..5293732ead1 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -96,6 +96,75 @@ module Gitlab end end + context 'with passthrough' do + it 'removes non heading ids' do + input = <<~ADOC + ++++ + <h2 id="foo">Title</h2> + ++++ + ADOC + + output = <<~HTML + <h2>Title</h2> + HTML + + expect(render(input, context)).to include(output.strip) + end + end + + context 'with section anchors' do + it 'preserves ids and links' do + input = <<~ADOC + = Title + + == First section + + This is the first section. + + == Second section + + This is the second section. + + == Thunder ⚡ ! + + This is the third section. + ADOC + + output = <<~HTML + <h1>Title</h1> + <div> + <h2 id="user-content-first-section"> + <a class="anchor" href="#user-content-first-section"></a>First section</h2> + <div> + <div> + <p>This is the first section.</p> + </div> + </div> + </div> + <div> + <h2 id="user-content-second-section"> + <a class="anchor" href="#user-content-second-section"></a>Second section</h2> + <div> + <div> + <p>This is the second section.</p> + </div> + </div> + </div> + <div> + <h2 id="user-content-thunder"> + <a class="anchor" href="#user-content-thunder"></a>Thunder ⚡ !</h2> + <div> + <div> + <p>This is the third section.</p> + </div> + </div> + </div> + HTML + + expect(render(input, context)).to include(output.strip) + end + end + context 'with checklist' do it 'preserves classes' do input = <<~ADOC 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 7d750877d09..b3e58c3dfdb 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb @@ -10,7 +10,11 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do let(:command) do Gitlab::Ci::Pipeline::Chain::Command.new( - project: project, current_user: user, origin_ref: origin_ref, merge_request: merge_request) + project: project, + current_user: user, + origin_ref: origin_ref, + merge_request: merge_request, + trigger_request: trigger_request) end let(:step) { described_class.new(pipeline, command) } @@ -18,6 +22,7 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do let(:ref) { 'master' } let(:origin_ref) { ref } let(:merge_request) { nil } + let(:trigger_request) { nil } shared_context 'detached merge request pipeline' do let(:merge_request) do @@ -69,6 +74,43 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do end end + context 'when pipeline triggered by legacy trigger' do + let(:user) { nil } + let(:trigger_request) do + build_stubbed(:ci_trigger_request, trigger: build_stubbed(:ci_trigger, owner: nil)) + end + + context 'when :use_legacy_pipeline_triggers feature flag is enabled' do + before do + stub_feature_flags(use_legacy_pipeline_triggers: true) + step.perform! + end + + it 'allows legacy triggers to create a pipeline' do + expect(pipeline).to be_valid + end + + it 'does not break the chain' do + expect(step.break?).to eq false + end + end + + context 'when :use_legacy_pipeline_triggers feature flag is disabled' do + before do + stub_feature_flags(use_legacy_pipeline_triggers: false) + step.perform! + end + + it 'prevents legacy triggers from creating a pipeline' do + expect(pipeline.errors.to_a).to include /Trigger token is invalid/ + end + + it 'breaks the pipeline builder chain' do + expect(step.break?).to eq true + end + end + end + describe '#allowed_to_create?' do subject { step.allowed_to_create? } diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb index 30ea3f3e28e..6ce4b321397 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb @@ -107,42 +107,6 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do expect(token.build.evaluate) .to eq Gitlab::UntrustedRegexp.new('some numeric \$ pattern') end - - context 'with the ci_variables_complex_expressions feature flag disabled' do - before do - stub_feature_flags(ci_variables_complex_expressions: false) - end - - it 'is a greedy scanner for regexp boundaries' do - scanner = StringScanner.new('/some .* / pattern/') - - token = described_class.scan(scanner) - - expect(token).not_to be_nil - expect(token.build.evaluate) - .to eq Gitlab::UntrustedRegexp.new('some .* / pattern') - end - - it 'does not recognize the \ escape character for /' do - scanner = StringScanner.new('/some .* \/ pattern/') - - token = described_class.scan(scanner) - - expect(token).not_to be_nil - expect(token.build.evaluate) - .to eq Gitlab::UntrustedRegexp.new('some .* \/ pattern') - end - - it 'does not recognize the \ escape character for $' do - scanner = StringScanner.new('/some numeric \$ pattern/') - - token = described_class.scan(scanner) - - expect(token).not_to be_nil - expect(token.build.evaluate) - .to eq Gitlab::UntrustedRegexp.new('some numeric \$ pattern') - end - end end describe '#evaluate' do diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb index d8db9c262a1..7c98e729b0b 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb @@ -80,34 +80,6 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do it { is_expected.to eq(tokens) } end end - - context 'with the ci_variables_complex_expressions feature flag turned off' do - before do - stub_feature_flags(ci_variables_complex_expressions: false) - end - - it 'incorrectly tokenizes conjunctive match statements as one match statement' do - tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/').tokens - - expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ && $EMPTY_VARIABLE =~ /nope/']) - end - - it 'incorrectly tokenizes disjunctive match statements as one statement' do - tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/').tokens - - expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ || $EMPTY_VARIABLE =~ /nope/']) - end - - it 'raises an error about && operators' do - expect { described_class.new('$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE').tokens } - .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!') - end - - it 'raises an error about || operators' do - expect { described_class.new('$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE').tokens } - .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!') - end - end end describe '#lexemes' do diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb index a2c2e3653d5..b259ef711aa 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb @@ -1,6 +1,4 @@ -# TODO switch this back after the "ci_variables_complex_expressions" feature flag is removed -# require 'fast_spec_helper' -require 'spec_helper' +require 'fast_spec_helper' require 'rspec-parameterized' describe Gitlab::Ci::Pipeline::Expression::Statement do @@ -118,54 +116,6 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do expect(subject.evaluate).to eq(value) end end - - context 'with the ci_variables_complex_expressions feature flag disabled' do - before do - stub_feature_flags(ci_variables_complex_expressions: false) - end - - where(:expression, :value) do - '$PRESENT_VARIABLE == "my variable"' | true - '"my variable" == $PRESENT_VARIABLE' | true - '$PRESENT_VARIABLE == null' | false - '$EMPTY_VARIABLE == null' | false - '"" == $EMPTY_VARIABLE' | true - '$EMPTY_VARIABLE' | '' - '$UNDEFINED_VARIABLE == null' | true - 'null == $UNDEFINED_VARIABLE' | true - '$PRESENT_VARIABLE' | 'my variable' - '$UNDEFINED_VARIABLE' | nil - "$PRESENT_VARIABLE =~ /var.*e$/" | true - "$PRESENT_VARIABLE =~ /^var.*/" | false - "$EMPTY_VARIABLE =~ /var.*/" | false - "$UNDEFINED_VARIABLE =~ /var.*/" | false - "$PRESENT_VARIABLE =~ /VAR.*/i" | true - '$PATH_VARIABLE =~ /path/variable/' | true - '$PATH_VARIABLE =~ /path\/variable/' | true - '$FULL_PATH_VARIABLE =~ /^/a/full/path/variable/value$/' | true - '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/' | true - '$PRESENT_VARIABLE != "my variable"' | false - '"my variable" != $PRESENT_VARIABLE' | false - '$PRESENT_VARIABLE != null' | true - '$EMPTY_VARIABLE != null' | true - '"" != $EMPTY_VARIABLE' | false - '$UNDEFINED_VARIABLE != null' | false - 'null != $UNDEFINED_VARIABLE' | false - "$PRESENT_VARIABLE !~ /var.*e$/" | false - "$PRESENT_VARIABLE !~ /^var.*/" | true - "$EMPTY_VARIABLE !~ /var.*/" | true - "$UNDEFINED_VARIABLE !~ /var.*/" | true - "$PRESENT_VARIABLE !~ /VAR.*/i" | false - end - - with_them do - let(:text) { expression } - - it "evaluates to `#{params[:value].inspect}`" do - expect(subject.evaluate).to eq value - end - end - end end describe '#truthful?' do diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index 7991e2f48b5..46ea0d7554b 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -1,32 +1,30 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Ci::Pipeline::Seed::Build do let(:project) { create(:project, :repository) } let(:pipeline) { create(:ci_empty_pipeline, project: project) } + let(:attributes) { { name: 'rspec', ref: 'master' } } - let(:attributes) do - { name: 'rspec', ref: 'master' } - end - - subject do - described_class.new(pipeline, attributes) - end + let(:seed_build) { described_class.new(pipeline, attributes) } describe '#attributes' do - it 'returns hash attributes of a build' do - expect(subject.attributes).to be_a Hash - expect(subject.attributes) - .to include(:name, :project, :ref) - end + subject { seed_build.attributes } + + it { is_expected.to be_a(Hash) } + it { is_expected.to include(:name, :project, :ref) } end describe '#bridge?' do + subject { seed_build.bridge? } + context 'when job is a bridge' do let(:attributes) do { name: 'rspec', ref: 'master', options: { trigger: 'my/project' } } end - it { is_expected.to be_bridge } + it { is_expected.to be_truthy } end context 'when trigger definition is empty' do @@ -34,20 +32,20 @@ describe Gitlab::Ci::Pipeline::Seed::Build do { name: 'rspec', ref: 'master', options: { trigger: '' } } end - it { is_expected.not_to be_bridge } + it { is_expected.to be_falsey } end context 'when job is not a bridge' do - it { is_expected.not_to be_bridge } + it { is_expected.to be_falsey } end end describe '#to_resource' do + subject { seed_build.to_resource } + context 'when job is not a bridge' do - it 'returns a valid build resource' do - expect(subject.to_resource).to be_a(::Ci::Build) - expect(subject.to_resource).to be_valid - end + it { is_expected.to be_a(::Ci::Build) } + it { is_expected.to be_valid } end context 'when job is a bridge' do @@ -55,71 +53,117 @@ describe Gitlab::Ci::Pipeline::Seed::Build do { name: 'rspec', ref: 'master', options: { trigger: 'my/project' } } end - it 'returns a valid bridge resource' do - expect(subject.to_resource).to be_a(::Ci::Bridge) - expect(subject.to_resource).to be_valid - end + it { is_expected.to be_a(::Ci::Bridge) } + it { is_expected.to be_valid } end it 'memoizes a resource object' do - build = subject.to_resource - - expect(build.object_id).to eq subject.to_resource.object_id + expect(subject.object_id).to eq seed_build.to_resource.object_id end it 'can not be persisted without explicit assignment' do - build = subject.to_resource - pipeline.save! - expect(build).not_to be_persisted + expect(subject).not_to be_persisted end end - describe 'applying only/except policies' do + describe 'applying job inclusion policies' do + subject { seed_build } + context 'when no branch policy is specified' do - let(:attributes) { { name: 'rspec' } } + let(:attributes) do + { name: 'rspec' } + end it { is_expected.to be_included } end context 'when branch policy does not match' do context 'when using only' do - let(:attributes) { { name: 'rspec', only: { refs: ['deploy'] } } } + let(:attributes) do + { name: 'rspec', only: { refs: ['deploy'] } } + end it { is_expected.not_to be_included } end context 'when using except' do - let(:attributes) { { name: 'rspec', except: { refs: ['deploy'] } } } + let(:attributes) do + { name: 'rspec', except: { refs: ['deploy'] } } + end it { is_expected.to be_included } end + + context 'with both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { refs: %w[deploy] }, + except: { refs: %w[deploy] } + } + end + + it { is_expected.not_to be_included } + end end context 'when branch regexp policy does not match' do context 'when using only' do - let(:attributes) { { name: 'rspec', only: { refs: ['/^deploy$/'] } } } + let(:attributes) do + { name: 'rspec', only: { refs: %w[/^deploy$/] } } + end it { is_expected.not_to be_included } end context 'when using except' do - let(:attributes) { { name: 'rspec', except: { refs: ['/^deploy$/'] } } } + let(:attributes) do + { name: 'rspec', except: { refs: %w[/^deploy$/] } } + end it { is_expected.to be_included } end + + context 'with both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { refs: %w[/^deploy$/] }, + except: { refs: %w[/^deploy$/] } + } + end + + it { is_expected.not_to be_included } + end end context 'when branch policy matches' do context 'when using only' do - let(:attributes) { { name: 'rspec', only: { refs: %w[deploy master] } } } + let(:attributes) do + { name: 'rspec', only: { refs: %w[deploy master] } } + end it { is_expected.to be_included } end context 'when using except' do - let(:attributes) { { name: 'rspec', except: { refs: %w[deploy master] } } } + let(:attributes) do + { name: 'rspec', except: { refs: %w[deploy master] } } + end + + it { is_expected.not_to be_included } + end + + context 'when using both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { refs: %w[deploy master] }, + except: { refs: %w[deploy master] } + } + end it { is_expected.not_to be_included } end @@ -127,13 +171,29 @@ describe Gitlab::Ci::Pipeline::Seed::Build do context 'when keyword policy matches' do context 'when using only' do - let(:attributes) { { name: 'rspec', only: { refs: ['branches'] } } } + let(:attributes) do + { name: 'rspec', only: { refs: %w[branches] } } + end it { is_expected.to be_included } end context 'when using except' do - let(:attributes) { { name: 'rspec', except: { refs: ['branches'] } } } + let(:attributes) do + { name: 'rspec', except: { refs: %w[branches] } } + end + + it { is_expected.not_to be_included } + end + + context 'when using both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { refs: %w[branches] }, + except: { refs: %w[branches] } + } + end it { is_expected.not_to be_included } end @@ -141,50 +201,78 @@ describe Gitlab::Ci::Pipeline::Seed::Build do context 'when keyword policy does not match' do context 'when using only' do - let(:attributes) { { name: 'rspec', only: { refs: ['tags'] } } } + let(:attributes) do + { name: 'rspec', only: { refs: %w[tags] } } + end it { is_expected.not_to be_included } end context 'when using except' do - let(:attributes) { { name: 'rspec', except: { refs: ['tags'] } } } + let(:attributes) do + { name: 'rspec', except: { refs: %w[tags] } } + end it { is_expected.to be_included } end + + context 'when using both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { refs: %w[tags] }, + except: { refs: %w[tags] } + } + end + + it { is_expected.not_to be_included } + end end context 'with source-keyword policy' do using RSpec::Parameterized - let(:pipeline) { build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) } + let(:pipeline) do + build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) + end context 'matches' do where(:keyword, :source) do [ - %w(pushes push), - %w(web web), - %w(triggers trigger), - %w(schedules schedule), - %w(api api), - %w(external external) + %w[pushes push], + %w[web web], + %w[triggers trigger], + %w[schedules schedule], + %w[api api], + %w[external external] ] end with_them do context 'using an only policy' do - let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } } + let(:attributes) do + { name: 'rspec', only: { refs: [keyword] } } + end it { is_expected.to be_included } end context 'using an except policy' do - let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } } + let(:attributes) do + { name: 'rspec', except: { refs: [keyword] } } + end it { is_expected.not_to be_included } end context 'using both only and except policies' do - let(:attributes) { { name: 'rspec', only: { refs: [keyword] }, except: { refs: [keyword] } } } + let(:attributes) do + { + name: 'rspec', + only: { refs: [keyword] }, + except: { refs: [keyword] } + } + end it { is_expected.not_to be_included } end @@ -193,29 +281,39 @@ describe Gitlab::Ci::Pipeline::Seed::Build do context 'non-matches' do where(:keyword, :source) do - %w(web trigger schedule api external).map { |source| ['pushes', source] } + - %w(push trigger schedule api external).map { |source| ['web', source] } + - %w(push web schedule api external).map { |source| ['triggers', source] } + - %w(push web trigger api external).map { |source| ['schedules', source] } + - %w(push web trigger schedule external).map { |source| ['api', source] } + - %w(push web trigger schedule api).map { |source| ['external', source] } + %w[web trigger schedule api external].map { |source| ['pushes', source] } + + %w[push trigger schedule api external].map { |source| ['web', source] } + + %w[push web schedule api external].map { |source| ['triggers', source] } + + %w[push web trigger api external].map { |source| ['schedules', source] } + + %w[push web trigger schedule external].map { |source| ['api', source] } + + %w[push web trigger schedule api].map { |source| ['external', source] } end with_them do context 'using an only policy' do - let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } } + let(:attributes) do + { name: 'rspec', only: { refs: [keyword] } } + end it { is_expected.not_to be_included } end context 'using an except policy' do - let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } } + let(:attributes) do + { name: 'rspec', except: { refs: [keyword] } } + end it { is_expected.to be_included } end context 'using both only and except policies' do - let(:attributes) { { name: 'rspec', only: { refs: [keyword] }, except: { refs: [keyword] } } } + let(:attributes) do + { + name: 'rspec', + only: { refs: [keyword] }, + except: { refs: [keyword] } + } + end it { is_expected.not_to be_included } end @@ -239,12 +337,24 @@ describe Gitlab::Ci::Pipeline::Seed::Build do it { is_expected.not_to be_included } end + + context 'when using both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { refs: ["branches@#{pipeline.project_full_path}"] }, + except: { refs: ["branches@#{pipeline.project_full_path}"] } + } + end + + it { is_expected.not_to be_included } + end end context 'when repository path does not matches' do context 'when using only' do let(:attributes) do - { name: 'rspec', only: { refs: ['branches@fork'] } } + { name: 'rspec', only: { refs: %w[branches@fork] } } end it { is_expected.not_to be_included } @@ -252,11 +362,23 @@ describe Gitlab::Ci::Pipeline::Seed::Build do context 'when using except' do let(:attributes) do - { name: 'rspec', except: { refs: ['branches@fork'] } } + { name: 'rspec', except: { refs: %w[branches@fork] } } end it { is_expected.to be_included } end + + context 'when using both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { refs: %w[branches@fork] }, + except: { refs: %w[branches@fork] } + } + end + + it { is_expected.not_to be_included } + end end end end diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb index eed233f1f3e..b8debee3b58 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -171,17 +171,6 @@ describe Gitlab::GitalyClient do end end end - - context 'when catfile-cache feature is disabled' do - before do - stub_feature_flags({ 'gitaly_catfile-cache': false }) - end - - it 'does not set the gitaly-session-id in the metadata' do - results = described_class.request_kwargs('default', nil) - expect(results[:metadata]).not_to include('gitaly-session-id') - end - end end describe 'enforce_gitaly_request_limits?' do diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 8be074f4b9b..c0b97486eeb 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -6630,8 +6630,16 @@ "id": 123, "token": "cdbfasdf44a5958c83654733449e585", "project_id": 5, + "owner_id": 1, "created_at": "2017-01-16T15:25:28.637Z", "updated_at": "2017-01-16T15:25:28.637Z" + }, + { + "id": 456, + "token": "33a66349b5ad01fc00174af87804e40", + "project_id": 5, + "created_at": "2017-01-16T15:25:29.637Z", + "updated_at": "2017-01-16T15:25:29.637Z" } ], "deploy_keys": [], diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index ca46006ea58..e6ce3f1bcea 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -32,6 +32,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end context 'JSON' do + before do + stub_feature_flags(use_legacy_pipeline_triggers: false) + end + it 'restores models based on JSON' do expect(@restored_project_json).to be_truthy end @@ -198,8 +202,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end context 'tokens are regenerated' do - it 'has a new CI trigger token' do - expect(Ci::Trigger.where(token: 'cdbfasdf44a5958c83654733449e585')).to be_empty + it 'has new CI trigger tokens' do + expect(Ci::Trigger.where(token: %w[cdbfasdf44a5958c83654733449e585 33a66349b5ad01fc00174af87804e40])) + .to be_empty end it 'has a new CI build token' do @@ -212,7 +217,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect(@project.merge_requests.size).to eq(9) end - it 'has the correct number of triggers' do + it 'only restores valid triggers' do expect(@project.triggers.size).to eq(1) end diff --git a/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb new file mode 100644 index 00000000000..38b4c22e186 --- /dev/null +++ b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shared_state do + context 'when redis_key is not defined' do + subject do + Class.new.extend(described_class) + end + + describe '.increment' do + it 'raises a NotImplementedError exception' do + expect { subject.increment}.to raise_error(NotImplementedError) + end + end + + describe '.total_count' do + it 'raises a NotImplementedError exception' do + expect { subject.total_count}.to raise_error(NotImplementedError) + end + end + end + + context 'when redis_key is defined' do + subject do + counter_module = described_class + + Class.new do + extend counter_module + + def self.redis_counter_key + 'foo_redis_key' + end + end + end + + describe '.increment' do + it 'increments the web ide commits counter by 1' do + expect do + subject.increment + end.to change { subject.total_count }.from(0).to(1) + end + end + + describe '.total_count' do + it 'returns the total amount of web ide commits' do + subject.increment + subject.increment + + expect(subject.total_count).to eq(2) + end + end + end +end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 67d49a30825..90a534de202 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::UsageData do before do create(:jira_service, project: projects[0]) create(:jira_service, project: projects[1]) - create(:jira_cloud_service, project: projects[2]) + create(:jira_service, :jira_cloud_service, project: projects[2]) create(:prometheus_service, project: projects[1]) create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true) create(:service, project: projects[1], type: 'SlackService', active: true) diff --git a/spec/lib/gitlab/web_ide_commits_counter_spec.rb b/spec/lib/gitlab/web_ide_commits_counter_spec.rb deleted file mode 100644 index c51889a1c63..00000000000 --- a/spec/lib/gitlab/web_ide_commits_counter_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::WebIdeCommitsCounter, :clean_gitlab_redis_shared_state do - describe '.increment' do - it 'increments the web ide commits counter by 1' do - expect do - described_class.increment - end.to change { described_class.total_count }.from(0).to(1) - end - end - - describe '.total_count' do - it 'returns the total amount of web ide commits' do - expect(described_class.total_count).to eq(0) - end - end -end diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index fde8375f2a5..5b5d6f51b33 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -54,19 +54,31 @@ describe Ci::Trigger do end describe '#can_access_project?' do + let(:owner) { create(:user) } let(:trigger) { create(:ci_trigger, owner: owner, project: project) } context 'when owner is blank' do - let(:owner) { nil } + before do + stub_feature_flags(use_legacy_pipeline_triggers: false) + trigger.update_attribute(:owner, nil) + end subject { trigger.can_access_project? } - it { is_expected.to eq(true) } + it { is_expected.to eq(false) } + + context 'when :use_legacy_pipeline_triggers feature flag is enabled' do + before do + stub_feature_flags(use_legacy_pipeline_triggers: true) + end + + subject { trigger.can_access_project? } + + it { is_expected.to eq(true) } + end end context 'when owner is set' do - let(:owner) { create(:user) } - subject { trigger.can_access_project? } context 'and is member of the project' do diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index 4f0cd0efe9c..4abe45a2152 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -18,7 +18,7 @@ describe Clusters::Applications::Runner do subject { gitlab_runner.can_uninstall? } - it { is_expected.to be_falsey } + it { is_expected.to be_truthy } end describe '#install_command' do @@ -156,4 +156,35 @@ describe Clusters::Applications::Runner do end end end + + describe '#prepare_uninstall' do + it 'pauses associated runner' do + active_runner = create(:ci_runner, contacted_at: 1.second.ago) + + expect(active_runner.status).to eq(:online) + + application_runner = create(:clusters_applications_runner, :scheduled, runner: active_runner) + application_runner.prepare_uninstall + + expect(active_runner.status).to eq(:paused) + end + end + + describe '#make_uninstalling!' do + subject { create(:clusters_applications_runner, :scheduled, runner: ci_runner) } + + it 'calls prepare_uninstall' do + expect_any_instance_of(described_class).to receive(:prepare_uninstall).and_call_original + + subject.make_uninstalling! + end + end + + describe '#post_uninstall' do + it 'destroys its runner' do + application_runner = create(:clusters_applications_runner, :scheduled, runner: ci_runner) + + expect { application_runner.post_uninstall }.to change { Ci::Runner.count }.by(-1) + end + end end diff --git a/spec/models/concerns/stepable_spec.rb b/spec/models/concerns/stepable_spec.rb new file mode 100644 index 00000000000..5685de6a9bf --- /dev/null +++ b/spec/models/concerns/stepable_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Stepable do + let(:described_class) do + Class.new do + include Stepable + + steps :method1, :method2, :method3 + + def execute + execute_steps + end + + private + + def method1 + { status: :success } + end + + def method2 + return { status: :error } unless @pass + + { status: :success, variable1: 'var1' } + end + + def method3 + { status: :success, variable2: 'var2' } + end + end + end + + let(:prepended_module) do + Module.new do + extend ActiveSupport::Concern + + prepended do + steps :appended_method1 + end + + private + + def appended_method1 + { status: :success } + end + end + end + + before do + described_class.prepend(prepended_module) + end + + it 'stops after the first error' do + expect(subject).not_to receive(:method3) + expect(subject).not_to receive(:appended_method1) + + expect(subject.execute).to eq( + status: :error, + failed_step: :method2 + ) + end + + context 'when all methods return success' do + before do + subject.instance_variable_set(:@pass, true) + end + + it 'calls all methods in order' do + expect(subject).to receive(:method1).and_call_original.ordered + expect(subject).to receive(:method2).and_call_original.ordered + expect(subject).to receive(:method3).and_call_original.ordered + expect(subject).to receive(:appended_method1).and_call_original.ordered + + subject.execute + end + + it 'merges variables returned by all steps' do + expect(subject.execute).to eq( + status: :success, + variable1: 'var1', + variable2: 'var2' + ) + end + end + + context 'with multiple stepable classes' do + let(:other_class) do + Class.new do + include Stepable + + steps :other_method1, :other_method2 + + private + + def other_method1 + { status: :success } + end + + def other_method2 + { status: :success } + end + end + end + + it 'does not leak steps' do + expect(other_class.new.steps).to contain_exactly(:other_method1, :other_method2) + expect(subject.steps).to contain_exactly(:method1, :method2, :method3, :appended_method1) + end + end +end diff --git a/spec/policies/ci/trigger_policy_spec.rb b/spec/policies/ci/trigger_policy_spec.rb index d8a63066265..e9a85890082 100644 --- a/spec/policies/ci/trigger_policy_spec.rb +++ b/spec/policies/ci/trigger_policy_spec.rb @@ -3,52 +3,24 @@ require 'spec_helper' describe Ci::TriggerPolicy do let(:user) { create(:user) } let(:project) { create(:project) } - let(:trigger) { create(:ci_trigger, project: project, owner: owner) } + let(:trigger) { create(:ci_trigger, project: project, owner: create(:user)) } - let(:policies) do - described_class.new(user, trigger) - end - - shared_examples 'allows to admin and manage trigger' do - it 'does include ability to admin trigger' do - expect(policies).to be_allowed :admin_trigger - end - - it 'does include ability to manage trigger' do - expect(policies).to be_allowed :manage_trigger - end - end - - shared_examples 'allows to manage trigger' do - it 'does not include ability to admin trigger' do - expect(policies).not_to be_allowed :admin_trigger - end - - it 'does include ability to manage trigger' do - expect(policies).to be_allowed :manage_trigger - end - end - - shared_examples 'disallows to admin and manage trigger' do - it 'does not include ability to admin trigger' do - expect(policies).not_to be_allowed :admin_trigger - end - - it 'does not include ability to manage trigger' do - expect(policies).not_to be_allowed :manage_trigger - end - end + subject { described_class.new(user, trigger) } describe '#rules' do context 'when owner is undefined' do - let(:owner) { nil } + before do + stub_feature_flags(use_legacy_pipeline_triggers: false) + trigger.update_attribute(:owner, nil) + end context 'when user is maintainer of the project' do before do project.add_maintainer(user) end - it_behaves_like 'allows to admin and manage trigger' + it { is_expected.to be_allowed(:manage_trigger) } + it { is_expected.not_to be_allowed(:admin_trigger) } end context 'when user is developer of the project' do @@ -56,35 +28,63 @@ describe Ci::TriggerPolicy do project.add_developer(user) end - it_behaves_like 'disallows to admin and manage trigger' + it { is_expected.not_to be_allowed(:manage_trigger) } + it { is_expected.not_to be_allowed(:admin_trigger) } end - context 'when user is not member of the project' do - it_behaves_like 'disallows to admin and manage trigger' + context 'when :use_legacy_pipeline_triggers feature flag is enabled' do + before do + stub_feature_flags(use_legacy_pipeline_triggers: true) + end + + context 'when user is maintainer of the project' do + before do + project.add_maintainer(user) + end + + it { is_expected.to be_allowed(:manage_trigger) } + it { is_expected.to be_allowed(:admin_trigger) } + end + + context 'when user is developer of the project' do + before do + project.add_developer(user) + end + + it { is_expected.not_to be_allowed(:manage_trigger) } + it { is_expected.not_to be_allowed(:admin_trigger) } + end + + context 'when user is not member of the project' do + it { is_expected.not_to be_allowed(:manage_trigger) } + it { is_expected.not_to be_allowed(:admin_trigger) } + end end end context 'when owner is an user' do - let(:owner) { user } + before do + trigger.update!(owner: user) + end context 'when user is maintainer of the project' do before do project.add_maintainer(user) end - it_behaves_like 'allows to admin and manage trigger' + it { is_expected.to be_allowed(:manage_trigger) } + it { is_expected.to be_allowed(:admin_trigger) } end end context 'when owner is another user' do - let(:owner) { create(:user) } - context 'when user is maintainer of the project' do before do project.add_maintainer(user) end - it_behaves_like 'allows to manage trigger' + it { is_expected.to be_allowed(:manage_trigger) } + it { is_expected.not_to be_allowed(:admin_trigger) } end context 'when user is developer of the project' do @@ -92,11 +92,13 @@ describe Ci::TriggerPolicy do project.add_developer(user) end - it_behaves_like 'disallows to admin and manage trigger' + it { is_expected.not_to be_allowed(:manage_trigger) } + it { is_expected.not_to be_allowed(:admin_trigger) } end context 'when user is not member of the project' do - it_behaves_like 'disallows to admin and manage trigger' + it { is_expected.not_to be_allowed(:manage_trigger) } + it { is_expected.not_to be_allowed(:admin_trigger) } end end end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 3df5d9412f8..204e378f7be 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -281,7 +281,7 @@ describe API::Commits do end it 'does not increment the usage counters using access token authentication' do - expect(::Gitlab::WebIdeCommitsCounter).not_to receive(:increment) + expect(::Gitlab::UsageDataCounters::WebIdeCommitsCounter).not_to receive(:increment) post api(url, user), params: valid_c_params end diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb index 3e0b478abb3..8abdcaa2e0e 100644 --- a/spec/requests/api/search_spec.rb +++ b/spec/requests/api/search_spec.rb @@ -89,7 +89,7 @@ describe API::Search do it 'returns empty array' do get api('/search', user), params: { scope: 'milestones', search: 'awesome' } - milestones = JSON.parse(response.body) + milestones = json_response expect(milestones).to be_empty end @@ -356,7 +356,7 @@ describe API::Search do it 'returns empty array' do get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' } - milestones = JSON.parse(response.body) + milestones = json_response expect(milestones).to be_empty end diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index 3f79e332b90..91cb8760a04 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -85,9 +85,7 @@ describe API::Services do include_context service # inject some properties into the service - before do - initialize_service(service) - end + let!(:initialized_service) { initialize_service(service) } it 'returns authentication error when unauthenticated' do get api("/projects/#{project.id}/services/#{dashed_service}") @@ -108,6 +106,15 @@ describe API::Services do expect(json_response['properties'].keys).to match_array(service_instance.api_field_names) end + it "returns empty hash if properties are empty" do + # deprecated services are not valid for update + initialized_service.update_attribute(:properties, {}) + get api("/projects/#{project.id}/services/#{dashed_service}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['properties'].keys).to be_empty + end + it "returns error when authenticated but not a project owner" do project.add_developer(user2) get api("/projects/#{project.id}/services/#{dashed_service}", user2) diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 0ad50e5347a..af2bee4563a 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -448,6 +448,7 @@ describe API::Users do it "returns 201 Created on success" do post api("/users", admin), params: attributes_for(:user, projects_limit: 3) + expect(response).to match_response_schema('public_api/v4/user/admin') expect(response).to have_gitlab_http_status(201) end @@ -643,6 +644,13 @@ describe API::Users do describe "PUT /users/:id" do let!(:admin_user) { create(:admin) } + it "returns 200 OK on success" do + put api("/users/#{user.id}", admin), params: { bio: 'new test bio' } + + expect(response).to match_response_schema('public_api/v4/user/admin') + expect(response).to have_gitlab_http_status(200) + end + it "updates user with new bio" do put api("/users/#{user.id}", admin), params: { bio: 'new test bio' } diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 1781759c54b..dc25e4d808e 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -1439,8 +1439,4 @@ describe 'Git LFS API and storage' do post(url, params: params, headers: headers) end - - def json_response - @json_response ||= JSON.parse(response.body) - end end diff --git a/spec/requests/lfs_locks_api_spec.rb b/spec/requests/lfs_locks_api_spec.rb index 5b7b3d2fdd6..11436e5cd0c 100644 --- a/spec/requests/lfs_locks_api_spec.rb +++ b/spec/requests/lfs_locks_api_spec.rb @@ -163,8 +163,4 @@ describe 'Git LFS File Locking API' do def do_get(url, params = nil, headers = nil) get(url, params: (params || {}), headers: (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE)) end - - def json_response - @json_response ||= JSON.parse(response.body) - end end diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb index 1bfb5602df2..cf84ec8fd4c 100644 --- a/spec/services/boards/issues/move_service_spec.rb +++ b/spec/services/boards/issues/move_service_spec.rb @@ -68,8 +68,8 @@ describe Boards::Issues::MoveService do project.add_developer(user) end - it 'returns false if list of issues is empty' do - expect(described_class.new(group, user, params).execute_multiple([])).to eq(false) + it 'returns the expected result if list of issues is empty' do + expect(described_class.new(group, user, params).execute_multiple([])).to eq({ count: 0, success: false, issues: [] }) end context 'moving multiple issues' do diff --git a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb index 9ab83d913f5..a948b442441 100644 --- a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb +++ b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb @@ -41,7 +41,7 @@ describe Clusters::Applications::CheckUninstallProgressService do end end - context 'when application is installing' do + context 'when application is uninstalling' do RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase } context 'when installation POD succeeded' do @@ -56,6 +56,12 @@ describe Clusters::Applications::CheckUninstallProgressService do service.execute end + it 'runs application post_uninstall' do + expect(application).to receive(:post_uninstall).and_call_original + + service.execute + end + it 'destroys the application' do expect(worker_class).not_to receive(:perform_in) diff --git a/spec/services/self_monitoring/project/create_service_spec.rb b/spec/services/self_monitoring/project/create_service_spec.rb new file mode 100644 index 00000000000..d11e27c6d52 --- /dev/null +++ b/spec/services/self_monitoring/project/create_service_spec.rb @@ -0,0 +1,201 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SelfMonitoring::Project::CreateService do + describe '#execute' do + let(:result) { subject.execute } + + let(:prometheus_settings) do + OpenStruct.new( + enable: true, + listen_address: 'localhost:9090' + ) + end + + before do + allow(Gitlab.config).to receive(:prometheus).and_return(prometheus_settings) + end + + context 'without admin users' do + it 'returns error' do + expect(subject).to receive(:log_error).and_call_original + expect(result).to eq( + status: :error, + message: 'No active admin user found', + failed_step: :validate_admins + ) + end + end + + context 'with admin users' do + let(:project) { result[:project] } + + let!(:user) { create(:user, :admin) } + + before do + allow(ApplicationSetting) + .to receive(:current) + .and_return( + ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: true) + ) + end + + shared_examples 'has prometheus service' do |listen_address| + it do + expect(result[:status]).to eq(:success) + + prometheus = project.prometheus_service + expect(prometheus).not_to eq(nil) + expect(prometheus.api_url).to eq(listen_address) + expect(prometheus.active).to eq(true) + expect(prometheus.manual_configuration).to eq(true) + end + end + + it_behaves_like 'has prometheus service', 'http://localhost:9090' + + it 'creates project with internal visibility' do + expect(result[:status]).to eq(:success) + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) + expect(project).to be_persisted + end + + it 'creates project with internal visibility even when internal visibility is restricted' do + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL]) + + expect(result[:status]).to eq(:success) + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) + expect(project).to be_persisted + end + + it 'creates project with correct name and description' do + expect(result[:status]).to eq(:success) + expect(project.name).to eq(described_class::DEFAULT_NAME) + expect(project.description).to eq(described_class::DEFAULT_DESCRIPTION) + end + + it 'adds all admins as maintainers' do + admin1 = create(:user, :admin) + admin2 = create(:user, :admin) + create(:user) + + expect(result[:status]).to eq(:success) + expect(project.owner).to eq(user) + expect(project.members.collect(&:user)).to contain_exactly(user, admin1, admin2) + expect(project.members.collect(&:access_level)).to contain_exactly( + Gitlab::Access::MAINTAINER, + Gitlab::Access::MAINTAINER, + Gitlab::Access::MAINTAINER + ) + end + + # This should pass when https://gitlab.com/gitlab-org/gitlab-ce/issues/44496 + # is complete and the prometheus listen address is added to the whitelist. + # context 'when local requests from hooks and services are not allowed' do + # before do + # allow(ApplicationSetting) + # .to receive(:current) + # .and_return( + # ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: false) + # ) + # end + + # it_behaves_like 'has prometheus service', 'http://localhost:9090' + # end + + context 'with non default prometheus address' do + before do + prometheus_settings.listen_address = 'https://localhost:9090' + end + + it_behaves_like 'has prometheus service', 'https://localhost:9090' + end + + context 'when prometheus setting is not present in gitlab.yml' do + before do + allow(Gitlab.config).to receive(:prometheus).and_raise(Settingslogic::MissingSetting) + end + + it 'does not fail' do + expect(result).to include(status: :success) + expect(project.prometheus_service).to be_nil + end + end + + context 'when prometheus setting is disabled in gitlab.yml' do + before do + prometheus_settings.enable = false + end + + it 'does not configure prometheus' do + expect(result).to include(status: :success) + expect(project.prometheus_service).to be_nil + end + end + + context 'when prometheus listen address is blank in gitlab.yml' do + before do + prometheus_settings.listen_address = '' + end + + it 'does not configure prometheus' do + expect(result).to include(status: :success) + expect(project.prometheus_service).to be_nil + end + end + + context 'when project cannot be created' do + let(:project) { build(:project) } + + before do + project.errors.add(:base, "Test error") + + expect_next_instance_of(::Projects::CreateService) do |project_create_service| + expect(project_create_service).to receive(:execute) + .and_return(project) + end + end + + it 'returns error' do + expect(subject).to receive(:log_error).and_call_original + expect(result).to eq({ + status: :error, + message: 'Could not create project', + failed_step: :create_project + }) + end + end + + context 'when user cannot be added to project' do + before do + subject.instance_variable_set(:@instance_admins, [user, build(:user, :admin)]) + end + + it 'returns error' do + expect(subject).to receive(:log_error).and_call_original + expect(result).to eq({ + status: :error, + message: 'Could not add admins as members', + failed_step: :add_project_members + }) + end + end + + context 'when prometheus manual configuration cannot be saved' do + before do + prometheus_settings.listen_address = 'httpinvalid://localhost:9090' + end + + it 'returns error' do + expect(subject).to receive(:log_error).and_call_original + expect(result).to eq( + status: :error, + message: 'Could not save prometheus manual configuration', + failed_step: :add_prometheus_manual_configuration + ) + end + end + end + end +end diff --git a/spec/support/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb index 02d310a9afa..0de92aedba5 100644 --- a/spec/support/features/rss_shared_examples.rb +++ b/spec/support/features/rss_shared_examples.rb @@ -6,7 +6,7 @@ end shared_examples "it has an RSS button with current_user's feed token" do it "shows the RSS button with current_user's feed token" do - expect(page).to have_css("a:has(.fa-rss)[href*='feed_token=#{user.feed_token}'], .js-rss-button[href*='feed_token=#{user.feed_token}']") + expect(page).to have_css("a:has(.fa-rss)[href*='feed_token=#{user.feed_token}']") end end @@ -18,6 +18,6 @@ end shared_examples "it has an RSS button without a feed token" do it "shows the RSS button without a feed token" do - expect(page).to have_css("a:has(.fa-rss):not([href*='feed_token']), .js-rss-button:not([href*='feed_token'])") + expect(page).to have_css("a:has(.fa-rss):not([href*='feed_token'])") end end diff --git a/spec/support/json_response.rb b/spec/support/json_response.rb index 210b0e6d867..43d8ab73dde 100644 --- a/spec/support/json_response.rb +++ b/spec/support/json_response.rb @@ -1,5 +1,5 @@ RSpec.configure do |config| - config.include_context 'JSON response' + config.include_context 'JSON response', type: :controller config.include_context 'JSON response', type: :request config.include_context 'JSON response', :api end diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb index 0acc9e2a836..f4b02dc5350 100644 --- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb +++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb @@ -46,7 +46,7 @@ shared_examples 'issuable notes filter' do user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable) get :discussions, params: params - discussions = JSON.parse(response.body) + discussions = json_response expect(discussions.count).to eq(1) expect(discussions.first["notes"].first["system"]).to be(false) @@ -56,7 +56,7 @@ shared_examples 'issuable notes filter' do user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_activity], issuable) get :discussions, params: params - discussions = JSON.parse(response.body) + discussions = json_response expect(discussions.count).to eq(1) expect(discussions.first["notes"].first["system"]).to be(true) diff --git a/spec/support/shared_examples/update_invalid_issuable.rb b/spec/support/shared_examples/update_invalid_issuable.rb index 64568de424e..4cb6d001b9b 100644 --- a/spec/support/shared_examples/update_invalid_issuable.rb +++ b/spec/support/shared_examples/update_invalid_issuable.rb @@ -38,7 +38,7 @@ shared_examples 'update invalid issuable' do |klass| put :update, params: params expect(response.status).to eq(409) - expect(JSON.parse(response.body)).to have_key('errors') + expect(json_response).to have_key('errors') end end |