diff options
705 files changed, 7338 insertions, 3801 deletions
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 0d73092cfba..0720ea3e056 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -233,22 +233,3 @@ qa-frontend-node:latest: extends: .qa-frontend-node image: node:latest allow_failure: true - -jsdoc: - extends: - - .default-tags - - .default-retry - - .default-cache - - .except-docs - variables: - SETUP_DB: "false" - stage: post-test - dependencies: ["compile-assets", "compile-assets pull-cache"] - script: - - date - - yarn run jsdoc || true # ignore exit code - artifacts: - name: jsdoc - expire_in: 31d - paths: - - jsdoc/ diff --git a/.gitlab/ci/pages.gitlab-ci.yml b/.gitlab/ci/pages.gitlab-ci.yml index 5d13a72e224..2de09753cca 100644 --- a/.gitlab/ci/pages.gitlab-ci.yml +++ b/.gitlab/ci/pages.gitlab-ci.yml @@ -9,7 +9,7 @@ pages: - master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ee stage: pages - dependencies: ["coverage", "karma", "gitlab:assets:compile", "jsdoc"] + dependencies: ["coverage", "karma", "gitlab:assets:compile"] script: - mv public/ .public/ - mkdir public/ @@ -18,7 +18,6 @@ pages: - mv webpack-report/ public/webpack-report/ || true - cp .public/assets/application-*.css public/application.css || true - cp .public/assets/application-*.css.gz public/application.css.gz || true - - mv jsdoc/ public/jsdoc/ || true artifacts: paths: - public diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml index ac2a70dda0b..9c021b23db6 100644 --- a/.gitlab/ci/qa.gitlab-ci.yml +++ b/.gitlab/ci/qa.gitlab-ci.yml @@ -1,7 +1,6 @@ .package-and-qa-base: image: ruby:2.6-alpine stage: qa - needs: ["build-qa-image", "gitlab:assets:compile pull-cache"] dependencies: [] variables: GIT_DEPTH: "1" @@ -11,24 +10,33 @@ - install_gitlab_gem - ./scripts/trigger-build omnibus only: - - branches@gitlab-org/gitlab-ce - - branches@gitlab-org/gitlab-ee - except: - - master + refs: + - branches@gitlab-org/gitlab-ce + - branches@gitlab-org/gitlab-ee package-and-qa-manual: - extends: - - .package-and-qa-base - - .except-docs-qa - when: manual + extends: .package-and-qa-base except: - - master - - /(^docs[\/-].+|.+-docs$)/ - - /(^qa[\/-].*|.*-qa$) + refs: + - master + - /(^docs[\/-].+|.+-docs$)/ + - /(^qa[\/-].*|.*-qa$)/ + when: manual + needs: ["build-qa-image", "gitlab:assets:compile pull-cache"] + +package-and-qa-manual:master: + extends: .package-and-qa-base + only: + refs: + - master + when: manual + needs: ["build-qa-image", "gitlab:assets:compile"] package-and-qa: extends: .package-and-qa-base - allow_failure: true only: - - /(^qa[\/-].*|.*-qa$)/@gitlab-org/gitlab-ce - - /(^qa[\/-].*|.*-qa$)/@gitlab-org/gitlab-ee + refs: + - /(^qa[\/-].*|.*-qa$)/@gitlab-org/gitlab-ce + - /(^qa[\/-].*|.*-qa$)/@gitlab-org/gitlab-ee + needs: ["build-qa-image", "gitlab:assets:compile pull-cache"] + allow_failure: true diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index 3415f1b6ab4..6695404653c 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -22,7 +22,9 @@ - source scripts/utils.sh .review-docker: - extends: .review-base + extends: + - .default-tags + - .default-retry image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine services: - docker:19.03.0-dind @@ -36,7 +38,13 @@ QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab/${CI_PROJECT_NAME}-qa:${CI_COMMIT_REF_SLUG}" build-qa-image: - extends: .review-docker + extends: + - .review-docker + - .except-docs + only: + refs: + - branches@gitlab-org/gitlab-ce + - branches@gitlab-org/gitlab-ee stage: test script: - time docker build --cache-from ${LATEST_QA_IMAGE} --tag ${QA_IMAGE} --file ./qa/Dockerfile ./ @@ -120,11 +128,14 @@ review-stop: - source utils.sh - source review-apps.sh script: - - delete - artifacts: {} + - delete_release + artifacts: + paths: [] .review-qa-base: - extends: .review-docker + extends: + - .review-docker + - .review-only retry: 2 stage: qa variables: diff --git a/.gitlab/ci/yaml.gitlab-ci.yml b/.gitlab/ci/yaml.gitlab-ci.yml index 3e107b475c9..dd61cb3f035 100644 --- a/.gitlab/ci/yaml.gitlab-ci.yml +++ b/.gitlab/ci/yaml.gitlab-ci.yml @@ -4,6 +4,7 @@ lint-ci-gitlab: extends: - .default-tags - .default-retry + - .except-docs image: sdesbure/yamllint:latest dependencies: [] script: diff --git a/.rubocop.yml b/.rubocop.yml index a20924c21b7..f24cbb6ce92 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -264,8 +264,6 @@ RSpec/EnvAssignment: Exclude: - 'spec/**/fast_spec_helper.rb' - 'ee/spec/**/fast_spec_helper.rb' - - 'spec/**/rails_helper.rb' - - 'ee/spec/**/rails_helper.rb' - 'spec/**/spec_helper.rb' - 'ee/spec/**/spec_helper.rb' RSpec/BeSuccessMatcher: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3898206e3b5..f1f8ff6e862 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -218,7 +218,7 @@ Lint/UriEscapeUnescape: - 'app/models/project_services/drone_ci_service.rb' - 'spec/lib/google_api/auth_spec.rb' - 'spec/requests/api/files_spec.rb' - - 'spec/requests/api/internal_spec.rb' + - 'spec/requests/api/internal/base_spec.rb' # Offense count: 1 # Configuration parameters: CheckForMethodsWithNoSideEffects. @@ -570,11 +570,6 @@ Style/EmptyMethod: Style/Encoding: Enabled: false -# Offense count: 2 -Style/EvalWithLocation: - Exclude: - - 'app/models/service.rb' - # Offense count: 203 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 3c40359d3dc..a13e7b9c87e 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -9.4.2 +10.0.0 @@ -83,7 +83,7 @@ gem 'grape-entity', '~> 0.7.1' gem 'rack-cors', '~> 1.0.0', require: 'rack/cors' # GraphQL API -gem 'graphql', '= 1.8.4' +gem 'graphql', '~> 1.9.11' gem 'graphiql-rails', '~> 1.4.10' gem 'apollo_upload_server', '~> 2.0.0.beta3' gem 'graphql-docs', '~> 1.6.0', group: [:development, :test] @@ -171,7 +171,7 @@ gem 'acts-as-taggable-on', '~> 6.0' gem 'sidekiq', '~> 5.2.7' gem 'sidekiq-cron', '~> 1.0' gem 'redis-namespace', '~> 1.6.0' -gem 'gitlab-sidekiq-fetcher', '0.5.1', require: 'sidekiq-reliable-fetch' +gem 'gitlab-sidekiq-fetcher', '0.5.2', require: 'sidekiq-reliable-fetch' # Cron Parser gem 'fugit', '~> 1.2.1' @@ -216,6 +216,7 @@ gem 'hipchat', '~> 1.5.0' # Jira integration gem 'jira-ruby', '~> 1.7' +gem 'atlassian-jwt', '~> 0.2.0' # Flowdock integration gem 'flowdock', '~> 0.7' @@ -310,7 +311,7 @@ group :metrics do gem 'influxdb', '~> 0.2', require: false # Prometheus - gem 'prometheus-client-mmap', '~> 0.9.8' + gem 'prometheus-client-mmap', '~> 0.9.9' gem 'raindrops', '~> 0.18' end diff --git a/Gemfile.lock b/Gemfile.lock index 0803cf7f752..6add217bc32 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -321,7 +321,7 @@ GEM gitlab-markup (1.7.0) gitlab-peek (0.0.1) railties (>= 4.0.0) - gitlab-sidekiq-fetcher (0.5.1) + gitlab-sidekiq-fetcher (0.5.2) sidekiq (~> 5) gitlab-styles (2.8.0) rubocop (~> 0.69.0) @@ -377,7 +377,7 @@ GEM graphiql-rails (1.4.10) railties sprockets-rails - graphql (1.8.4) + graphql (1.9.11) graphql-docs (1.6.0) commonmarker (~> 0.16) escape_utils (~> 1.2) @@ -650,7 +650,7 @@ GEM parser unparser procto (0.0.3) - prometheus-client-mmap (0.9.8) + prometheus-client-mmap (0.9.9) pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) @@ -1028,6 +1028,7 @@ DEPENDENCIES asciidoctor (~> 2.0.10) asciidoctor-include-ext (~> 0.3.1) asciidoctor-plantuml (= 0.0.9) + atlassian-jwt (~> 0.2.0) attr_encrypted (~> 3.1.0) awesome_print babosa (~> 1.0.2) @@ -1097,7 +1098,7 @@ DEPENDENCIES gitlab-labkit (~> 0.5) gitlab-markup (~> 1.7.0) gitlab-peek (~> 0.0.1) - gitlab-sidekiq-fetcher (= 0.5.1) + gitlab-sidekiq-fetcher (= 0.5.2) gitlab-styles (~> 2.7) gitlab_omniauth-ldap (~> 2.1.1) gon (~> 6.2) @@ -1109,7 +1110,7 @@ DEPENDENCIES grape-path-helpers (~> 1.1) grape_logging (~> 1.7) graphiql-rails (~> 1.4.10) - graphql (= 1.8.4) + graphql (~> 1.9.11) graphql-docs (~> 1.6.0) grpc (~> 1.19.0) haml_lint (~> 0.31.0) @@ -1169,7 +1170,7 @@ DEPENDENCIES org-ruby (~> 0.9.12) pg (~> 1.1) premailer-rails (~> 1.9.7) - prometheus-client-mmap (~> 0.9.8) + prometheus-client-mmap (~> 0.9.9) pry-byebug (~> 3.5.1) pry-rails (~> 0.3.4) puma (~> 3.12) diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue index 4180023b7db..f9284266b72 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.vue +++ b/app/assets/javascripts/boards/components/board_new_issue.vue @@ -114,7 +114,7 @@ export default { name="issue_title" autocomplete="off" /> - <project-select v-if="groupId" :group-id="groupId" /> + <project-select v-if="groupId" :group-id="groupId" :list="list" /> <div class="clearfix prepend-top-10"> <gl-button ref="submit-button" diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue index e8d25e84be1..e5ebb887ce0 100644 --- a/app/assets/javascripts/boards/components/project_select.vue +++ b/app/assets/javascripts/boards/components/project_select.vue @@ -6,6 +6,7 @@ import Icon from '~/vue_shared/components/icon.vue'; import { GlLoadingIcon } from '@gitlab/ui'; import eventHub from '../eventhub'; import Api from '../../api'; +import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants'; export default { name: 'BoardProjectSelect', @@ -19,6 +20,10 @@ export default { required: true, default: 0, }, + list: { + type: Object, + required: true, + }, }, data() { return { @@ -49,6 +54,12 @@ export default { selectable: true, data: (term, callback) => { this.loading = true; + const additionalAttrs = {}; + + if (this.list.type && this.list.type !== 'backlog') { + additionalAttrs.min_access_level = featureAccessLevel.EVERYONE; + } + return Api.groupProjects( this.groupId, term, @@ -56,6 +67,7 @@ export default { with_issues_enabled: true, with_shared: false, include_subgroups: true, + ...additionalAttrs, }, projects => { this.loading = false; diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js index 9069b35db9a..086340105b7 100644 --- a/app/assets/javascripts/boards/models/issue.js +++ b/app/assets/javascripts/boards/models/issue.js @@ -3,7 +3,7 @@ /* global ListMilestone */ /* global ListAssignee */ -import Vue from 'vue'; +import axios from '~/lib/utils/axios_utils'; import './label'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import IssueProject from './project'; @@ -133,7 +133,7 @@ class ListIssue { } const projectPath = this.project ? this.project.path : ''; - return Vue.http.patch(`${this.path}.json`, data).then(({ body = {} } = {}) => { + return axios.patch(`${this.path}.json`, data).then(({ data: body = {} } = {}) => { /** * Since post implementation of Scoped labels, server can reject * same key-ed labels. To keep the UI and server Model consistent, diff --git a/app/assets/javascripts/branches/divergence_graph.js b/app/assets/javascripts/branches/divergence_graph.js index 7dbaf984acf..303735a1807 100644 --- a/app/assets/javascripts/branches/divergence_graph.js +++ b/app/assets/javascripts/branches/divergence_graph.js @@ -25,6 +25,11 @@ export default endpoint => { const names = [...document.querySelectorAll('.js-branch-item')].map( ({ dataset }) => dataset.name, ); + + if (names.length === 0) { + return true; + } + return axios .get(endpoint, { params: { names }, diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index 5f5c8044b49..1f213d5aaf2 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -14,6 +14,8 @@ import ClustersStore from './stores/clusters_store'; import Applications from './components/applications.vue'; import setupToggleButtons from '../toggle_buttons'; +const Environments = () => import('ee_component/clusters/components/environments.vue'); + Vue.use(GlToast); /** @@ -44,6 +46,9 @@ export default class Clusters { helpPath, ingressHelpPath, ingressDnsHelpPath, + environmentsHelpPath, + clustersHelpPath, + deployBoardsHelpPath, clusterId, } = document.querySelector('.js-edit-cluster-form').dataset; @@ -52,7 +57,14 @@ export default class Clusters { this.clusterBannerDismissedKey = `cluster_${this.clusterId}_banner_dismissed`; this.store = new ClustersStore(); - this.store.setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath); + this.store.setHelpPaths( + helpPath, + ingressHelpPath, + ingressDnsHelpPath, + environmentsHelpPath, + clustersHelpPath, + deployBoardsHelpPath, + ); this.store.setManagePrometheusPath(managePrometheusPath); this.store.updateStatus(clusterStatus); this.store.updateStatusReason(clusterStatusReason); @@ -95,11 +107,12 @@ export default class Clusters { setupToggleButtons(toggleButtonsContainer); } this.initApplications(clusterType); + this.initEnvironments(); this.updateContainer(null, this.store.state.status, this.store.state.statusReason); this.addListeners(); - if (statusPath) { + if (statusPath && !this.environments) { this.initPolling(); } } @@ -131,6 +144,34 @@ export default class Clusters { }); } + initEnvironments() { + const { store } = this; + const el = document.querySelector('#js-cluster-environments'); + + if (!el) { + return; + } + + this.environments = new Vue({ + el, + data() { + return { + state: store.state, + }; + }, + render(createElement) { + return createElement(Environments, { + props: { + environments: this.state.environments, + environmentsHelpPath: this.state.environmentsHelpPath, + clustersHelpPath: this.state.clustersHelpPath, + deployBoardsHelpPath: this.state.deployBoardsHelpPath, + }, + }); + }, + }); + } + static initDismissableCallout() { const callout = document.querySelector('.js-cluster-security-warning'); PersistentUserCallout.factory(callout); @@ -390,6 +431,10 @@ export default class Clusters { this.poll.stop(); } + if (this.environments) { + this.environments.$destroy(); + } + this.applications.$destroy(); } } diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js index 772f16cab4e..83533c88f69 100644 --- a/app/assets/javascripts/clusters/stores/clusters_store.js +++ b/app/assets/javascripts/clusters/stores/clusters_store.js @@ -32,6 +32,9 @@ export default class ClusterStore { this.state = { helpPath: null, ingressHelpPath: null, + environmentsHelpPath: null, + clustersHelpPath: null, + deployBoardsHelpPath: null, status: null, rbac: false, statusReason: null, @@ -80,13 +83,24 @@ export default class ClusterStore { updateFailed: false, }, }, + environments: [], }; } - setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath) { + setHelpPaths( + helpPath, + ingressHelpPath, + ingressDnsHelpPath, + environmentsHelpPath, + clustersHelpPath, + deployBoardsHelpPath, + ) { this.state.helpPath = helpPath; this.state.ingressHelpPath = ingressHelpPath; this.state.ingressDnsHelpPath = ingressDnsHelpPath; + this.state.environmentsHelpPath = environmentsHelpPath; + this.state.clustersHelpPath = clustersHelpPath; + this.state.deployBoardsHelpPath = deployBoardsHelpPath; } setManagePrometheusPath(managePrometheusPath) { @@ -191,4 +205,17 @@ export default class ClusterStore { } }); } + + updateEnvironments(environments = []) { + this.state.environments = environments.map(environment => ({ + name: environment.name, + project: environment.project, + environmentPath: environment.environment_path, + lastDeployment: environment.last_deployment, + rolloutStatus: { + instances: environment.rollout_status ? environment.rollout_status.instances : [], + }, + updatedAt: environment.updatedAt, + })); + } } diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index c2397842125..660f0f0ba3e 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -1,4 +1,5 @@ import _ from 'underscore'; +import { spriteIcon } from './lib/utils/common_utils'; const hideFlash = (flashEl, fadeTransition = true) => { if (fadeTransition) { @@ -35,16 +36,11 @@ const createAction = config => ` </a> `; -const createFlashEl = (message, type, isFixedLayout = false) => ` - <div - class="flash-${type}" - > - <div - class="flash-text ${ - isFixedLayout ? 'container-fluid container-limited limit-container-width' : '' - }" - > +const createFlashEl = (message, type) => ` + <div class="flash-content flash-${type} rounded"> + <div class="flash-text"> ${_.escape(message)} + ${spriteIcon('close', 'close-icon')} </div> </div> `; @@ -76,15 +72,10 @@ const createFlash = function createFlash( addBodyClass = false, ) { const flashContainer = parent.querySelector('.flash-container'); - const navigation = parent.querySelector('.content'); if (!flashContainer) return null; - const isFixedLayout = navigation - ? navigation.parentNode.classList.contains('container-limited') - : true; - - flashContainer.innerHTML = createFlashEl(message, type, isFixedLayout); + flashContainer.innerHTML = createFlashEl(message, type); const flashEl = flashContainer.querySelector(`.flash-${type}`); removeFlashClickListener(flashEl, fadeTransition); diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue index aa50fd8ff62..8d2dac47ff2 100644 --- a/app/assets/javascripts/groups/components/app.vue +++ b/app/assets/javascripts/groups/components/app.vue @@ -95,10 +95,8 @@ export default { if (updatePagination) { this.updatePagination(res.headers); } - - return res; + return res.data; }) - .then(res => res.json()) .catch(() => { this.isLoading = false; $.scrollTo(0); @@ -190,11 +188,10 @@ export default { this.targetGroup.isBeingRemoved = true; this.service .leaveGroup(this.targetGroup.leavePath) - .then(res => res.json()) .then(res => { $.scrollTo(0); this.store.removeGroup(this.targetGroup, this.targetParentGroup); - Flash(res.notice, 'notice'); + Flash(res.data.notice, 'notice'); }) .catch(err => { let message = COMMON_STR.FAILURE; diff --git a/app/assets/javascripts/groups/service/groups_service.js b/app/assets/javascripts/groups/service/groups_service.js index b79ba291463..790b581a7c0 100644 --- a/app/assets/javascripts/groups/service/groups_service.js +++ b/app/assets/javascripts/groups/service/groups_service.js @@ -1,40 +1,39 @@ -import Vue from 'vue'; -import '../../vue_shared/vue_resource_interceptor'; +import axios from '~/lib/utils/axios_utils'; export default class GroupsService { constructor(endpoint) { - this.groups = Vue.resource(endpoint); + this.endpoint = endpoint; } getGroups(parentId, page, filterGroups, sort, archived) { - const data = {}; + const params = {}; if (parentId) { - data.parent_id = parentId; + params.parent_id = parentId; } else { // Do not send the following param for sub groups if (page) { - data.page = page; + params.page = page; } if (filterGroups) { - data.filter = filterGroups; + params.filter = filterGroups; } if (sort) { - data.sort = sort; + params.sort = sort; } if (archived) { - data.archived = archived; + params.archived = archived; } } - return this.groups.get(data); + return axios.get(this.endpoint, { params }); } // eslint-disable-next-line class-methods-use-this leaveGroup(endpoint) { - return Vue.http.delete(endpoint); + return axios.delete(endpoint); } } diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index ad1072366f3..c7d4d7c4b9b 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -83,6 +83,11 @@ export default { type: String, required: true, }, + subscriptionsMoreMinutesUrl: { + type: String, + required: false, + default: null, + }, }, computed: { ...mapState([ @@ -265,6 +270,7 @@ export default { :quota-limit="job.runners.quota.limit" :runners-path="runnerHelpUrl" :project-path="projectPath" + :subscriptions-more-minutes-url="subscriptionsMoreMinutesUrl" /> <environments-block diff --git a/app/assets/javascripts/jobs/components/log/line.vue b/app/assets/javascripts/jobs/components/log/line.vue new file mode 100644 index 00000000000..86d0fcc3b74 --- /dev/null +++ b/app/assets/javascripts/jobs/components/log/line.vue @@ -0,0 +1,28 @@ +<script> +import LineNumber from './line_number.vue'; + +export default { + components: { + LineNumber, + }, + props: { + line: { + type: Object, + required: true, + }, + path: { + type: String, + required: true, + }, + }, +}; +</script> + +<template> + <div class="line"> + <line-number :line-number="line.lineNumber" :path="path" /> + <span v-for="(content, i) in line.content" :key="i" class="line-text" :class="content.style">{{ + content.text + }}</span> + </div> +</template> diff --git a/app/assets/javascripts/jobs/components/log/line_header.vue b/app/assets/javascripts/jobs/components/log/line_header.vue new file mode 100644 index 00000000000..4ec212d2333 --- /dev/null +++ b/app/assets/javascripts/jobs/components/log/line_header.vue @@ -0,0 +1,45 @@ +<script> +import Icon from '~/vue_shared/components/icon.vue'; +import LineNumber from './line_number.vue'; + +export default { + components: { + Icon, + LineNumber, + }, + props: { + line: { + type: Object, + required: true, + }, + isClosed: { + type: Boolean, + required: true, + }, + path: { + type: String, + required: true, + }, + }, + computed: { + iconName() { + return this.isClosed ? 'angle-right' : 'angle-down'; + }, + }, + methods: { + handleOnClick() { + this.$emit('toggleLine'); + }, + }, +}; +</script> + +<template> + <div class="line collapsible-line" role="button" @click="handleOnClick"> + <icon :name="iconName" class="arrow" /> + <line-number :line-number="line.lineNumber" :path="path" /> + <span v-for="(content, i) in line.content" :key="i" class="line-text" :class="content.style">{{ + content.text + }}</span> + </div> +</template> diff --git a/app/assets/javascripts/jobs/components/log/line_number.vue b/app/assets/javascripts/jobs/components/log/line_number.vue new file mode 100644 index 00000000000..e06836e2e97 --- /dev/null +++ b/app/assets/javascripts/jobs/components/log/line_number.vue @@ -0,0 +1,52 @@ +<script> +import { GlLink } from '@gitlab/ui'; + +export default { + components: { + GlLink, + }, + props: { + lineNumber: { + type: Number, + required: true, + }, + path: { + type: String, + required: true, + }, + }, + computed: { + /** + * Builds the url for each line number + * + * @returns {String} + */ + buildLineNumber() { + return `${this.path}#${this.lineNumberId}`; + }, + /** + * Array indexes start with 0, so we add 1 + * to create the line number + * + * @returns {Number} the line number + */ + parsedLineNumber() { + return this.lineNumber + 1; + }, + + /** + * Creates the anchor for each link + * + * @returns {String} + */ + lineNumberId() { + return `L${this.parsedLineNumber}`; + }, + }, +}; +</script> +<template> + <gl-link :id="lineNumberId" class="line-number" :href="buildLineNumber">{{ + parsedLineNumber + }}</gl-link> +</template> diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js index add7f9b710a..9c35534523e 100644 --- a/app/assets/javascripts/jobs/index.js +++ b/app/assets/javascripts/jobs/index.js @@ -15,6 +15,7 @@ export default () => { runnerHelpUrl, runnerSettingsUrl, variablesSettingsUrl, + subscriptionsMoreMinutesUrl, endpoint, pagePath, logState, @@ -28,6 +29,7 @@ export default () => { runnerHelpUrl, runnerSettingsUrl, variablesSettingsUrl, + subscriptionsMoreMinutesUrl, endpoint, pagePath, logState, diff --git a/app/assets/javascripts/jobs/store/utils.js b/app/assets/javascripts/jobs/store/utils.js new file mode 100644 index 00000000000..de7de92ed2e --- /dev/null +++ b/app/assets/javascripts/jobs/store/utils.js @@ -0,0 +1,40 @@ +/** + * Parses the job log content into a structure usable by the template + * + * For collaspible lines (section_header = true): + * - creates a new array to hold the lines that are collpasible, + * - adds a isClosed property to handle toggle + * - adds a isHeader property to handle template logic + * For each line: + * - adds the index as lineNumber + * + * @param {Array} lines + * @returns {Array} + */ +export default (lines = []) => + lines.reduce((acc, line, index) => { + if (line.section_header) { + acc.push({ + isClosed: true, + isHeader: true, + line: { + ...line, + lineNumber: index, + }, + + lines: [], + }); + } else if (acc.length && acc[acc.length - 1].isHeader) { + acc[acc.length - 1].lines.push({ + ...line, + lineNumber: index, + }); + } else { + acc.push({ + ...line, + lineNumber: index, + }); + } + + return acc; + }, []); diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue deleted file mode 100644 index cac10474d06..00000000000 --- a/app/assets/javascripts/monitoring/components/charts/area.vue +++ /dev/null @@ -1,304 +0,0 @@ -<script> -import { __ } from '~/locale'; -import { GlLink } from '@gitlab/ui'; -import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; -import dateFormat from 'dateformat'; -import { debounceByAnimationFrame, roundOffFloat } from '~/lib/utils/common_utils'; -import { getSvgIconPathContent } from '~/lib/utils/icon_utils'; -import Icon from '~/vue_shared/components/icon.vue'; -import { chartHeight, graphTypes, lineTypes } from '../../constants'; -import { makeDataSeries } from '~/helpers/monitor_helper'; -import { graphDataValidatorForValues } from '../../utils'; - -let debouncedResize; - -// TODO: Remove this component in favor of the more general time_series.vue -// Please port all changes here to time_series.vue as well. - -export default { - components: { - GlAreaChart, - GlChartSeriesLabel, - GlLink, - Icon, - }, - inheritAttrs: false, - props: { - graphData: { - type: Object, - required: true, - validator: graphDataValidatorForValues.bind(null, false), - }, - containerWidth: { - type: Number, - required: true, - }, - deploymentData: { - type: Array, - required: false, - default: () => [], - }, - projectPath: { - type: String, - required: false, - default: () => '', - }, - showBorder: { - type: Boolean, - required: false, - default: () => false, - }, - singleEmbed: { - type: Boolean, - required: false, - default: false, - }, - thresholds: { - type: Array, - required: false, - default: () => [], - }, - }, - data() { - return { - tooltip: { - title: '', - content: [], - commitUrl: '', - isDeployment: false, - sha: '', - }, - width: 0, - height: chartHeight, - svgs: {}, - primaryColor: null, - }; - }, - computed: { - chartData() { - // Transforms & supplements query data to render appropriate labels & styles - // Input: [{ queryAttributes1 }, { queryAttributes2 }] - // Output: [{ seriesAttributes1 }, { seriesAttributes2 }] - return this.graphData.queries.reduce((acc, query) => { - const { appearance } = query; - const lineType = - appearance && appearance.line && appearance.line.type - ? appearance.line.type - : lineTypes.default; - const lineWidth = - appearance && appearance.line && appearance.line.width - ? appearance.line.width - : undefined; - - const series = makeDataSeries(query.result, { - name: this.formatLegendLabel(query), - lineStyle: { - type: lineType, - width: lineWidth, - }, - areaStyle: { - opacity: - appearance && appearance.area && typeof appearance.area.opacity === 'number' - ? appearance.area.opacity - : undefined, - }, - }); - - return acc.concat(series); - }, []); - }, - chartOptions() { - return { - xAxis: { - name: __('Time'), - type: 'time', - axisLabel: { - formatter: date => dateFormat(date, 'h:MM TT'), - }, - axisPointer: { - snap: true, - }, - }, - yAxis: { - name: this.yAxisLabel, - axisLabel: { - formatter: num => roundOffFloat(num, 3).toString(), - }, - }, - series: this.scatterSeries, - dataZoom: [this.dataZoomConfig], - }; - }, - dataZoomConfig() { - const handleIcon = this.svgs['scroll-handle']; - - return handleIcon ? { handleIcon } : {}; - }, - earliestDatapoint() { - return this.chartData.reduce((acc, series) => { - const { data } = series; - const { length } = data; - if (!length) { - return acc; - } - - const [first] = data[0]; - const [last] = data[length - 1]; - const seriesEarliest = first < last ? first : last; - - return seriesEarliest < acc || acc === null ? seriesEarliest : acc; - }, null); - }, - isMultiSeries() { - return this.tooltip.content.length > 1; - }, - recentDeployments() { - return this.deploymentData.reduce((acc, deployment) => { - if (deployment.created_at >= this.earliestDatapoint) { - acc.push({ - id: deployment.id, - createdAt: deployment.created_at, - sha: deployment.sha, - commitUrl: `${this.projectPath}/commit/${deployment.sha}`, - tag: deployment.tag, - tagUrl: deployment.tag ? `${this.tagsPath}/${deployment.ref.name}` : null, - ref: deployment.ref.name, - showDeploymentFlag: false, - }); - } - - return acc; - }, []); - }, - scatterSeries() { - return { - type: graphTypes.deploymentData, - data: this.recentDeployments.map(deployment => [deployment.createdAt, 0]), - symbol: this.svgs.rocket, - symbolSize: 14, - itemStyle: { - color: this.primaryColor, - }, - }; - }, - yAxisLabel() { - return `${this.graphData.y_label}`; - }, - }, - watch: { - containerWidth: 'onResize', - }, - beforeDestroy() { - window.removeEventListener('resize', debouncedResize); - }, - created() { - debouncedResize = debounceByAnimationFrame(this.onResize); - window.addEventListener('resize', debouncedResize); - this.setSvg('rocket'); - this.setSvg('scroll-handle'); - }, - methods: { - formatLegendLabel(query) { - return `${query.label}`; - }, - formatTooltipText(params) { - this.tooltip.title = dateFormat(params.value, 'dd mmm yyyy, h:MMTT'); - this.tooltip.content = []; - params.seriesData.forEach(seriesData => { - this.tooltip.isDeployment = seriesData.componentSubType === graphTypes.deploymentData; - if (this.tooltip.isDeployment) { - const [deploy] = this.recentDeployments.filter( - deployment => deployment.createdAt === seriesData.value[0], - ); - this.tooltip.sha = deploy.sha.substring(0, 8); - this.tooltip.commitUrl = deploy.commitUrl; - } else { - const { seriesName, color } = seriesData; - // seriesData.value contains the chart's [x, y] value pair - // seriesData.value[1] is threfore the chart y value - const value = seriesData.value[1].toFixed(3); - - this.tooltip.content.push({ - name: seriesName, - value, - color, - }); - } - }); - }, - setSvg(name) { - getSvgIconPathContent(name) - .then(path => { - if (path) { - this.$set(this.svgs, name, `path://${path}`); - } - }) - .catch(() => {}); - }, - onChartUpdated(chart) { - [this.primaryColor] = chart.getOption().color; - }, - onResize() { - if (!this.$refs.areaChart) return; - const { width } = this.$refs.areaChart.$el.getBoundingClientRect(); - this.width = width; - }, - }, -}; -</script> - -<template> - <div - class="prometheus-graph col-12" - :class="[showBorder ? 'p-2' : 'p-0', { 'col-lg-6': !singleEmbed }]" - > - <div :class="{ 'prometheus-graph-embed w-100 p-3': showBorder }"> - <div class="prometheus-graph-header"> - <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5> - <div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div> - </div> - <gl-area-chart - ref="areaChart" - v-bind="$attrs" - :data="chartData" - :option="chartOptions" - :format-tooltip-text="formatTooltipText" - :thresholds="thresholds" - :width="width" - :height="height" - @updated="onChartUpdated" - > - <template v-if="tooltip.isDeployment"> - <template slot="tooltipTitle"> - {{ __('Deployed') }} - </template> - <div slot="tooltipContent" class="d-flex align-items-center"> - <icon name="commit" class="mr-2" /> - <gl-link :href="tooltip.commitUrl">{{ tooltip.sha }}</gl-link> - </div> - </template> - <template v-else> - <template slot="tooltipTitle"> - <div class="text-nowrap"> - {{ tooltip.title }} - </div> - </template> - <template slot="tooltipContent"> - <div - v-for="(content, key) in tooltip.content" - :key="key" - class="d-flex justify-content-between" - > - <gl-chart-series-label :color="isMultiSeries ? content.color : ''"> - {{ content.name }} - </gl-chart-series-label> - <div class="prepend-left-32"> - {{ content.value }} - </div> - </div> - </template> - </template> - </gl-area-chart> - </div> - </div> -</template> diff --git a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js index 820f0f7f12d..0d377eb9c68 100644 --- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js +++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js @@ -5,9 +5,10 @@ import { parseBoolean } from '~/lib/utils/common_utils'; document.addEventListener('DOMContentLoaded', () => { const twoFactorNode = document.querySelector('.js-two-factor-auth'); const skippable = parseBoolean(twoFactorNode.dataset.twoFactorSkippable); + if (skippable) { const button = `<a class="btn btn-sm btn-warning float-right" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`; - const flashAlert = document.querySelector('.flash-alert .container-fluid'); + const flashAlert = document.querySelector('.flash-alert'); if (flashAlert) flashAlert.insertAdjacentHTML('beforeend', button); } diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js index 332b6811af6..33e9a8e9d56 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -73,13 +73,6 @@ export default class Project { .remove(); return e.preventDefault(); }); - $('.hide-shared-runner-limit-message').on('click', function(e) { - var $alert = $(this).parents('.shared-runner-quota-message'); - var scope = $alert.data('scope'); - Cookies.set('hide_shared_runner_quota_message', 'false', { path: scope }); - $alert.remove(); - e.preventDefault(); - }); $('.hide-auto-devops-implicitly-enabled-banner').on('click', function(e) { const projectId = $(this).data('project-id'); const cookieKey = `hide_auto_devops_implicitly_enabled_banner_${projectId}`; diff --git a/app/assets/javascripts/pages/projects/shared/permissions/constants.js b/app/assets/javascripts/pages/projects/shared/permissions/constants.js index 73269c6f3ba..6771391254e 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/constants.js +++ b/app/assets/javascripts/pages/projects/shared/permissions/constants.js @@ -16,7 +16,7 @@ export const visibilityLevelDescriptions = { ), }; -const featureAccessLevel = { +export const featureAccessLevel = { NOT_ENABLED: 0, PROJECT_MEMBERS: 10, EVERYONE: 20, diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index 96f6d02a68f..af05d069f97 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -1,3 +1,5 @@ +$notification-box-shadow-color: rgba(0, 0, 0, 0.25); + .flash-container { cursor: pointer; margin: 0; @@ -6,12 +8,32 @@ position: relative; z-index: 1; + &.sticky { + position: sticky; + position: -webkit-sticky; + top: $flash-container-top; + z-index: 200; + + .flash-content { + box-shadow: 0 2px 4px 0 $notification-box-shadow-color; + } + } + + .close-icon { + width: 16px; + height: 16px; + position: absolute; + right: $gl-padding; + top: $gl-padding; + } + .flash-notice, .flash-alert, .flash-success, .flash-warning { border-radius: $border-radius-default; color: $white-light; + padding-right: $gl-padding * 2; .container-fluid, .container-fluid.container-limited { @@ -97,3 +119,28 @@ } } } + +.gl-browser-ie .flash-container { + position: fixed; + max-width: $limited-layout-width; + left: 50%; + + .flash-alert { + position: relative; + left: -50%; + } +} + +.with-system-header .flash-container { + top: $flash-container-top + $system-header-height; +} + +.with-performance-bar { + .flash-container { + top: $flash-container-top + $performance-bar-height; + } + + &.with-system-header .flash-container { + top: $flash-container-top + $performance-bar-height + $system-header-height; + } +} diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 97cb9d90ff0..7205324e86f 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -29,6 +29,15 @@ body { } } +.container-fluid { + &.limit-container-width { + .flash-container.sticky { + max-width: $limited-layout-width; + margin: 0 auto; + } + } +} + .content-wrapper { margin-top: $header-height; padding-bottom: 100px; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 7a3fd2adfbb..15a779dde1d 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -377,6 +377,7 @@ $performance-bar-height: 35px; $system-header-height: 16px; $system-footer-height: $system-header-height; $flash-height: 52px; +$flash-container-top: 48px; $context-header-height: 60px; $breadcrumb-min-height: 48px; $home-panel-title-row-height: 64px; diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss index 255383d89c8..88d6b0d3746 100644 --- a/app/assets/stylesheets/pages/clusters.scss +++ b/app/assets/stylesheets/pages/clusters.scss @@ -154,3 +154,12 @@ } } } + +.cluster-deployments-warning { + color: $orange-600; +} + +.badge.pods-badge { + color: $black; + font-weight: $gl-font-weight-bold; +} diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 6317fa7c8d1..32a36da56fe 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -14,7 +14,11 @@ class Admin::GroupsController < Admin::ApplicationController # rubocop: disable CodeReuse/ActiveRecord def show - @group = Group.with_statistics.joins(:route).group('routes.path').find_by_full_path(params[:id]) + # Group.with_statistics doesn't behave nicely when including other relations. + # Group.find_by_full_path includes the routes relation to avoid a common N+1 + # (at the expense of this action: there are two queries here to find and retrieve + # the Group with statistics). + @group = Group.with_statistics.find(group&.id) @members = present_members( @group.members.order("access_level DESC").page(params[:members_page])) @requesters = present_members( diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 1773ac2d508..b735f9ff3b8 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -40,11 +40,11 @@ class IssuableFinder requires_cross_project_access unless: -> { project? } # This is used as a common filter for None / Any - FILTER_NONE = 'none'.freeze - FILTER_ANY = 'any'.freeze + FILTER_NONE = 'none' + FILTER_ANY = 'any' # This is used in unassigning users - NONE = '0'.freeze + NONE = '0' attr_accessor :current_user, :params diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index d001e18fea9..ed6d20b9585 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -21,7 +21,7 @@ class TodosFinder requires_cross_project_access unless: -> { project? } - NONE = '0'.freeze + NONE = '0' TODO_TYPES = Set.new(%w(Issue MergeRequest Epic)).freeze diff --git a/app/graphql/mutations/notes/create/base.rb b/app/graphql/mutations/notes/create/base.rb index d3a5dae2188..cf9f74a63d8 100644 --- a/app/graphql/mutations/notes/create/base.rb +++ b/app/graphql/mutations/notes/create/base.rb @@ -18,8 +18,6 @@ module Mutations required: true, description: copy_field_description(Types::Notes::NoteType, :body) - private - def resolve(args) noteable = authorized_find!(id: args[:noteable_id]) @@ -37,6 +35,8 @@ module Mutations } end + private + def create_note_params(noteable, args) { noteable: noteable, diff --git a/app/graphql/resolvers/full_path_resolver.rb b/app/graphql/resolvers/full_path_resolver.rb index 972f318c806..2afd0411ea6 100644 --- a/app/graphql/resolvers/full_path_resolver.rb +++ b/app/graphql/resolvers/full_path_resolver.rb @@ -11,7 +11,7 @@ module Resolvers end def model_by_full_path(model, full_path) - BatchLoader.for(full_path).batch(key: model) do |full_paths, loader, args| + BatchLoader::GraphQL.for(full_path).batch(key: model) do |full_paths, loader, args| # `with_route` avoids an N+1 calculating full_path args[:key].where_full_path_in(full_paths).with_route.each do |model_instance| loader.call(model_instance.full_path, model_instance) diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb index 6988b451ec3..dd104e83f43 100644 --- a/app/graphql/resolvers/issues_resolver.rb +++ b/app/graphql/resolvers/issues_resolver.rb @@ -41,13 +41,11 @@ module Resolvers type Types::IssueType, null: true - alias_method :project, :object - def resolve(**args) # The project could have been loaded in batch by `BatchLoader`. # At this point we need the `id` of the project to query for issues, so # make sure it's loaded and not `nil` before continuing. - project.sync if project.respond_to?(:sync) + project = object.respond_to?(:sync) ? object.sync : object return Issue.none if project.nil? # Will need to be be made group & namespace aware with diff --git a/app/graphql/resolvers/merge_requests_resolver.rb b/app/graphql/resolvers/merge_requests_resolver.rb index b84e60066e1..1740d614b69 100644 --- a/app/graphql/resolvers/merge_requests_resolver.rb +++ b/app/graphql/resolvers/merge_requests_resolver.rb @@ -25,8 +25,10 @@ module Resolvers # rubocop: disable CodeReuse/ActiveRecord def batch_load(iid) - BatchLoader.for(iid.to_s).batch(key: project) do |iids, loader, args| - args[:key].merge_requests.where(iid: iids).each do |mr| + BatchLoader::GraphQL.for(iid.to_s).batch(key: project) do |iids, loader, args| + arg_key = args[:key].respond_to?(:sync) ? args[:key].sync : args[:key] + + arg_key.merge_requests.where(iid: iids).each do |mr| loader.call(mr.iid.to_s, mr) end end diff --git a/app/graphql/resolvers/namespace_projects_resolver.rb b/app/graphql/resolvers/namespace_projects_resolver.rb index 677ea808aeb..f5b60f91be6 100644 --- a/app/graphql/resolvers/namespace_projects_resolver.rb +++ b/app/graphql/resolvers/namespace_projects_resolver.rb @@ -9,13 +9,11 @@ module Resolvers type Types::ProjectType, null: true - alias_method :namespace, :object - def resolve(include_subgroups:) # The namespace could have been loaded in batch by `BatchLoader`. # At this point we need the `id` or the `full_path` of the namespace # to query for projects, so make sure it's loaded and not `nil` before continuing. - namespace.sync if namespace.respond_to?(:sync) + namespace = object.respond_to?(:sync) ? object.sync : object return Project.none if namespace.nil? if include_subgroups diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb index bbe05f40999..8ef3ed9e8a5 100644 --- a/app/helpers/boards_helper.rb +++ b/app/helpers/boards_helper.rb @@ -10,7 +10,7 @@ module BoardsHelper boards_endpoint: @boards_endpoint, lists_endpoint: board_lists_path(board), board_id: board.id, - disabled: "#{!can?(current_user, :admin_list, current_board_parent)}", + disabled: (!can?(current_user, :create_non_backlog_issues, board)).to_s, issue_link_base: build_issue_link_base, root_path: root_path, bulk_update_path: @bulk_issues_path, diff --git a/app/helpers/jobs_helper.rb b/app/helpers/jobs_helper.rb new file mode 100644 index 00000000000..46edba261dd --- /dev/null +++ b/app/helpers/jobs_helper.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module JobsHelper + def jobs_data + { + "endpoint" => project_job_path(@project, @build, format: :json), + "project_path" => @project.full_path, + "deployment_help_url" => help_page_path('user/project/clusters/index.html', anchor: 'troubleshooting-failed-deployment-jobs'), + "runner_help_url" => help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner'), + "runner_settings_url" => project_runners_path(@build.project, anchor: 'js-runners-settings'), + "variables_settings_url" => project_variables_path(@build.project, anchor: 'js-cicd-variables-settings'), + "page_path" => project_job_path(@project, @build), + "build_status" => @build.status, + "build_stage" => @build.stage, + "log_state" => '', + "build_options" => javascript_build_options + } + end +end diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 04db1980b99..8b93ead0ee6 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -2,44 +2,44 @@ module Emails module Notes - def note_commit_email(recipient_id, note_id) + def note_commit_email(recipient_id, note_id, reason = nil) setup_note_mail(note_id, recipient_id) @commit = @note.noteable @target_url = project_commit_url(*note_target_url_options) - mail_answer_note_thread(@commit, @note, note_thread_options(recipient_id)) + mail_answer_note_thread(@commit, @note, note_thread_options(recipient_id, reason)) end - def note_issue_email(recipient_id, note_id) + def note_issue_email(recipient_id, note_id, reason = nil) setup_note_mail(note_id, recipient_id) @issue = @note.noteable @target_url = project_issue_url(*note_target_url_options) - mail_answer_note_thread(@issue, @note, note_thread_options(recipient_id)) + mail_answer_note_thread(@issue, @note, note_thread_options(recipient_id, reason)) end - def note_merge_request_email(recipient_id, note_id) + def note_merge_request_email(recipient_id, note_id, reason = nil) setup_note_mail(note_id, recipient_id) @merge_request = @note.noteable @target_url = project_merge_request_url(*note_target_url_options) - mail_answer_note_thread(@merge_request, @note, note_thread_options(recipient_id)) + mail_answer_note_thread(@merge_request, @note, note_thread_options(recipient_id, reason)) end - def note_project_snippet_email(recipient_id, note_id) + def note_project_snippet_email(recipient_id, note_id, reason = nil) setup_note_mail(note_id, recipient_id) @snippet = @note.noteable @target_url = project_snippet_url(*note_target_url_options) - mail_answer_note_thread(@snippet, @note, note_thread_options(recipient_id)) + mail_answer_note_thread(@snippet, @note, note_thread_options(recipient_id, reason)) end - def note_personal_snippet_email(recipient_id, note_id) + def note_personal_snippet_email(recipient_id, note_id, reason = nil) setup_note_mail(note_id, recipient_id) @snippet = @note.noteable @target_url = snippet_url(@note.noteable) - mail_answer_note_thread(@snippet, @note, note_thread_options(recipient_id)) + mail_answer_note_thread(@snippet, @note, note_thread_options(recipient_id, reason)) end private @@ -48,11 +48,12 @@ module Emails [@project || @group, @note.noteable, anchor: "note_#{@note.id}"] end - def note_thread_options(recipient_id) + def note_thread_options(recipient_id, reason) { from: sender(@note.author_id), to: recipient(recipient_id, @project&.group || @group), - subject: subject("#{@note.noteable.title} (#{@note.noteable.reference_link_text})") + subject: subject("#{@note.noteable.title} (#{@note.noteable.reference_link_text})"), + 'X-GitLab-NotificationReason' => reason } end diff --git a/app/models/board.rb b/app/models/board.rb index 50b6ca9b70f..b5d07f1b282 100644 --- a/app/models/board.rb +++ b/app/models/board.rb @@ -19,6 +19,7 @@ class Board < ApplicationRecord def parent @parent ||= group || project end + alias_method :resource_parent, :parent def group_board? group_id.present? diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 64e372878e6..2b6f10ef79f 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -460,8 +460,8 @@ module Ci canceled? && auto_canceled_by_id? end - def cancel_running - retry_optimistic_lock(cancelable_statuses) do |cancelable| + def cancel_running(retries: nil) + retry_optimistic_lock(cancelable_statuses, retries) do |cancelable| cancelable.find_each do |job| yield(job) if block_given? job.cancel @@ -469,10 +469,10 @@ module Ci end end - def auto_cancel_running(pipeline) + def auto_cancel_running(pipeline, retries: nil) update(auto_canceled_by: pipeline) - cancel_running do |job| + cancel_running(retries: retries) do |job| job.auto_canceled_by = pipeline end end @@ -670,6 +670,7 @@ module Ci variables.append(key: 'CI_COMMIT_REF_PROTECTED', value: (!!protected_ref?).to_s) if merge_request_event? && merge_request + variables.append(key: 'CI_MERGE_REQUEST_EVENT_TYPE', value: merge_request_event_type.to_s) variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', value: source_sha.to_s) variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s) variables.concat(merge_request.predefined_variables) @@ -772,10 +773,18 @@ module Ci triggered_by_merge_request? && target_sha.present? end + def merge_train_pipeline? + merge_request_pipeline? && merge_train_ref? + end + def merge_request_ref? MergeRequest.merge_request_ref?(ref) end + def merge_train_ref? + MergeRequest.merge_train_ref?(ref) + end + def matches_sha_or_source_sha?(sha) self.sha == sha || self.source_sha == sha end @@ -804,6 +813,20 @@ module Ci errors ? errors.full_messages.to_sentence : "" end + def merge_request_event_type + return unless merge_request_event? + + strong_memoize(:merge_request_event_type) do + if detached_merge_request_pipeline? + :detached + elsif merge_request_pipeline? + :merged_result + elsif merge_train_pipeline? + :merge_train + end + end + end + private def ci_yaml_from_repo diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index eefe9f00836..a998d9b7e1b 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -277,6 +277,10 @@ module Issuable end end + def resource_parent + project + end + def milestone_available? project_id == milestone&.project_id || project.ancestors_upto.compact.include?(milestone&.group) end diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index 3a486632800..8b011bca72c 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -33,12 +33,12 @@ module Routable # # Returns a single object, or nil. def find_by_full_path(path, follow_redirects: false) - increment_counter(:routable_find_by_full_path, 'Number of calls to Routable.find_by_full_path') + routable_calls_counter.increment(method: 'find_by_full_path') if Feature.enabled?(:routable_two_step_lookup) # Case sensitive match first (it's cheaper and the usual case) # If we didn't have an exact match, we perform a case insensitive search - found = joins(:route).find_by(routes: { path: path }) || where_full_path_in([path]).take + found = includes(:route).find_by(routes: { path: path }) || where_full_path_in([path]).take else order_sql = Arel.sql("(CASE WHEN routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)") found = where_full_path_in([path]).reorder(order_sql).take @@ -61,22 +61,18 @@ module Routable def where_full_path_in(paths) return none if paths.empty? - increment_counter(:routable_where_full_path_in, 'Number of calls to Routable.where_full_path_in') + routable_calls_counter.increment(method: 'where_full_path_in') wheres = paths.map do |path| "(LOWER(routes.path) = LOWER(#{connection.quote(path)}))" end - joins(:route).where(wheres.join(' OR ')) + includes(:route).where(wheres.join(' OR ')).references(:routes) end # Temporary instrumentation of method calls - def increment_counter(counter, description) - @counters[counter] ||= Gitlab::Metrics.counter(counter, description) - - @counters[counter].increment - rescue - # ignore the error + def routable_calls_counter + @routable_calls_counter ||= Gitlab::Metrics.counter(:gitlab_routable_calls_total, 'Number of calls to Routable by method') end end diff --git a/app/models/event.rb b/app/models/event.rb index 392d7368033..52d54be39a9 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -51,6 +51,7 @@ class Event < ApplicationRecord belongs_to :author, class_name: "User" belongs_to :project + belongs_to :group belongs_to :target, -> { # If the association for "target" defines an "author" association we want to diff --git a/app/models/member.rb b/app/models/member.rb index dbae1076670..6457fe9ef0c 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -399,7 +399,7 @@ class Member < ApplicationRecord end def post_update_hook - # override in sub class + system_hook_service.execute_hooks_for(self, :update) end def post_destroy_hook diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index bfd636fa62a..28e450f9b30 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1142,6 +1142,10 @@ class MergeRequest < ApplicationRecord ref.start_with?("refs/#{Repository::REF_MERGE_REQUEST}/") end + def self.merge_train_ref?(ref) + %r{\Arefs/#{Repository::REF_MERGE_REQUEST}/\d+/train\z}.match?(ref) + end + def in_locked_state begin lock_mr diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 2402fa8e38f..4db2b7a74e5 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -197,7 +197,7 @@ class MergeRequestDiff < ApplicationRecord def lines_count strong_memoize(:lines_count) do - diffs.diff_files.sum(&:line_count) + raw_diffs(limits: false).line_count end end @@ -222,6 +222,10 @@ class MergeRequestDiff < ApplicationRecord commits.last end + def last_commit + commits.first + end + def base_commit return unless base_commit_sha diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 2ad2838111e..915978d37b8 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -16,6 +16,7 @@ class Milestone < ApplicationRecord include Referable include StripAttribute include Milestoneish + include FromUnion include Gitlab::SQL::Pattern cache_markdown_field :title, pipeline: :single_line @@ -24,6 +25,12 @@ class Milestone < ApplicationRecord belongs_to :project belongs_to :group + # A one-to-one relationship is set up here as part of a MVC: https://gitlab.com/gitlab-org/gitlab-ce/issues/62402 + # However, on the long term, we will want a many-to-many relationship between Release and Milestone. + # The "has_one through" allows us today to set up this one-to-one relationship while setting up the architecture for the long-term (ie intermediate table). + has_one :milestone_release + has_one :release, through: :milestone_release + has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.milestones&.maximum(:iid) } has_internal_id :iid, scope: :group, init: ->(s) { s&.group&.milestones&.maximum(:iid) } @@ -59,6 +66,7 @@ class Milestone < ApplicationRecord validate :milestone_type_check validate :start_date_should_be_less_than_due_date, if: proc { |m| m.start_date.present? && m.due_date.present? } validate :dates_within_4_digits + validates_associated :milestone_release, message: -> (_, obj) { obj[:value].errors.full_messages.join(",") } strip_attributes :title @@ -253,6 +261,7 @@ class Milestone < ApplicationRecord def parent group || project end + alias_method :resource_parent, :parent def group_milestone? group_id.present? diff --git a/app/models/milestone_release.rb b/app/models/milestone_release.rb new file mode 100644 index 00000000000..c8743a8cad8 --- /dev/null +++ b/app/models/milestone_release.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class MilestoneRelease < ApplicationRecord + belongs_to :milestone + belongs_to :release + + validates :milestone_id, uniqueness: { scope: [:release_id] } + validate :same_project_between_milestone_and_release + + private + + def same_project_between_milestone_and_release + return if milestone&.project_id == release&.project_id + + errors.add(:base, 'does not have the same project as the milestone') + end +end diff --git a/app/models/note.rb b/app/models/note.rb index ebd13675dc9..0d024b0a25c 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -477,6 +477,7 @@ class Note < ApplicationRecord def parent project end + alias_method :resource_parent, :parent private diff --git a/app/models/release.rb b/app/models/release.rb index 459a7c29ad0..b2e65974aa0 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -12,6 +12,12 @@ class Release < ApplicationRecord has_many :links, class_name: 'Releases::Link' + # A one-to-one relationship is set up here as part of a MVC: https://gitlab.com/gitlab-org/gitlab-ce/issues/62402 + # However, on the long term, we will want a many-to-many relationship between Release and Milestone. + # The "has_one through" allows us today to set up this one-to-one relationship while setting up the architecture for the long-term (ie intermediate table). + has_one :milestone_release + has_one :milestone, through: :milestone_release + default_value_for :released_at, allows_nil: false do Time.zone.now end @@ -20,6 +26,7 @@ class Release < ApplicationRecord validates :description, :project, :tag, presence: true validates :name, presence: true, on: :create + validates_associated :milestone_release, message: -> (_, obj) { obj[:value].errors.full_messages.join(",") } scope :sorted, -> { order(released_at: :desc) } diff --git a/app/models/repository.rb b/app/models/repository.rb index 7882b2b3036..5cb4b56a114 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -456,6 +456,10 @@ class Repository def after_import expire_content_cache + # This call is stubbed in tests due to being an expensive operation + # It can be reenabled for specific tests via: + # + # allow(DetectRepositoryLanguagesWorker).to receive(:perform_async).and_call_original DetectRepositoryLanguagesWorker.perform_async(project.id) end diff --git a/app/models/service.rb b/app/models/service.rb index f6d8fb1fb46..431c5881460 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -174,7 +174,7 @@ class Service < ApplicationRecord # Also keep track of updated properties in a similar way as ActiveModel::Dirty def self.prop_accessor(*args) args.each do |arg| - class_eval %{ + class_eval <<~RUBY, __FILE__, __LINE__ + 1 unless method_defined?(arg) def #{arg} properties['#{arg}'] @@ -198,7 +198,7 @@ class Service < ApplicationRecord def #{arg}_was updated_properties['#{arg}'] end - } + RUBY end end @@ -209,12 +209,12 @@ class Service < ApplicationRecord self.prop_accessor(*args) args.each do |arg| - class_eval %{ + class_eval <<~RUBY, __FILE__, __LINE__ + 1 def #{arg}? # '!!' is used because nil or empty string is converted to nil !!ActiveRecord::Type::Boolean.new.cast(#{arg}) end - } + RUBY end end diff --git a/app/models/todo.rb b/app/models/todo.rb index 1ec04189482..f7f30aed832 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -146,6 +146,7 @@ class Todo < ApplicationRecord def parent project end + alias_method :resource_parent, :parent def unmergeable? action == UNMERGEABLE diff --git a/app/policies/board_policy.rb b/app/policies/board_policy.rb index 4bf1e7bd3e1..b8435dad3f1 100644 --- a/app/policies/board_policy.rb +++ b/app/policies/board_policy.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class BoardPolicy < BasePolicy + include FindGroupProjects + delegate { @subject.parent } condition(:is_group_board) { @subject.group_board? } @@ -13,4 +15,20 @@ class BoardPolicy < BasePolicy enable :read_milestone enable :read_issue end + + condition(:reporter_of_group_projects) do + next unless @user + + group_projects_for(user: @user, group: @subject.parent) + .visible_to_user_and_access_level(@user, ::Gitlab::Access::REPORTER) + .exists? + end + + rule { is_group_board & reporter_of_group_projects }.policy do + enable :create_non_backlog_issues + end + + rule { is_project_board & can?(:admin_issue) }.policy do + enable :create_non_backlog_issues + end end diff --git a/app/policies/concerns/find_group_projects.rb b/app/policies/concerns/find_group_projects.rb new file mode 100644 index 00000000000..e2cb90079c7 --- /dev/null +++ b/app/policies/concerns/find_group_projects.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module FindGroupProjects + extend ActiveSupport::Concern + + def group_projects_for(user:, group:) + GroupProjectsFinder.new( + group: group, + current_user: user, + options: { include_subgroups: true, only_owned: true } + ).execute + end +end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 5d2b74b17a2..c726c7c24a7 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class GroupPolicy < BasePolicy + include FindGroupProjects + desc "Group is public" with_options scope: :subject, score: 0 condition(:public_group) { @subject.public? } @@ -22,7 +24,7 @@ class GroupPolicy < BasePolicy condition(:can_change_parent_share_with_group_lock) { can?(:change_share_with_group_lock, @subject.parent) } condition(:has_projects) do - GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true, only_owned: true }).execute.any? + group_projects_for(user: @user, group: @subject).any? end with_options scope: :subject, score: 0 diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb index b928988ed8c..5231a8efa55 100644 --- a/app/presenters/ci/build_runner_presenter.rb +++ b/app/presenters/ci/build_runner_presenter.rb @@ -4,8 +4,8 @@ module Ci class BuildRunnerPresenter < SimpleDelegator include Gitlab::Utils::StrongMemoize - RUNNER_REMOTE_TAG_PREFIX = 'refs/tags/'.freeze - RUNNER_REMOTE_BRANCH_PREFIX = 'refs/remotes/origin/'.freeze + RUNNER_REMOTE_TAG_PREFIX = 'refs/tags/' + RUNNER_REMOTE_BRANCH_PREFIX = 'refs/remotes/origin/' def artifacts return unless options[:artifacts] diff --git a/app/presenters/ci/pipeline_presenter.rb b/app/presenters/ci/pipeline_presenter.rb index 358473d0a74..a96f97988b2 100644 --- a/app/presenters/ci/pipeline_presenter.rb +++ b/app/presenters/ci/pipeline_presenter.rb @@ -34,6 +34,18 @@ module Ci end end + NAMES = { + merge_train: s_('Pipeline|Merge train pipeline'), + merged_result: s_('Pipeline|Merged result pipeline'), + detached: s_('Pipeline|Detached merge request pipeline') + }.freeze + + def name + # Currently, `merge_request_event_type` is the only source to name pipelines + # but this could be extended with the other types in the future. + NAMES.fetch(pipeline.merge_request_event_type, s_('Pipeline|Pipeline')) + end + def ref_text if pipeline.detached_merge_request_pipeline? _("for %{link_to_merge_request} with %{link_to_merge_request_source_branch}").html_safe % { link_to_merge_request: link_to_merge_request, link_to_merge_request_source_branch: link_to_merge_request_source_branch } diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb index fff6d23efdf..d1bf0344b66 100644 --- a/app/presenters/clusterable_presenter.rb +++ b/app/presenters/clusterable_presenter.rb @@ -53,6 +53,11 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated raise NotImplementedError end + # Will be overidden in EE + def environments_cluster_path(cluster) + nil + end + def empty_state_help_text nil end diff --git a/app/serializers/entity_date_helper.rb b/app/serializers/entity_date_helper.rb index f515abe5917..d8f267d7183 100644 --- a/app/serializers/entity_date_helper.rb +++ b/app/serializers/entity_date_helper.rb @@ -46,12 +46,14 @@ module EntityDateHelper # If start date is provided and elapsed, with no due date, it returns "# days elapsed" def remaining_days_in_words(due_date, start_date = nil) if due_date&.past? - content_tag(:strong, 'Past due') + content_tag(:strong, _('Past due')) + elsif due_date&.today? + content_tag(:strong, _('Today')) elsif start_date&.future? - content_tag(:strong, 'Upcoming') + content_tag(:strong, _('Upcoming')) elsif due_date is_upcoming = (due_date - Date.today).to_i > 0 - time_ago = time_ago_in_words(due_date) + time_ago = distance_of_time_in_words(due_date, Date.today) # https://gitlab.com/gitlab-org/gitlab-ce/issues/49440 # @@ -59,8 +61,8 @@ module EntityDateHelper # of the string instead of piecewise translations. content = time_ago .gsub(/\d+/) { |match| "<strong>#{match}</strong>" } - .remove("about ") - remaining_or_ago = is_upcoming ? _("remaining") : _("ago") + .remove('about ') + remaining_or_ago = is_upcoming ? _('remaining') : _('ago') "#{content} #{remaining_or_ago}".html_safe elsif start_date&.past? diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 9ef93b2387f..94e8b174f0f 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -2,6 +2,9 @@ class PipelineEntity < Grape::Entity include RequestAwareEntity + include Gitlab::Utils::StrongMemoize + + delegate :name, :failure_reason, to: :presented_pipeline expose :id expose :user, using: UserEntity @@ -36,6 +39,7 @@ class PipelineEntity < Grape::Entity expose :ordered_stages, as: :stages, using: StageEntity expose :duration expose :finished_at + expose :name end expose :merge_request, if: -> (*) { has_presentable_merge_request? }, with: MergeRequestForPipelineEntity do |pipeline| @@ -59,13 +63,11 @@ class PipelineEntity < Grape::Entity end expose :commit, using: CommitEntity + expose :merge_request_event_type, if: -> (pipeline, _) { pipeline.merge_request_event? } expose :source_sha, if: -> (pipeline, _) { pipeline.merge_request_pipeline? } expose :target_sha, if: -> (pipeline, _) { pipeline.merge_request_pipeline? } expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? } - - expose :failure_reason, if: -> (pipeline, _) { pipeline.failure_reason? } do |pipeline| - pipeline.present.failure_reason - end + expose :failure_reason, if: -> (pipeline, _) { pipeline.failure_reason? } expose :retry_path, if: -> (*) { can_retry? } do |pipeline| retry_project_pipeline_path(pipeline.project, pipeline) @@ -97,4 +99,10 @@ class PipelineEntity < Grape::Entity def detailed_status pipeline.detailed_status(request.current_user) end + + def presented_pipeline + strong_memoize(:presented_pipeline) do + pipeline.present + end + end end diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb index 73f3408a240..22ade0b0658 100644 --- a/app/services/audit_event_service.rb +++ b/app/services/audit_event_service.rb @@ -21,6 +21,10 @@ class AuditEventService log_security_event_to_database end + def log_security_event_to_file + file_logger.info(base_payload.merge(formatted_details)) + end + private def base_payload @@ -39,10 +43,6 @@ class AuditEventService @details.merge(@details.slice(:from, :to).transform_values(&:to_s)) end - def log_security_event_to_file - file_logger.info(base_payload.merge(formatted_details)) - end - def log_security_event_to_database SecurityEvent.create(base_payload.merge(details: @details)) end diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index 0a069320936..9c210f3a1f5 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -2,7 +2,7 @@ module Auth class ContainerRegistryAuthenticationService < BaseService - AUDIENCE = 'container_registry'.freeze + AUDIENCE = 'container_registry' def execute(authentication_abilities:) @authentication_abilities = authentication_abilities @@ -124,13 +124,21 @@ module Auth build_can_pull?(requested_project) || user_can_pull?(requested_project) || deploy_token_can_pull?(requested_project) when 'push' build_can_push?(requested_project) || user_can_push?(requested_project) - when '*', 'delete' + when 'delete' + build_can_delete?(requested_project) || user_can_admin?(requested_project) + when '*' user_can_admin?(requested_project) else false end end + def build_can_delete?(requested_project) + # Build can delete only from the project from which it originates + has_authentication_ability?(:build_destroy_container_image) && + requested_project == project + end + def registry Gitlab.config.registry end diff --git a/app/services/auto_merge_service.rb b/app/services/auto_merge_service.rb index 95bf2db2018..053c6d71fbb 100644 --- a/app/services/auto_merge_service.rb +++ b/app/services/auto_merge_service.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class AutoMergeService < BaseService - STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS = 'merge_when_pipeline_succeeds'.freeze + STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS = 'merge_when_pipeline_succeeds' STRATEGIES = [STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS].freeze class << self diff --git a/app/services/clusters/applications/check_installation_progress_service.rb b/app/services/clusters/applications/check_installation_progress_service.rb index 65d08966802..1ce6e0c1cb0 100644 --- a/app/services/clusters/applications/check_installation_progress_service.rb +++ b/app/services/clusters/applications/check_installation_progress_service.rb @@ -33,6 +33,10 @@ module Clusters def timed_out? Time.now.utc - app.updated_at.utc > ClusterWaitForAppInstallationWorker::TIMEOUT end + + def remove_installation_pod + helm_api.delete_pod!(pod_name) + end end end end diff --git a/app/services/clusters/applications/check_uninstall_progress_service.rb b/app/services/clusters/applications/check_uninstall_progress_service.rb index 6a618d61c4f..fe9c488bdfd 100644 --- a/app/services/clusters/applications/check_uninstall_progress_service.rb +++ b/app/services/clusters/applications/check_uninstall_progress_service.rb @@ -15,7 +15,7 @@ module Clusters rescue StandardError => e app.make_errored!(_('Application uninstalled but failed to destroy: %{error_message}') % { error_message: e.message }) ensure - remove_installation_pod + remove_uninstallation_pod end def check_timeout @@ -33,6 +33,10 @@ module Clusters def timed_out? Time.now.utc - app.updated_at.utc > WaitForUninstallAppWorker::TIMEOUT end + + def remove_uninstallation_pod + helm_api.delete_pod!(pod_name) + end end end end diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb index e7464fd9d5f..39266a6c961 100644 --- a/app/services/event_create_service.rb +++ b/app/services/event_create_service.rb @@ -95,16 +95,23 @@ class EventCreateService private def create_record_event(record, current_user, status) - create_event(record.project, current_user, status, target_id: record.id, target_type: record.class.name) + create_event(record.resource_parent, current_user, status, target_id: record.id, target_type: record.class.name) end - def create_event(project, current_user, status, attributes = {}) + def create_event(resource_parent, current_user, status, attributes = {}) attributes.reverse_merge!( - project: project, action: status, author_id: current_user.id ) + resource_parent_attr = case resource_parent + when Project + :project + when Group + :group + end + attributes[resource_parent_attr] = resource_parent if resource_parent_attr + Event.create!(attributes) end end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 2ab6e88599f..3555864f834 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -221,6 +221,7 @@ class IssuableBaseService < BaseService # We have to perform this check before saving the issuable as Rails resets # the changed fields upon calling #save. update_project_counters = issuable.project && update_project_counter_caches?(issuable) + ensure_milestone_available(issuable) if issuable.with_transaction_returning_status { issuable.save(touch: should_touch) } # We do not touch as it will affect a update on updated_at field diff --git a/app/services/merge_requests/rebase_service.rb b/app/services/merge_requests/rebase_service.rb index 27c16ba1777..4d36dd4feae 100644 --- a/app/services/merge_requests/rebase_service.rb +++ b/app/services/merge_requests/rebase_service.rb @@ -2,7 +2,7 @@ module MergeRequests class RebaseService < MergeRequests::WorkingCopyBaseService - REBASE_ERROR = 'Rebase failed. Please rebase locally'.freeze + REBASE_ERROR = 'Rebase failed. Please rebase locally' def execute(merge_request) @merge_request = merge_request diff --git a/app/services/milestones/find_or_create_service.rb b/app/services/milestones/find_or_create_service.rb new file mode 100644 index 00000000000..881011e5106 --- /dev/null +++ b/app/services/milestones/find_or_create_service.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Milestones + class FindOrCreateService + attr_accessor :project, :current_user, :params + + def initialize(project, user, params = {}) + @project, @current_user, @params = project, user, params.dup + end + + def execute + find_milestone || create_milestone + end + + private + + # rubocop: disable CodeReuse/ActiveRecord + def find_milestone + groups = project.group&.self_and_ancestors_ids + Milestone.for_projects_and_groups([project.id], groups).find_by(title: params["title"]) + end + # rubocop: enable CodeReuse/ActiveRecord + + def create_milestone + return unless current_user.can?(:admin_milestone, project) + + new_milestone if new_milestone.persisted? + end + + def new_milestone + @new_milestone ||= CreateService.new(project, current_user, params).execute + end + end +end diff --git a/app/services/milestones/transfer_service.rb b/app/services/milestones/transfer_service.rb new file mode 100644 index 00000000000..1efbfed4853 --- /dev/null +++ b/app/services/milestones/transfer_service.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +# Milestones::TransferService class +# +# Used for recreating the missing group milestones at project level when +# transferring a project to a new namespace +# +module Milestones + class TransferService + attr_reader :current_user, :old_group, :project + + def initialize(current_user, old_group, project) + @current_user = current_user + @old_group = old_group + @project = project + end + + def execute + return unless old_group.present? + + Milestone.transaction do + milestones_to_transfer.find_each do |milestone| + new_milestone = find_or_create_milestone(milestone) + + update_issues_milestone(milestone.id, new_milestone&.id) + update_merge_requests_milestone(milestone.id, new_milestone&.id) + end + end + end + + private + + # rubocop: disable CodeReuse/ActiveRecord + def milestones_to_transfer + Milestone.from_union([ + group_milestones_applied_to_issues, + group_milestones_applied_to_merge_requests + ]) + .reorder(nil) + .distinct + end + # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable CodeReuse/ActiveRecord + def group_milestones_applied_to_issues + Milestone.joins(:issues) + .where( + issues: { project_id: project.id }, + group_id: old_group.id + ) + end + # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable CodeReuse/ActiveRecord + def group_milestones_applied_to_merge_requests + Milestone.joins(:merge_requests) + .where( + merge_requests: { target_project_id: project.id }, + group_id: old_group.id + ) + end + # rubocop: enable CodeReuse/ActiveRecord + + def find_or_create_milestone(milestone) + params = milestone.attributes.slice('title', 'description', 'start_date', 'due_date') + + FindOrCreateService.new(project, current_user, params).execute + end + + # rubocop: disable CodeReuse/ActiveRecord + def update_issues_milestone(old_milestone_id, new_milestone_id) + Issue.where(project: project, milestone_id: old_milestone_id) + .update_all(milestone_id: new_milestone_id) + end + # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable CodeReuse/ActiveRecord + def update_merge_requests_milestone(old_milestone_id, new_milestone_id) + MergeRequest.where(project: project, milestone_id: old_milestone_id) + .update_all(milestone_id: new_milestone_id) + end + # rubocop: enable CodeReuse/ActiveRecord + end +end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 5b8c1288854..a4243b7bc5e 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -285,7 +285,7 @@ class NotificationService recipients = NotificationRecipientService.build_new_note_recipients(note) recipients.each do |recipient| - mailer.send(notify_method, recipient.user.id, note.id).deliver_later + mailer.send(notify_method, recipient.user.id, note.id, recipient.reason).deliver_later end end diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 5893b8eedff..108c4a79cde 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -6,7 +6,7 @@ module Projects DestroyError = Class.new(StandardError) - DELETED_FLAG = '+deleted'.freeze + DELETED_FLAG = '+deleted' REPO_REMOVAL_DELAY = 5.minutes.to_i def async_execute diff --git a/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb b/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb index 9b72480d18b..5ef7e03ea02 100644 --- a/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb +++ b/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb @@ -5,7 +5,7 @@ module Projects module LfsPointers class LfsDownloadLinkListService < BaseService - DOWNLOAD_ACTION = 'download'.freeze + DOWNLOAD_ACTION = 'download' DownloadLinksError = Class.new(StandardError) DownloadLinkNotFound = Class.new(StandardError) diff --git a/app/services/projects/lfs_pointers/lfs_list_service.rb b/app/services/projects/lfs_pointers/lfs_list_service.rb index 22160017f4f..a07fa93a279 100644 --- a/app/services/projects/lfs_pointers/lfs_list_service.rb +++ b/app/services/projects/lfs_pointers/lfs_list_service.rb @@ -4,7 +4,7 @@ module Projects module LfsPointers class LfsListService < BaseService - REV = 'HEAD'.freeze + REV = 'HEAD' # Retrieve all lfs blob pointers and returns a hash # with the structure { lfs_file_oid => lfs_file_size } diff --git a/app/services/projects/lfs_pointers/lfs_object_download_list_service.rb b/app/services/projects/lfs_pointers/lfs_object_download_list_service.rb index 5ba0f50f2ff..4224b56c42c 100644 --- a/app/services/projects/lfs_pointers/lfs_object_download_list_service.rb +++ b/app/services/projects/lfs_pointers/lfs_object_download_list_service.rb @@ -8,9 +8,9 @@ module Projects class LfsObjectDownloadListService < BaseService include Gitlab::Utils::StrongMemoize - HEAD_REV = 'HEAD'.freeze + HEAD_REV = 'HEAD' LFS_ENDPOINT_PATTERN = /^\t?url\s*=\s*(.+)$/.freeze - LFS_BATCH_API_ENDPOINT = '/info/lfs/objects/batch'.freeze + LFS_BATCH_API_ENDPOINT = '/info/lfs/objects/batch' LfsObjectDownloadListError = Class.new(StandardError) diff --git a/app/services/projects/open_issues_count_service.rb b/app/services/projects/open_issues_count_service.rb index ee9884e9042..bc8f7f342f7 100644 --- a/app/services/projects/open_issues_count_service.rb +++ b/app/services/projects/open_issues_count_service.rb @@ -7,8 +7,8 @@ module Projects include Gitlab::Utils::StrongMemoize # Cache keys used to store issues count - PUBLIC_COUNT_KEY = 'public_open_issues_count'.freeze - TOTAL_COUNT_KEY = 'total_open_issues_count'.freeze + PUBLIC_COUNT_KEY = 'public_open_issues_count' + TOTAL_COUNT_KEY = 'total_open_issues_count' def initialize(project, user = nil) @user = user diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 233dcf37e35..078a751025f 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -72,6 +72,9 @@ module Projects # Move missing group labels to project Labels::TransferService.new(current_user, @old_group, project).execute + # Move missing group milestones + Milestones::TransferService.new(current_user, @old_group, project).execute + # Move uploads move_project_uploads(project) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index 5caeb4cfa5f..fa7a4f0ed82 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -7,11 +7,11 @@ module Projects BLOCK_SIZE = 32.kilobytes MAX_SIZE = 1.terabyte - PUBLIC_DIR = 'public'.freeze + PUBLIC_DIR = 'public' # this has to be invalid group name, # as it shares the namespace with groups - TMP_EXTRACT_PATH = '@pages.tmp'.freeze + TMP_EXTRACT_PATH = '@pages.tmp' attr_reader :build diff --git a/app/services/releases/concerns.rb b/app/services/releases/concerns.rb index 618d96717b8..b5412e97284 100644 --- a/app/services/releases/concerns.rb +++ b/app/services/releases/concerns.rb @@ -47,6 +47,27 @@ module Releases project.repository end end + + def milestone + return unless params[:milestone] + + strong_memoize(:milestone) do + MilestonesFinder.new( + project: project, + current_user: current_user, + project_ids: Array(project.id), + title: params[:milestone] + ).execute.first + end + end + + def inexistent_milestone? + params[:milestone] && !params[:milestone].empty? && !milestone + end + + def param_for_milestone_title_provided? + params[:milestone].present? || params[:milestone]&.empty? + end end end end diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb index 5b13ac631ba..c91d43084d3 100644 --- a/app/services/releases/create_service.rb +++ b/app/services/releases/create_service.rb @@ -7,6 +7,7 @@ module Releases def execute return error('Access Denied', 403) unless allowed? return error('Release already exists', 409) if release + return error('Milestone does not exist', 400) if inexistent_milestone? tag = ensure_tag @@ -59,7 +60,8 @@ module Releases tag: tag.name, sha: tag.dereferenced_target.sha, released_at: released_at, - links_attributes: params.dig(:assets, 'links') || [] + links_attributes: params.dig(:assets, 'links') || [], + milestone: milestone ) end end diff --git a/app/services/releases/update_service.rb b/app/services/releases/update_service.rb index fabfa398c59..70acc68f747 100644 --- a/app/services/releases/update_service.rb +++ b/app/services/releases/update_service.rb @@ -9,6 +9,9 @@ module Releases return error('Release does not exist', 404) unless release return error('Access Denied', 403) unless allowed? return error('params is empty', 400) if empty_params? + return error('Milestone does not exist', 400) if inexistent_milestone? + + params[:milestone] = milestone if param_for_milestone_title_provided? if release.update(params) success(tag: existing_tag, release: release) diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb index 4f10f220298..415a02ab337 100644 --- a/app/services/submit_usage_ping_service.rb +++ b/app/services/submit_usage_ping_service.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class SubmitUsagePingService - URL = 'https://version.gitlab.com/usage_data'.freeze + URL = 'https://version.gitlab.com/usage_data' METRICS = %w[leader_issues instance_issues percentage_issues leader_notes instance_notes percentage_notes leader_milestones instance_milestones percentage_milestones diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 858e04f43b2..34260d12a62 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -74,9 +74,11 @@ class SystemHooksService when ProjectMember return "user_add_to_team" if event == :create return "user_remove_from_team" if event == :destroy + return "user_update_for_team" if event == :update when GroupMember return 'user_add_to_group' if event == :create return 'user_remove_from_group' if event == :destroy + return 'user_update_for_group' if event == :update else "#{model.class.name.downcase}_#{event}" end diff --git a/app/services/wikis/create_attachment_service.rb b/app/services/wikis/create_attachment_service.rb index df31ad7c8ea..6ef6cbc3c12 100644 --- a/app/services/wikis/create_attachment_service.rb +++ b/app/services/wikis/create_attachment_service.rb @@ -2,7 +2,7 @@ module Wikis class CreateAttachmentService < Files::CreateService - ATTACHMENT_PATH = 'uploads'.freeze + ATTACHMENT_PATH = 'uploads' MAX_FILENAME_LENGTH = 255 delegate :wiki, to: :project diff --git a/app/uploaders/object_storage.rb b/app/uploaders/object_storage.rb index 0a44d60778d..499807d1438 100644 --- a/app/uploaders/object_storage.rb +++ b/app/uploaders/object_storage.rb @@ -23,7 +23,7 @@ module ObjectStorage end end - TMP_UPLOAD_PATH = 'tmp/uploads'.freeze + TMP_UPLOAD_PATH = 'tmp/uploads' module Store LOCAL = 1 diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb index bb445499cee..f292730441c 100644 --- a/app/validators/addressable_url_validator.rb +++ b/app/validators/addressable_url_validator.rb @@ -42,6 +42,11 @@ class AddressableUrlValidator < ActiveModel::EachValidator attr_reader :record + # By default, we avoid checking the dns rebinding protection + # when saving/updating a record. Sometimes, the url + # is not resolvable at that point, and some automated + # tasks that uses that url won't work. + # See https://gitlab.com/gitlab-org/gitlab-ce/issues/66723 BLOCKER_VALIDATE_OPTIONS = { schemes: %w(http https), ports: [], @@ -49,7 +54,8 @@ class AddressableUrlValidator < ActiveModel::EachValidator allow_local_network: true, ascii_only: false, enforce_user: false, - enforce_sanitization: false + enforce_sanitization: false, + dns_rebind_protection: false }.freeze DEFAULT_OPTIONS = BLOCKER_VALIDATE_OPTIONS.merge({ diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml index a0a00ac5d96..1001a69b787 100644 --- a/app/views/admin/background_jobs/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml @@ -1,10 +1,8 @@ -- @no_container = true - page_title "Background Jobs" -%div{ class: container_class } - %h3.page-title Background Jobs - %p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing +%h3.page-title Background Jobs +%p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing - %hr - .card - %iframe{ src: sidekiq_path, width: '100%', height: 970, style: "border: 0" } +%hr +.card + %iframe{ src: sidekiq_path, width: '100%', height: 970, style: "border: 0" } diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index c29ecb43fe6..8aca61efe7b 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -1,167 +1,165 @@ -- @no_container = true - breadcrumb_title "Dashboard" -%div{ class: container_class } - = render_if_exists 'admin/licenses/breakdown', license: @license += render_if_exists 'admin/licenses/breakdown', license: @license - .admin-dashboard.prepend-top-default - .row - .col-sm-4 - .info-well.dark-well - .well-segment.well-centered - = link_to admin_projects_path do - %h3.text-center - Projects: - = approximate_count_with_delimiters(@counts, Project) - %hr - = link_to('New project', new_project_path, class: "btn btn-success") - .col-sm-4 - .info-well.dark-well - .well-segment.well-centered - = link_to admin_users_path do - %h3.text-center - Users: - = approximate_count_with_delimiters(@counts, User) - %hr - .btn-group.d-flex{ role: 'group' } - = link_to 'New user', new_admin_user_path, class: "btn btn-success" - = render_if_exists 'admin/dashboard/users_statistics' - .col-sm-4 - .info-well.dark-well - .well-segment.well-centered - = link_to admin_groups_path do - %h3.text-center - Groups: - = approximate_count_with_delimiters(@counts, Group) - %hr - = link_to 'New group', new_admin_group_path, class: "btn btn-success" - .row - .col-md-4 - .info-well - .well-segment.admin-well.admin-well-statistics - %h4 Statistics - %p - Forks - %span.light.float-right - = approximate_fork_count_with_delimiters(@counts) - %p - Issues - %span.light.float-right - = approximate_count_with_delimiters(@counts, Issue) - %p - Merge Requests - %span.light.float-right - = approximate_count_with_delimiters(@counts, MergeRequest) - %p - Notes - %span.light.float-right - = approximate_count_with_delimiters(@counts, Note) - %p - Snippets - %span.light.float-right - = approximate_count_with_delimiters(@counts, Snippet) - %p - SSH Keys - %span.light.float-right - = approximate_count_with_delimiters(@counts, Key) - %p - Milestones - %span.light.float-right - = approximate_count_with_delimiters(@counts, Milestone) - %p - Active Users - %span.light.float-right - = number_with_delimiter(User.active.count) - .col-md-4 - .info-well - .well-segment.admin-well.admin-well-features - %h4 Features - = feature_entry(_('Sign up'), href: admin_application_settings_path(anchor: 'js-signup-settings')) - = feature_entry(_('LDAP'), enabled: Gitlab.config.ldap.enabled) - = feature_entry(_('Gravatar'), href: admin_application_settings_path(anchor: 'js-account-settings'), enabled: gravatar_enabled?) - = feature_entry(_('OmniAuth'), href: admin_application_settings_path(anchor: 'js-signin-settings'), enabled: Gitlab::Auth.omniauth_enabled?) - = feature_entry(_('Reply by email'), enabled: Gitlab::IncomingEmail.enabled?) +.admin-dashboard.prepend-top-default + .row + .col-sm-4 + .info-well.dark-well + .well-segment.well-centered + = link_to admin_projects_path do + %h3.text-center + Projects: + = approximate_count_with_delimiters(@counts, Project) + %hr + = link_to('New project', new_project_path, class: "btn btn-success") + .col-sm-4 + .info-well.dark-well + .well-segment.well-centered + = link_to admin_users_path do + %h3.text-center + Users: + = approximate_count_with_delimiters(@counts, User) + %hr + .btn-group.d-flex{ role: 'group' } + = link_to 'New user', new_admin_user_path, class: "btn btn-success" + = render_if_exists 'admin/dashboard/users_statistics' + .col-sm-4 + .info-well.dark-well + .well-segment.well-centered + = link_to admin_groups_path do + %h3.text-center + Groups: + = approximate_count_with_delimiters(@counts, Group) + %hr + = link_to 'New group', new_admin_group_path, class: "btn btn-success" + .row + .col-md-4 + .info-well + .well-segment.admin-well.admin-well-statistics + %h4 Statistics + %p + Forks + %span.light.float-right + = approximate_fork_count_with_delimiters(@counts) + %p + Issues + %span.light.float-right + = approximate_count_with_delimiters(@counts, Issue) + %p + Merge Requests + %span.light.float-right + = approximate_count_with_delimiters(@counts, MergeRequest) + %p + Notes + %span.light.float-right + = approximate_count_with_delimiters(@counts, Note) + %p + Snippets + %span.light.float-right + = approximate_count_with_delimiters(@counts, Snippet) + %p + SSH Keys + %span.light.float-right + = approximate_count_with_delimiters(@counts, Key) + %p + Milestones + %span.light.float-right + = approximate_count_with_delimiters(@counts, Milestone) + %p + Active Users + %span.light.float-right + = number_with_delimiter(User.active.count) + .col-md-4 + .info-well + .well-segment.admin-well.admin-well-features + %h4 Features + = feature_entry(_('Sign up'), href: admin_application_settings_path(anchor: 'js-signup-settings')) + = feature_entry(_('LDAP'), enabled: Gitlab.config.ldap.enabled) + = feature_entry(_('Gravatar'), href: admin_application_settings_path(anchor: 'js-account-settings'), enabled: gravatar_enabled?) + = feature_entry(_('OmniAuth'), href: admin_application_settings_path(anchor: 'js-signin-settings'), enabled: Gitlab::Auth.omniauth_enabled?) + = feature_entry(_('Reply by email'), enabled: Gitlab::IncomingEmail.enabled?) - = render_if_exists 'admin/dashboard/elastic_and_geo' + = render_if_exists 'admin/dashboard/elastic_and_geo' - = feature_entry(_('Container Registry'), href: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'), enabled: Gitlab.config.registry.enabled) - = feature_entry(_('Gitlab Pages'), href: help_instance_configuration_url, enabled: Gitlab.config.pages.enabled) - = feature_entry(_('Shared Runners'), href: admin_runners_path, enabled: Gitlab.config.gitlab_ci.shared_runners_enabled) - .col-md-4 - .info-well - .well-segment.admin-well - %h4 - Components - - if Gitlab::CurrentSettings.version_check_enabled - .float-right - = version_status_badge - %p - %a{ href: admin_application_settings_path } - GitLab + = feature_entry(_('Container Registry'), href: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'), enabled: Gitlab.config.registry.enabled) + = feature_entry(_('Gitlab Pages'), href: help_instance_configuration_url, enabled: Gitlab.config.pages.enabled) + = feature_entry(_('Shared Runners'), href: admin_runners_path, enabled: Gitlab.config.gitlab_ci.shared_runners_enabled) + .col-md-4 + .info-well + .well-segment.admin-well + %h4 + Components + - if Gitlab::CurrentSettings.version_check_enabled + .float-right + = version_status_badge + %p + %a{ href: admin_application_settings_path } + GitLab + %span.float-right + = Gitlab::VERSION + = "(#{Gitlab.revision})" + %p + GitLab Shell + %span.float-right + = Gitlab::Shell.new.version + %p + GitLab Workhorse + %span.float-right + = gitlab_workhorse_version + %p + GitLab API + %span.float-right + = API::API::version + - if Gitlab.config.pages.enabled + %p + GitLab Pages %span.float-right - = Gitlab::VERSION - = "(#{Gitlab.revision})" - %p - GitLab Shell - %span.float-right - = Gitlab::Shell.new.version - %p - GitLab Workhorse - %span.float-right - = gitlab_workhorse_version - %p - GitLab API - %span.float-right - = API::API::version - - if Gitlab.config.pages.enabled - %p - GitLab Pages - %span.float-right - = Gitlab::Pages::VERSION + = Gitlab::Pages::VERSION - = render_if_exists 'admin/dashboard/geo' + = render_if_exists 'admin/dashboard/geo' - %p - Ruby - %span.float-right - #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} - %p - Rails - %span.float-right - #{Rails::VERSION::STRING} - %p - = Gitlab::Database.human_adapter_name - %span.float-right - = Gitlab::Database.version - %p - = link_to "Gitaly Servers", admin_gitaly_servers_path - .row - .col-md-4 - .info-well - .well-segment.admin-well - %h4 Latest projects - - @projects.each do |project| - %p - = link_to project.full_name, admin_project_path(project), class: 'str-truncated-60' - %span.light.float-right - #{time_ago_with_tooltip(project.created_at)} - .col-md-4 - .info-well - .well-segment.admin-well - %h4 Latest users - - @users.each do |user| - %p - = link_to [:admin, user], class: 'str-truncated-60' do - = user.name - %span.light.float-right - #{time_ago_with_tooltip(user.created_at)} - .col-md-4 - .info-well - .well-segment.admin-well - %h4 Latest groups - - @groups.each do |group| - %p - = link_to [:admin, group], class: 'str-truncated-60' do - = group.full_name - %span.light.float-right - #{time_ago_with_tooltip(group.created_at)} + %p + Ruby + %span.float-right + #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} + %p + Rails + %span.float-right + #{Rails::VERSION::STRING} + %p + = Gitlab::Database.human_adapter_name + %span.float-right + = Gitlab::Database.version + %p + = link_to "Gitaly Servers", admin_gitaly_servers_path + .row + .col-md-4 + .info-well + .well-segment.admin-well + %h4 Latest projects + - @projects.each do |project| + %p + = link_to project.full_name, admin_project_path(project), class: 'str-truncated-60' + %span.light.float-right + #{time_ago_with_tooltip(project.created_at)} + .col-md-4 + .info-well + .well-segment.admin-well + %h4 Latest users + - @users.each do |user| + %p + = link_to [:admin, user], class: 'str-truncated-60' do + = user.name + %span.light.float-right + #{time_ago_with_tooltip(user.created_at)} + .col-md-4 + .info-well + .well-segment.admin-well + %h4 Latest groups + - @groups.each do |group| + %p + = link_to [:admin, group], class: 'str-truncated-60' do + = group.full_name + %span.light.float-right + #{time_ago_with_tooltip(group.created_at)} diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index cb833ffd9ac..434b6e3a37e 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -1,20 +1,18 @@ -- @no_container = true - page_title _("Groups") -%div{ class: container_class } - .top-area - .prepend-top-default.append-bottom-default - = form_tag admin_groups_path, method: :get, class: 'js-search-form' do |f| - = hidden_field_tag :sort, @sort - .search-holder - - project_name = params[:name].present? ? params[:name] : nil - .search-field-holder - = search_field_tag :name, project_name, class: "form-control search-text-input js-search-input", autofocus: true, spellcheck: false, placeholder: 'Search by name' - = icon("search", class: "search-icon") - = render "shared/groups/dropdown", options_hash: admin_groups_sort_options_hash - = link_to new_admin_group_path, class: "btn btn-success" do - = _('New group') - %ul.content-list - = render @groups +.top-area + .prepend-top-default.append-bottom-default + = form_tag admin_groups_path, method: :get, class: 'js-search-form' do |f| + = hidden_field_tag :sort, @sort + .search-holder + - project_name = params[:name].present? ? params[:name] : nil + .search-field-holder + = search_field_tag :name, project_name, class: "form-control search-text-input js-search-input", autofocus: true, spellcheck: false, placeholder: 'Search by name' + = icon("search", class: "search-icon") + = render "shared/groups/dropdown", options_hash: admin_groups_sort_options_hash + = link_to new_admin_group_path, class: "btn btn-success" do + = _('New group') +%ul.content-list + = render @groups - = paginate @groups, theme: "gitlab" += paginate @groups, theme: "gitlab" diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml index ac56e354a4d..587bfba8d47 100644 --- a/app/views/admin/health_check/show.html.haml +++ b/app/views/admin/health_check/show.html.haml @@ -1,41 +1,39 @@ -- @no_container = true - page_title _('Health Check') - no_errors = @errors.blank? -%div{ class: container_class } - %h3.page-title= page_title - .bs-callout.clearfix - .float-left - %p - #{ s_('HealthCheck|Access token is') } - %code#health-check-token= Gitlab::CurrentSettings.health_check_access_token - .prepend-top-10 - = button_to _("Reset health check access token"), reset_health_check_token_admin_application_settings_path, - method: :put, class: 'btn btn-default', - data: { confirm: _('Are you sure you want to reset the health check token?') } - %p.light - #{ _('Health information can be retrieved from the following endpoints. More information is available') } - = link_to s_('More information is available|here'), help_page_path('user/admin_area/monitoring/health_check') - %ul - %li - %code= readiness_url(token: Gitlab::CurrentSettings.health_check_access_token) - %li - %code= liveness_url(token: Gitlab::CurrentSettings.health_check_access_token) - %li - %code= metrics_url(token: Gitlab::CurrentSettings.health_check_access_token) - = render_if_exists 'admin/health_check/health_check_url' - %hr - .card - .card-header - Current Status: - - if no_errors - = icon('circle', class: 'cgreen') - #{ s_('HealthCheck|Healthy') } - - else - = icon('warning', class: 'cred') - #{ s_('HealthCheck|Unhealthy') } - .card-body - - if no_errors - #{ s_('HealthCheck|No Health Problems Detected') } - - else - = @errors +%h3.page-title= page_title +.bs-callout.clearfix + .float-left + %p + #{ s_('HealthCheck|Access token is') } + %code#health-check-token= Gitlab::CurrentSettings.health_check_access_token + .prepend-top-10 + = button_to _("Reset health check access token"), reset_health_check_token_admin_application_settings_path, + method: :put, class: 'btn btn-default', + data: { confirm: _('Are you sure you want to reset the health check token?') } +%p.light + #{ _('Health information can be retrieved from the following endpoints. More information is available') } + = link_to s_('More information is available|here'), help_page_path('user/admin_area/monitoring/health_check') + %ul + %li + %code= readiness_url(token: Gitlab::CurrentSettings.health_check_access_token) + %li + %code= liveness_url(token: Gitlab::CurrentSettings.health_check_access_token) + %li + %code= metrics_url(token: Gitlab::CurrentSettings.health_check_access_token) + = render_if_exists 'admin/health_check/health_check_url' +%hr +.card + .card-header + Current Status: + - if no_errors + = icon('circle', class: 'cgreen') + #{ s_('HealthCheck|Healthy') } + - else + = icon('warning', class: 'cred') + #{ s_('HealthCheck|Unhealthy') } + .card-body + - if no_errors + #{ s_('HealthCheck|No Health Problems Detected') } + - else + = @errors diff --git a/app/views/admin/jobs/index.html.haml b/app/views/admin/jobs/index.html.haml index 4e3e2f7a475..f1bdd52b399 100644 --- a/app/views/admin/jobs/index.html.haml +++ b/app/views/admin/jobs/index.html.haml @@ -1,22 +1,19 @@ - breadcrumb_title "Jobs" -- @no_container = true -%div{ class: container_class } +.top-area.scrolling-tabs-container.inner-page-scroll-tabs + - build_path_proc = ->(scope) { admin_jobs_path(scope: scope) } + = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope - .top-area.scrolling-tabs-container.inner-page-scroll-tabs - - build_path_proc = ->(scope) { admin_jobs_path(scope: scope) } - = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope + - if @all_builds.running_or_pending.any? + #stop-jobs-modal + .nav-controls + %button#stop-jobs-button.btn.btn-danger{ data: { toggle: 'modal', + target: '#stop-jobs-modal', + url: cancel_all_admin_jobs_path } } + = s_('AdminArea|Stop all jobs') - - if @all_builds.running_or_pending.any? - #stop-jobs-modal - .nav-controls - %button#stop-jobs-button.btn.btn-danger{ data: { toggle: 'modal', - target: '#stop-jobs-modal', - url: cancel_all_admin_jobs_path } } - = s_('AdminArea|Stop all jobs') +.row-content-block.second-block + #{(@scope || 'all').capitalize} jobs - .row-content-block.second-block - #{(@scope || 'all').capitalize} jobs - - %ul.content-list.builds-content-list.admin-builds-table - = render "projects/jobs/table", builds: @builds, admin: true +%ul.content-list.builds-content-list.admin-builds-table + = render "projects/jobs/table", builds: @builds, admin: true diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index e4c0382a437..eb93f645ea6 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -1,26 +1,24 @@ -- @no_container = true - page_title "Logs" -%div{ class: container_class } - %ul.nav-links.log-tabs.nav.nav-tabs - - @loggers.each do |klass| - %li.nav-item - = link_to klass.file_name, "##{klass.file_name_noext}", data: { toggle: 'tab' }, class: "#{active_when(klass == @loggers.first)} nav-link" - .row-content-block - To prevent performance issues admin logs output the last 2000 lines - .tab-content - - @loggers.each do |klass| - .tab-pane{ class: active_when(klass == @loggers.first), id: klass.file_name_noext } - .file-holder#README - .js-file-title.file-title - %i.fa.fa-file - = klass.file_name - .float-right - = link_to '#', class: 'log-bottom' do - %i.fa.fa-arrow-down - Scroll down - .file-content.logs - %ol - - klass.read_latest.each do |line| - %li - %p= line +%ul.nav-links.log-tabs.nav.nav-tabs + - @loggers.each do |klass| + %li.nav-item + = link_to klass.file_name, "##{klass.file_name_noext}", data: { toggle: 'tab' }, class: "#{active_when(klass == @loggers.first)} nav-link" +.row-content-block + To prevent performance issues admin logs output the last 2000 lines +.tab-content + - @loggers.each do |klass| + .tab-pane{ class: active_when(klass == @loggers.first), id: klass.file_name_noext } + .file-holder#README + .js-file-title.file-title + %i.fa.fa-file + = klass.file_name + .float-right + = link_to '#', class: 'log-bottom' do + %i.fa.fa-arrow-down + Scroll down + .file-content.logs + %ol + - klass.read_latest.each do |line| + %li + %p= line diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index b88b760536d..7e03eb4f075 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -1,44 +1,41 @@ -- @no_container = true - page_title "Projects" - params[:visibility_level] ||= [] +.top-area.scrolling-tabs-container.inner-page-scroll-tabs + .prepend-top-default + .search-holder + = render 'shared/projects/search_form', autofocus: true, icon: true, admin_view: true + .dropdown + - toggle_text = 'Namespace' + - if params[:namespace_id].present? + = hidden_field_tag :namespace_id, params[:namespace_id] + - namespace = Namespace.find(params[:namespace_id]) + - toggle_text = "#{namespace.kind}: #{namespace.full_path}" + = dropdown_toggle(toggle_text, { toggle: 'dropdown', is_filter: 'true' }, { toggle_class: 'js-namespace-select large' }) + .dropdown-menu.dropdown-select.dropdown-menu-right + = dropdown_title('Namespaces') + = dropdown_filter("Search for Namespace") + = dropdown_content + = dropdown_loading + = render 'shared/projects/dropdown' + = link_to new_project_path, class: 'btn btn-success' do + New Project + = button_tag "Search", class: "btn btn-primary btn-search hide" -%div{ class: container_class } - .top-area.scrolling-tabs-container.inner-page-scroll-tabs - .prepend-top-default - .search-holder - = render 'shared/projects/search_form', autofocus: true, icon: true, admin_view: true - .dropdown - - toggle_text = 'Namespace' - - if params[:namespace_id].present? - = hidden_field_tag :namespace_id, params[:namespace_id] - - namespace = Namespace.find(params[:namespace_id]) - - toggle_text = "#{namespace.kind}: #{namespace.full_path}" - = dropdown_toggle(toggle_text, { toggle: 'dropdown', is_filter: 'true' }, { toggle_class: 'js-namespace-select large' }) - .dropdown-menu.dropdown-select.dropdown-menu-right - = dropdown_title('Namespaces') - = dropdown_filter("Search for Namespace") - = dropdown_content - = dropdown_loading - = render 'shared/projects/dropdown' - = link_to new_project_path, class: 'btn btn-success' do - New Project - = button_tag "Search", class: "btn btn-primary btn-search hide" + %ul.nav-links.nav.nav-tabs + - opts = params[:visibility_level].present? ? {} : { page: admin_projects_path } + = nav_link(opts) do + = link_to admin_projects_path do + All - %ul.nav-links.nav.nav-tabs - - opts = params[:visibility_level].present? ? {} : { page: admin_projects_path } - = nav_link(opts) do - = link_to admin_projects_path do - All + = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s) }) do + = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) do + Private + = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s) }) do + = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) do + Internal + = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s) }) do + = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do + Public - = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s) }) do - = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) do - Private - = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s) }) do - = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) do - Internal - = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s) }) do - = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do - Public - - = render 'projects' += render 'projects' diff --git a/app/views/admin/requests_profiles/index.html.haml b/app/views/admin/requests_profiles/index.html.haml index 86bfeef580c..efc16bb4d3b 100644 --- a/app/views/admin/requests_profiles/index.html.haml +++ b/app/views/admin/requests_profiles/index.html.haml @@ -1,26 +1,24 @@ -- @no_container = true - page_title 'Requests Profiles' -%div{ class: container_class } - %h3.page-title - = page_title +%h3.page-title + = page_title - .bs-callout.clearfix - Pass the header - %code X-Profile-Token: #{@profile_token} - to profile the request +.bs-callout.clearfix + Pass the header + %code X-Profile-Token: #{@profile_token} + to profile the request - - if @profiles.present? - .prepend-top-default - - @profiles.each do |path, profiles| - .card.card-small - .card-header - %code= path - %ul.content-list - - profiles.each do |profile| - %li - = link_to profile.time.to_s(:long) + ' ' + profile.profile_mode.capitalize, - admin_requests_profile_path(profile) - - else - %p - No profiles found +- if @profiles.present? + .prepend-top-default + - @profiles.each do |path, profiles| + .card.card-small + .card-header + %code= path + %ul.content-list + - profiles.each do |profile| + %li + = link_to profile.time.to_s(:long) + ' ' + profile.profile_mode.capitalize, + admin_requests_profile_path(profile) +- else + %p + No profiles found diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 5129f5d193b..76af4189b5b 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -1,96 +1,87 @@ - breadcrumb_title _('Runners') -- @no_container = true -%div{ class: container_class } - .row - .col-sm-6 - .bs-callout - %p - = (_"A 'Runner' is a process which runs a job. You can set up as many Runners as you need.") - %br - = _('Runners can be placed on separate users, servers, even on your local machine.') - %br +.row + .col-sm-6 + .bs-callout + %p + = (_"A 'Runner' is a process which runs a job. You can set up as many Runners as you need.") + %br + = _('Runners can be placed on separate users, servers, even on your local machine.') + %br - %div - %span= _('Each Runner can be in one of the following states:') - %ul - %li - %span.badge.badge-success shared - \- - = _('Runner runs jobs from all unassigned projects') - %li - %span.badge.badge-success group - \- - = _('Runner runs jobs from all unassigned projects in its group') - %li - %span.badge.badge-info specific - \- - = _('Runner runs jobs from assigned projects') - %li - %span.badge.badge-warning locked - \- - = _('Runner cannot be assigned to other projects') - %li - %span.badge.badge-danger paused - \- - = _('Runner will not receive any new jobs') + %div + %span= _('Each Runner can be in one of the following states:') + %ul + %li + %span.badge.badge-success shared + \- + = _('Runner runs jobs from all unassigned projects') + %li + %span.badge.badge-success group + \- + = _('Runner runs jobs from all unassigned projects in its group') + %li + %span.badge.badge-info specific + \- + = _('Runner runs jobs from assigned projects') + %li + %span.badge.badge-warning locked + \- + = _('Runner cannot be assigned to other projects') + %li + %span.badge.badge-danger paused + \- + = _('Runner will not receive any new jobs') - .col-sm-6 - .bs-callout - = render partial: 'ci/runner/how_to_setup_runner', - locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token, - type: 'shared', - reset_token_url: reset_registration_token_admin_application_settings_path } + .col-sm-6 + .bs-callout + = render partial: 'ci/runner/how_to_setup_runner', + locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token, + type: 'shared', + reset_token_url: reset_registration_token_admin_application_settings_path } - .row - .col-sm-9 - = form_tag admin_runners_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do - .filtered-search-wrapper - .filtered-search-box - = dropdown_tag(custom_icon('icon_history'), - options: { wrapper_class: 'filtered-search-history-dropdown-wrapper', - toggle_class: 'filtered-search-history-dropdown-toggle-button', - dropdown_class: 'filtered-search-history-dropdown', - content_class: 'filtered-search-history-dropdown-content', - title: _('Recent searches') }) do - .js-filtered-search-history-dropdown{ data: { full_path: admin_runners_path } } - .filtered-search-box-input-container.droplab-dropdown - .scroll-container - %ul.tokens-container.list-unstyled - %li.input-token - %input.form-control.filtered-search{ search_filter_input_options('runners') } - #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown - %ul{ data: { dropdown: true } } - %li.filter-dropdown-item{ data: { action: 'submit' } } - = button_tag class: %w[btn btn-link] do - = sprite_icon('search') - %span - = _('Press Enter or click to search') - %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } - %li.filter-dropdown-item - = button_tag class: %w[btn btn-link] do - -# Encapsulate static class name `{{icon}}` inside #{} to bypass - -# haml lint's ClassAttributeWithStaticValue - %svg - %use{ 'xlink:href': "#{'{{icon}}'}" } - %span.js-filter-hint - {{hint}} - %span.js-filter-tag.dropdown-light-content - {{tag}} - - #js-dropdown-admin-runner-status.filtered-search-input-dropdown-menu.dropdown-menu - %ul{ data: { dropdown: true } } - - Ci::Runner::AVAILABLE_STATUSES.each do |status| - %li.filter-dropdown-item{ data: { value: status } } - = button_tag class: %w[btn btn-link] do - = status.titleize +.row + .col-sm-9 + = form_tag admin_runners_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do + .filtered-search-wrapper + .filtered-search-box + = dropdown_tag(custom_icon('icon_history'), + options: { wrapper_class: 'filtered-search-history-dropdown-wrapper', + toggle_class: 'filtered-search-history-dropdown-toggle-button', + dropdown_class: 'filtered-search-history-dropdown', + content_class: 'filtered-search-history-dropdown-content', + title: _('Recent searches') }) do + .js-filtered-search-history-dropdown{ data: { full_path: admin_runners_path } } + .filtered-search-box-input-container.droplab-dropdown + .scroll-container + %ul.tokens-container.list-unstyled + %li.input-token + %input.form-control.filtered-search{ search_filter_input_options('runners') } + #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown + %ul{ data: { dropdown: true } } + %li.filter-dropdown-item{ data: { action: 'submit' } } + = button_tag class: %w[btn btn-link] do + = sprite_icon('search') + %span + = _('Press Enter or click to search') + %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } + %li.filter-dropdown-item + = button_tag class: %w[btn btn-link] do + -# Encapsulate static class name `{{icon}}` inside #{} to bypass + -# haml lint's ClassAttributeWithStaticValue + %svg + %use{ 'xlink:href': "#{'{{icon}}'}" } + %span.js-filter-hint + {{hint}} + %span.js-filter-tag.dropdown-light-content + {{tag}} - #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu - %ul{ data: { dropdown: true } } - - Ci::Runner::AVAILABLE_TYPES.each do |runner_type| - %li.filter-dropdown-item{ data: { value: runner_type } } - = button_tag class: %w[btn btn-link] do - = runner_type.titleize + #js-dropdown-admin-runner-status.filtered-search-input-dropdown-menu.dropdown-menu + %ul{ data: { dropdown: true } } + - Ci::Runner::AVAILABLE_STATUSES.each do |status| + %li.filter-dropdown-item{ data: { value: status } } + = button_tag class: %w[btn btn-link] do + = status.titleize #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu %ul{ data: { dropdown: true } } @@ -99,43 +90,50 @@ = button_tag class: %w[btn btn-link] do = runner_type.titleize - #js-dropdown-runner-tag.filtered-search-input-dropdown-menu.dropdown-menu - %ul{ data: { dropdown: true } } - %li.filter-dropdown-item{ data: { value: 'none' } } - %button.btn.btn-link - = _('No Tag') - %li.divider.droplab-item-ignore - %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } - %li.filter-dropdown-item - %button.btn.btn-link.js-data-value - %span.dropdown-light-content - {{name}} + #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu + %ul{ data: { dropdown: true } } + - Ci::Runner::AVAILABLE_TYPES.each do |runner_type| + %li.filter-dropdown-item{ data: { value: runner_type } } + = button_tag class: %w[btn btn-link] do + = runner_type.titleize + + #js-dropdown-runner-tag.filtered-search-input-dropdown-menu.dropdown-menu + %ul{ data: { dropdown: true } } + %li.filter-dropdown-item{ data: { value: 'none' } } + %button.btn.btn-link + = _('No Tag') + %li.divider.droplab-item-ignore + %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } + %li.filter-dropdown-item + %button.btn.btn-link.js-data-value + %span.dropdown-light-content + {{name}} - = button_tag class: %w[clear-search hidden] do - = icon('times') - .filter-dropdown-container - = render 'sort_dropdown' + = button_tag class: %w[clear-search hidden] do + = icon('times') + .filter-dropdown-container + = render 'sort_dropdown' - .col-sm-3.text-right-lg - = _('Runners currently online: %{active_runners_count}') % { active_runners_count: @active_runners_count } + .col-sm-3.text-right-lg + = _('Runners currently online: %{active_runners_count}') % { active_runners_count: @active_runners_count } - - if @runners.any? - .runners-content.content-list - .table-holder - .gl-responsive-table-row.table-row-header{ role: 'row' } - .table-section.section-10{ role: 'rowheader' }= _('Type') - .table-section.section-10{ role: 'rowheader' }= _('Runner token') - .table-section.section-20{ role: 'rowheader' }= _('Description') - .table-section.section-10{ role: 'rowheader' }= _('Version') - .table-section.section-10{ role: 'rowheader' }= _('IP Address') - .table-section.section-5{ role: 'rowheader' }= _('Projects') - .table-section.section-5{ role: 'rowheader' }= _('Jobs') - .table-section.section-10{ role: 'rowheader' }= _('Tags') - .table-section.section-10{ role: 'rowheader' }= _('Last contact') - .table-section.section-10{ role: 'rowheader' } +- if @runners.any? + .runners-content.content-list + .table-holder + .gl-responsive-table-row.table-row-header{ role: 'row' } + .table-section.section-10{ role: 'rowheader' }= _('Type') + .table-section.section-10{ role: 'rowheader' }= _('Runner token') + .table-section.section-20{ role: 'rowheader' }= _('Description') + .table-section.section-10{ role: 'rowheader' }= _('Version') + .table-section.section-10{ role: 'rowheader' }= _('IP Address') + .table-section.section-5{ role: 'rowheader' }= _('Projects') + .table-section.section-5{ role: 'rowheader' }= _('Jobs') + .table-section.section-10{ role: 'rowheader' }= _('Tags') + .table-section.section-10{ role: 'rowheader' }= _('Last contact') + .table-section.section-10{ role: 'rowheader' } - - @runners.each do |runner| - = render 'admin/runners/runner', runner: runner - = paginate @runners, theme: 'gitlab' - - else - .nothing-here-block= _('No runners found') + - @runners.each do |runner| + = render 'admin/runners/runner', runner: runner + = paginate @runners, theme: 'gitlab' +- else + .nothing-here-block= _('No runners found') diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml index b19934e028d..948a11646f7 100644 --- a/app/views/admin/system_info/show.html.haml +++ b/app/views/admin/system_info/show.html.haml @@ -1,37 +1,35 @@ -- @no_container = true - page_title "System Info" -%div{ class: container_class } - .prepend-top-default - .row - .col-sm-4 - .card.bg-light.light-well - %h4 CPU - .data - - if @cpus - %h1 #{@cpus.length} cores - - else - = icon('warning', class: 'text-warning') - Unable to collect CPU info - .col-sm-4 - .card.bg-light.light-well - %h4 Memory Usage - .data - - if @memory - %h1 #{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)} - - else - = icon('warning', class: 'text-warning') - Unable to collect memory info - .col-sm-4 - .card.bg-light.light-well - %h4 Disk Usage - .data - - @disks.each do |disk| - %h1 #{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])} - %p= disk[:disk_name] - %p= disk[:mount_path] - .col-sm-4 - .card.bg-light.light-well - %h4 Uptime - .data - %h1= distance_of_time_in_words_to_now(Rails.application.config.booted_at) +.prepend-top-default +.row + .col-sm-4 + .card.bg-light.light-well + %h4 CPU + .data + - if @cpus + %h1 #{@cpus.length} cores + - else + = icon('warning', class: 'text-warning') + Unable to collect CPU info + .col-sm-4 + .card.bg-light.light-well + %h4 Memory Usage + .data + - if @memory + %h1 #{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)} + - else + = icon('warning', class: 'text-warning') + Unable to collect memory info + .col-sm-4 + .card.bg-light.light-well + %h4 Disk Usage + .data + - @disks.each do |disk| + %h1 #{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])} + %p= disk[:disk_name] + %p= disk[:mount_path] + .col-sm-4 + .card.bg-light.light-well + %h4 Uptime + .data + %h1= distance_of_time_in_words_to_now(Rails.application.config.booted_at) diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 6fc7ec1bb6f..36b62557fa6 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -1,79 +1,77 @@ -- @no_container = true - page_title "Users" -%div{ class: container_class } - .top-area.scrolling-tabs-container.inner-page-scroll-tabs - .fade-left - = icon('angle-left') - .fade-right - = icon('angle-right') - %ul.nav-links.nav.nav-tabs.scrolling-tabs - = nav_link(html_options: { class: active_when(params[:filter].nil?) }) do - = link_to admin_users_path do - = s_('AdminUsers|Active') - %small.badge.badge-pill= limited_counter_with_delimiter(User.active) - = nav_link(html_options: { class: active_when(params[:filter] == 'admins') }) do - = link_to admin_users_path(filter: "admins") do - = s_('AdminUsers|Admins') - %small.badge.badge-pill= limited_counter_with_delimiter(User.admins) - = nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_enabled')} filter-two-factor-enabled" }) do - = link_to admin_users_path(filter: 'two_factor_enabled') do - = s_('AdminUsers|2FA Enabled') - %small.badge.badge-pill= limited_counter_with_delimiter(User.with_two_factor) - = nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_disabled')} filter-two-factor-disabled" }) do - = link_to admin_users_path(filter: 'two_factor_disabled') do - = s_('AdminUsers|2FA Disabled') - %small.badge.badge-pill= limited_counter_with_delimiter(User.without_two_factor) - = nav_link(html_options: { class: active_when(params[:filter] == 'external') }) do - = link_to admin_users_path(filter: 'external') do - = s_('AdminUsers|External') - %small.badge.badge-pill= limited_counter_with_delimiter(User.external) - = nav_link(html_options: { class: active_when(params[:filter] == 'blocked') }) do - = link_to admin_users_path(filter: "blocked") do - = s_('AdminUsers|Blocked') - %small.badge.badge-pill= limited_counter_with_delimiter(User.blocked) - = nav_link(html_options: { class: active_when(params[:filter] == 'wop') }) do - = link_to admin_users_path(filter: "wop") do - = s_('AdminUsers|Without projects') - %small.badge.badge-pill= limited_counter_with_delimiter(User.without_projects) - .nav-controls - = render_if_exists 'admin/users/admin_email_users' - = link_to s_('AdminUsers|New user'), new_admin_user_path, class: 'btn btn-success btn-search float-right' +.top-area.scrolling-tabs-container.inner-page-scroll-tabs + .fade-left + = icon('angle-left') + .fade-right + = icon('angle-right') + %ul.nav-links.nav.nav-tabs.scrolling-tabs + = nav_link(html_options: { class: active_when(params[:filter].nil?) }) do + = link_to admin_users_path do + = s_('AdminUsers|Active') + %small.badge.badge-pill= limited_counter_with_delimiter(User.active) + = nav_link(html_options: { class: active_when(params[:filter] == 'admins') }) do + = link_to admin_users_path(filter: "admins") do + = s_('AdminUsers|Admins') + %small.badge.badge-pill= limited_counter_with_delimiter(User.admins) + = nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_enabled')} filter-two-factor-enabled" }) do + = link_to admin_users_path(filter: 'two_factor_enabled') do + = s_('AdminUsers|2FA Enabled') + %small.badge.badge-pill= limited_counter_with_delimiter(User.with_two_factor) + = nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_disabled')} filter-two-factor-disabled" }) do + = link_to admin_users_path(filter: 'two_factor_disabled') do + = s_('AdminUsers|2FA Disabled') + %small.badge.badge-pill= limited_counter_with_delimiter(User.without_two_factor) + = nav_link(html_options: { class: active_when(params[:filter] == 'external') }) do + = link_to admin_users_path(filter: 'external') do + = s_('AdminUsers|External') + %small.badge.badge-pill= limited_counter_with_delimiter(User.external) + = nav_link(html_options: { class: active_when(params[:filter] == 'blocked') }) do + = link_to admin_users_path(filter: "blocked") do + = s_('AdminUsers|Blocked') + %small.badge.badge-pill= limited_counter_with_delimiter(User.blocked) + = nav_link(html_options: { class: active_when(params[:filter] == 'wop') }) do + = link_to admin_users_path(filter: "wop") do + = s_('AdminUsers|Without projects') + %small.badge.badge-pill= limited_counter_with_delimiter(User.without_projects) + .nav-controls + = render_if_exists 'admin/users/admin_email_users' + = link_to s_('AdminUsers|New user'), new_admin_user_path, class: 'btn btn-success btn-search float-right' - .filtered-search-block.row-content-block.border-top-0 - = form_tag admin_users_path, method: :get do - - if params[:filter].present? - = hidden_field_tag "filter", h(params[:filter]) - .search-holder - .search-field-holder - = search_field_tag :search_query, params[:search_query], placeholder: s_('AdminUsers|Search by name, email or username'), class: 'form-control search-text-input js-search-input', spellcheck: false - - if @sort.present? - = hidden_field_tag :sort, @sort - = icon("search", class: "search-icon") - = button_tag s_('AdminUsers|Search users') if Rails.env.test? - .dropdown.user-sort-dropdown - - toggle_text = @sort.present? ? users_sort_options_hash[@sort] : sort_title_name - = dropdown_toggle(toggle_text, { toggle: 'dropdown' }) - %ul.dropdown-menu.dropdown-menu-right - %li.dropdown-header - = s_('AdminUsers|Sort by') - %li - - users_sort_options_hash.each do |value, title| - = link_to admin_users_path(sort: value, filter: params[:filter], search_query: params[:search_query]) do - = title +.filtered-search-block.row-content-block.border-top-0 + = form_tag admin_users_path, method: :get do + - if params[:filter].present? + = hidden_field_tag "filter", h(params[:filter]) + .search-holder + .search-field-holder + = search_field_tag :search_query, params[:search_query], placeholder: s_('AdminUsers|Search by name, email or username'), class: 'form-control search-text-input js-search-input', spellcheck: false + - if @sort.present? + = hidden_field_tag :sort, @sort + = icon("search", class: "search-icon") + = button_tag s_('AdminUsers|Search users') if Rails.env.test? + .dropdown.user-sort-dropdown + - toggle_text = @sort.present? ? users_sort_options_hash[@sort] : sort_title_name + = dropdown_toggle(toggle_text, { toggle: 'dropdown' }) + %ul.dropdown-menu.dropdown-menu-right + %li.dropdown-header + = s_('AdminUsers|Sort by') + %li + - users_sort_options_hash.each do |value, title| + = link_to admin_users_path(sort: value, filter: params[:filter], search_query: params[:search_query]) do + = title - - if @users.empty? - .nothing-here-block.border-top-0 - = s_('AdminUsers|No users found') - - else - .table-holder - .thead-white.text-nowrap.gl-responsive-table-row.table-row-header{ role: 'row' } - .table-section.section-40{ role: 'rowheader' }= _('Name') - .table-section.section-25{ role: 'rowheader' }= _('Created on') - .table-section.section-15{ role: 'rowheader' }= _('Last activity') +- if @users.empty? + .nothing-here-block.border-top-0 + = s_('AdminUsers|No users found') +- else + .table-holder + .thead-white.text-nowrap.gl-responsive-table-row.table-row-header{ role: 'row' } + .table-section.section-40{ role: 'rowheader' }= _('Name') + .table-section.section-25{ role: 'rowheader' }= _('Created on') + .table-section.section-15{ role: 'rowheader' }= _('Last activity') - = render partial: 'admin/users/user', collection: @users + = render partial: 'admin/users/user', collection: @users - = paginate @users, theme: "gitlab" += paginate @users, theme: "gitlab" #delete-user-modal diff --git a/app/views/clusters/clusters/_configure.html.haml b/app/views/clusters/clusters/_configure.html.haml new file mode 100644 index 00000000000..4ce00c67866 --- /dev/null +++ b/app/views/clusters/clusters/_configure.html.haml @@ -0,0 +1,26 @@ +%section#cluster-integration + - unless @cluster.status_name.in? %i/scheduled creating/ + = render 'form' + +- unless @cluster.status_name.in? %i/scheduled creating/ + = render_if_exists 'projects/clusters/prometheus_graphs' + + .cluster-applications-table#js-cluster-applications + + %section.settings#js-cluster-details{ class: ('expanded' if expanded) } + .settings-header + %h4= s_('ClusterIntegration|Kubernetes cluster details') + %button.btn.js-settings-toggle{ type: 'button' } + = expanded ? _('Collapse') : _('Expand') + %p= s_('ClusterIntegration|See and edit the details for your Kubernetes cluster') + .settings-content + = render 'clusters/platforms/kubernetes/form', cluster: @cluster, platform: @cluster.platform_kubernetes, update_cluster_url_path: clusterable.cluster_path(@cluster) + + %section.settings.no-animate#js-cluster-advanced-settings{ class: ('expanded' if expanded) } + .settings-header + %h4= _('Advanced settings') + %button.btn.js-settings-toggle{ type: 'button' } + = expanded ? _('Collapse') : _('Expand') + %p= s_("ClusterIntegration|Advanced options on this Kubernetes cluster's integration") + .settings-content#advanced-settings-section + = render 'advanced_settings' diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml index 913d4caa0bc..6052e5d96f2 100644 --- a/app/views/clusters/clusters/show.html.haml +++ b/app/views/clusters/clusters/show.html.haml @@ -24,38 +24,19 @@ help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'), ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-endpoint'), ingress_dns_help_path: help_page_path('user/project/clusters/index.md', anchor: 'manually-determining-the-external-endpoint'), + environments_help_path: help_page_path('ci/environments', anchor: 'defining-environments'), + clusters_help_path: help_page_path('user/project/clusters/index.md', anchor: 'deploying-to-a-kubernetes-cluster'), + deploy_boards_help_path: help_page_path('user/project/deploy_boards.html', anchor: 'enabling-deploy-boards'), manage_prometheus_path: manage_prometheus_path, cluster_id: @cluster.id } } .js-cluster-application-notice .flash-container - %section#cluster-integration - %h4= @cluster.name - = render 'banner' + %h4= @cluster.name + = render 'banner' - - unless @cluster.status_name.in? %i/scheduled creating/ - = render 'form' + = render_if_exists 'clusters/clusters/group_cluster_environments', expanded: expanded - - unless @cluster.status_name.in? %i/scheduled creating/ - = render_if_exists 'projects/clusters/prometheus_graphs' - - .cluster-applications-table#js-cluster-applications - - %section.settings#js-cluster-details{ class: ('expanded' if expanded) } - .settings-header - %h4= s_('ClusterIntegration|Kubernetes cluster details') - %button.btn.js-settings-toggle{ type: 'button' } - = expanded ? _('Collapse') : _('Expand') - %p= s_('ClusterIntegration|See and edit the details for your Kubernetes cluster') - .settings-content - = render 'clusters/platforms/kubernetes/form', cluster: @cluster, platform: @cluster.platform_kubernetes, update_cluster_url_path: clusterable.cluster_path(@cluster) - - %section.settings.no-animate#js-cluster-advanced-settings{ class: ('expanded' if expanded) } - .settings-header - %h4= _('Advanced settings') - %button.btn.js-settings-toggle{ type: 'button' } - = expanded ? _('Collapse') : _('Expand') - %p= s_("ClusterIntegration|Advanced options on this Kubernetes cluster's integration") - .settings-content#advanced-settings-section - = render 'advanced_settings' + - unless Gitlab.ee? + = render 'configure', expanded: expanded diff --git a/app/views/clusters/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml index 1d212553c3b..5507f12b73b 100644 --- a/app/views/clusters/clusters/user/_form.html.haml +++ b/app/views/clusters/clusters/user/_form.html.haml @@ -25,6 +25,7 @@ help: '%{help_text} %{help_link}'.html_safe % { help_text: api_url_help_text, help_link: more_info_link } = platform_kubernetes_field.text_area :ca_cert, + rows: '10', placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)'), label: s_('ClusterIntegration|CA Certificate'), label_class: 'label-bold', help: '%{help_text} %{help_link}'.html_safe % { help_text: ca_cert_help_text, help_link: more_info_link } diff --git a/app/views/clusters/platforms/kubernetes/_form.html.haml b/app/views/clusters/platforms/kubernetes/_form.html.haml index e50c573bd90..41701b5614a 100644 --- a/app/views/clusters/platforms/kubernetes/_form.html.haml +++ b/app/views/clusters/platforms/kubernetes/_form.html.haml @@ -19,7 +19,7 @@ - copy_ca_cert_btn = clipboard_button(text: platform.ca_cert, title: s_('ClusterIntegration|Copy CA Certificate'), class: 'input-group-text btn-default') if cluster.read_only_kubernetes_platform_fields? - = platform_field.text_area :ca_cert, class: 'js-select-on-focus', rows: '5', + = platform_field.text_area :ca_cert, class: 'js-select-on-focus', rows: '10', readonly: cluster.read_only_kubernetes_platform_fields?, placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)'), label: s_('ClusterIntegration|CA Certificate'), label_class: 'label-bold', diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml index b1c192d7bad..d7306f5932d 100644 --- a/app/views/dashboard/activity.html.haml +++ b/app/views/dashboard/activity.html.haml @@ -1,18 +1,15 @@ - @hide_top_links = true -- @no_container = true = content_for :meta_tags do = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity") - = render_dashboard_gold_trial(current_user) - page_title "Activity" - header_title "Activity", activity_dashboard_path -%div{ class: container_class } - = render "projects/last_push" - = render 'dashboard/activity_head' += render "projects/last_push" += render 'dashboard/activity_head' - %section.activities - = render 'activities' +%section.activities + = render 'activities' diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml index 0298f539b4b..d2aa07bab22 100644 --- a/app/views/dashboard/projects/index.html.haml +++ b/app/views/dashboard/projects/index.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - @hide_top_links = true = content_for :meta_tags do @@ -9,11 +8,10 @@ - page_title "Projects" - header_title "Projects", dashboard_projects_path -%div{ class: container_class } - = render "projects/last_push" - - if show_projects?(@projects, params) - = render 'dashboard/projects_head' - = render 'nav' unless Feature.enabled?(:project_list_filter_bar) - = render 'projects' - - else - = render "zero_authorized_projects" += render "projects/last_push" +- if show_projects?(@projects, params) + = render 'dashboard/projects_head' + = render 'nav' unless Feature.enabled?(:project_list_filter_bar) + = render 'projects' +- else + = render "zero_authorized_projects" diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml index 0fcc6894b68..2924918aa4f 100644 --- a/app/views/dashboard/projects/starred.html.haml +++ b/app/views/dashboard/projects/starred.html.haml @@ -1,16 +1,14 @@ - @hide_top_links = true -- @no_container = true - breadcrumb_title _("Projects") - page_title _("Starred Projects") - header_title _("Projects"), dashboard_projects_path = render_dashboard_gold_trial(current_user) -%div{ class: container_class } - = render "projects/last_push" - = render 'dashboard/projects_head', project_tab_filter: :starred += render "projects/last_push" += render 'dashboard/projects_head', project_tab_filter: :starred - - if params[:filter_projects] || any_projects?(@projects) - = render 'projects' - - else - = render 'starred_empty_state' +- if params[:filter_projects] || any_projects?(@projects) + = render 'projects' +- else + = render 'starred_empty_state' diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index a8358704b03..41c1d3e84b7 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - page_title 'Labels' - can_admin_label = can?(current_user, :admin_label, @group) - search = params[:search] @@ -7,24 +6,23 @@ - if labels_or_filters #promote-label-modal - %div{ class: container_class } - = render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label + = render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label - .labels-container.prepend-top-5 - - if @labels.any? - .text-muted - = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuable_types.to_sentence } - .other-labels - %h5= _('Labels') - %ul.content-list.manage-labels-list.js-other-labels - = render partial: 'shared/label', collection: @labels, as: :label, locals: { use_label_priority: false, subject: @group } - = paginate @labels, theme: 'gitlab' - - elsif search.present? - .nothing-here-block - = _('No labels with such name or description') - - elsif subscribed.present? - .nothing-here-block - = _('You do not have any subscriptions yet') + .labels-container.prepend-top-5 + - if @labels.any? + .text-muted + = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuable_types.to_sentence } + .other-labels + %h5= _('Labels') + %ul.content-list.manage-labels-list.js-other-labels + = render partial: 'shared/label', collection: @labels, as: :label, locals: { use_label_priority: false, subject: @group } + = paginate @labels, theme: 'gitlab' + - elsif search.present? + .nothing-here-block + = _('No labels with such name or description') + - elsif subscribed.present? + .nothing-here-block + = _('You do not have any subscriptions yet') - else = render 'shared/empty_states/labels' diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index 248cb3b0ba5..2c93b0e4efd 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -1,12 +1,10 @@ -- @no_container = true - add_to_breadcrumbs _("Milestones"), group_milestones_path(@group) - breadcrumb_title _("New") - page_title _("Milestones"), @milestone.name, _("Milestones") -%div{ class: container_class } - %h3.page-title - New Milestone +%h3.page-title + New Milestone - %hr +%hr - = render "form" += render "form" diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 255a9ad038c..0e6c16f0f06 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,11 +1,10 @@ -- @no_container = true - breadcrumb_title _("Details") - @content_class = "limit-container-width" unless fluid_layout = content_for :meta_tags do = auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity") -%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } +%div{ class: [("limit-container-width" unless fluid_layout)] } = render 'groups/home_panel' .groups-listing{ data: { endpoints: { default: group_children_path(@group, format: :json), shared: group_shared_projects_path(@group, format: :json) } } } diff --git a/app/views/instance_statistics/cohorts/index.html.haml b/app/views/instance_statistics/cohorts/index.html.haml index e135bab10d8..c438566cb05 100644 --- a/app/views/instance_statistics/cohorts/index.html.haml +++ b/app/views/instance_statistics/cohorts/index.html.haml @@ -1,16 +1,14 @@ - breadcrumb_title _("Cohorts") -- @no_container = true -%div{ class: container_class } - - if @cohorts - = render 'cohorts_table' - - else - .bs-callout.bs-callout-warning.clearfix - %p - - usage_ping_path = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-ping') - - usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: usage_ping_path } - = s_('User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled.').html_safe % { usage_ping_link_start: usage_ping_link_start, usage_ping_link_end: '</a>'.html_safe } - - if current_user.admin? - - application_settings_path = admin_application_settings_path(anchor: 'usage-statistics') - - application_settings_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: application_settings_path } - = s_('To enable it and see User Cohorts, visit %{application_settings_link_start}application settings%{application_settings_link_end}.').html_safe % { application_settings_link_start: application_settings_link_start, application_settings_link_end: '</a>'.html_safe } +- if @cohorts + = render 'cohorts_table' +- else + .bs-callout.bs-callout-warning.clearfix + %p + - usage_ping_path = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-ping') + - usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: usage_ping_path } + = s_('User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled.').html_safe % { usage_ping_link_start: usage_ping_link_start, usage_ping_link_end: '</a>'.html_safe } + - if current_user.admin? + - application_settings_path = admin_application_settings_path(anchor: 'usage-statistics') + - application_settings_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: application_settings_path } + = s_('To enable it and see User Cohorts, visit %{application_settings_link_start}application settings%{application_settings_link_end}.').html_safe % { application_settings_link_start: application_settings_link_start, application_settings_link_end: '</a>'.html_safe } diff --git a/app/views/instance_statistics/conversational_development_index/index.html.haml b/app/views/instance_statistics/conversational_development_index/index.html.haml index 23f90b876a0..49c8fdc9630 100644 --- a/app/views/instance_statistics/conversational_development_index/index.html.haml +++ b/app/views/instance_statistics/conversational_development_index/index.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - page_title _('ConvDev Index') - usage_ping_enabled = Gitlab::CurrentSettings.usage_ping_enabled diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml index 2cdaa85bdaa..d673d7164b3 100644 --- a/app/views/layouts/_flash.html.haml +++ b/app/views/layouts/_flash.html.haml @@ -1,10 +1,8 @@ -- extra_flash_class = local_assigns.fetch(:extra_flash_class, nil) - -.flash-container.flash-container-page +.flash-container.flash-container-page.sticky -# We currently only support `alert`, `notice`, `success` - flash.each do |key, value| -# Don't show a flash message if the message is nil - if value - %div{ class: "flash-#{key}" } - %div{ class: "#{(container_class unless fluid_layout)} #{(extra_flash_class unless @no_container)} #{@content_class}" } - %span= value + %div{ class: "flash-content flash-#{key} rounded" } + %span= value + = sprite_icon('close', size: 16, css_class: 'close-icon') diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 006334ade07..443a73f5cce 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -13,8 +13,8 @@ = render "shared/ping_consent" - unless @hide_breadcrumbs = render "layouts/nav/breadcrumbs" - = render "layouts/flash", extra_flash_class: 'limit-container-width' .d-flex %div{ class: "#{(container_class unless @no_container)} #{@content_class}" } .content{ id: "content-body" } + = render "layouts/flash", extra_flash_class: 'limit-container-width' = yield diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml index b28a375e956..6a4760c3954 100644 --- a/app/views/projects/activity.html.haml +++ b/app/views/projects/activity.html.haml @@ -1,8 +1,4 @@ -- @no_container = true - - page_title _("Activity") -%div{ class: container_class } - = render 'projects/last_push' - += render 'projects/last_push' = render 'projects/activity' diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index ef6f5c76de6..f2215765974 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -1,44 +1,42 @@ -- @no_container = true - project_duration = age_map_duration(@blame_groups, @project) - page_title "Blame", @blob.path, @ref -%div{ class: container_class } - #blob-content-holder.tree-holder - = render "projects/blob/breadcrumb", blob: @blob, blame: true +#blob-content-holder.tree-holder + = render "projects/blob/breadcrumb", blob: @blob, blame: true - .file-holder - = render "projects/blob/header", blob: @blob, blame: true - .file-blame-legend - = render 'age_map_legend' - .table-responsive.file-content.blame.code.js-syntax-highlight - %table - - current_line = 1 - - @blame_groups.each do |blame_group| - %tr - - commit = blame_group[:commit] - %td.blame-commit{ class: age_map_class(commit.committed_date, project_duration) } - .commit - = author_avatar(commit, size: 36, has_tooltip: false) - .commit-row-title - %span.item-title.str-truncated-100 - = link_to_markdown commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title - .float-right - = link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha" - - .light - = commit_author_link(commit, avatar: false) - committed - #{time_ago_with_tooltip(commit.committed_date)} - %td.line-numbers - - line_count = blame_group[:lines].count - - (current_line...(current_line + line_count)).each do |i| - %a.diff-line-num{ href: "#L#{i}", id: "L#{i}", 'data-line-number' => i } - = icon("link") - = i - \ - - current_line += line_count - %td.lines - %pre.code.highlight - %code - - blame_group[:lines].each do |line| - #{line} + .file-holder + = render "projects/blob/header", blob: @blob, blame: true + .file-blame-legend + = render 'age_map_legend' + .table-responsive.file-content.blame.code.js-syntax-highlight + %table + - current_line = 1 + - @blame_groups.each do |blame_group| + %tr + - commit = blame_group[:commit] + %td.blame-commit{ class: age_map_class(commit.committed_date, project_duration) } + .commit + = author_avatar(commit, size: 36, has_tooltip: false) + .commit-row-title + %span.item-title.str-truncated-100 + = link_to_markdown commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title + .float-right + = link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha" + + .light + = commit_author_link(commit, avatar: false) + committed + #{time_ago_with_tooltip(commit.committed_date)} + %td.line-numbers + - line_count = blame_group[:lines].count + - (current_line...(current_line + line_count)).each do |i| + %a.diff-line-num{ href: "#L#{i}", id: "L#{i}", 'data-line-number' => i } + = icon("link") + = i + \ + - current_line += line_count + %td.lines + %pre.code.highlight + %code + - blame_group[:lines].each do |line| + #{line} diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 4520cca8cf5..51e42091ab8 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -1,33 +1,31 @@ - breadcrumb_title "Repository" -- @no_container = true - page_title "Edit", @blob.path, @ref - content_for :page_specific_javascripts do = page_specific_javascript_tag('lib/ace.js') -%div{ class: container_class } - - if @conflict - .alert.alert-danger - Someone edited the file the same time you did. Please check out - = link_to "the file", project_blob_path(@project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer' - and make sure your changes will not unintentionally remove theirs. - .editor-title-row - %h3.page-title.blob-edit-page-title - Edit file - = render 'template_selectors' - .file-editor - %ul.nav-links.no-bottom.js-edit-mode.nav.nav-tabs - %li.active - = link_to '#editor' do - Write +- if @conflict + .alert.alert-danger + Someone edited the file the same time you did. Please check out + = link_to "the file", project_blob_path(@project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer' + and make sure your changes will not unintentionally remove theirs. +.editor-title-row + %h3.page-title.blob-edit-page-title + Edit file + = render 'template_selectors' +.file-editor + %ul.nav-links.no-bottom.js-edit-mode.nav.nav-tabs + %li.active + = link_to '#editor' do + Write - %li - = link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id) do - = editing_preview_title(@blob.name) + %li + = link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id) do + = editing_preview_title(@blob.name) - = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths(@project)) do - = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data - = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" - = hidden_field_tag 'last_commit_sha', @last_commit_sha - = hidden_field_tag 'content', '', id: "file-content" - = hidden_field_tag 'from_merge_request_iid', params[:from_merge_request_iid] - = render 'projects/commit_button', ref: @ref, cancel_path: project_blob_path(@project, @id) + = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths(@project)) do + = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data + = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" + = hidden_field_tag 'last_commit_sha', @last_commit_sha + = hidden_field_tag 'content', '', id: "file-content" + = hidden_field_tag 'from_merge_request_iid', params[:from_merge_request_iid] + = render 'projects/commit_button', ref: @ref, cancel_path: project_blob_path(@project, @id) diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index a0b0384d78d..688b8f001c3 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -1,19 +1,16 @@ - breadcrumb_title "Repository" -- @no_container = true - - page_title @blob.path, @ref - - signatures_path = namespace_project_signatures_path(namespace_id: @project.namespace.full_path, project_id: @project.path, id: @last_commit) + .js-signature-container{ data: { 'signatures-path': signatures_path } } -%div{ class: container_class } - = render 'projects/last_push' += render 'projects/last_push' - #tree-holder.tree-holder - = render 'blob', blob: @blob +#tree-holder.tree-holder + = render 'blob', blob: @blob - - if can_modify_blob?(@blob) - = render 'projects/blob/remove' + - if can_modify_blob?(@blob) + = render 'projects/blob/remove' - - title = "Replace #{@blob.name}" - = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put + - title = "Replace #{@blob.name}" + = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 11340d12423..6bdc6f716fe 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -1,70 +1,68 @@ -- @no_container = true - page_title _('Branches') - add_to_breadcrumbs(_('Repository'), project_tree_path(@project)) -%div{ class: container_class } - .top-area.adjust - %ul.nav-links.issues-state-filters.nav.nav-tabs - %li{ class: active_when(@mode == 'overview') }> - = link_to s_('Branches|Overview'), project_branches_path(@project), title: s_('Branches|Show overview of the branches') +.top-area.adjust + %ul.nav-links.issues-state-filters.nav.nav-tabs + %li{ class: active_when(@mode == 'overview') }> + = link_to s_('Branches|Overview'), project_branches_path(@project), title: s_('Branches|Show overview of the branches') - %li{ class: active_when(@mode == 'active') }> - = link_to s_('Branches|Active'), project_branches_filtered_path(@project, state: 'active'), title: s_('Branches|Show active branches') + %li{ class: active_when(@mode == 'active') }> + = link_to s_('Branches|Active'), project_branches_filtered_path(@project, state: 'active'), title: s_('Branches|Show active branches') - %li{ class: active_when(@mode == 'stale') }> - = link_to s_('Branches|Stale'), project_branches_filtered_path(@project, state: 'stale'), title: s_('Branches|Show stale branches') + %li{ class: active_when(@mode == 'stale') }> + = link_to s_('Branches|Stale'), project_branches_filtered_path(@project, state: 'stale'), title: s_('Branches|Show stale branches') - %li{ class: active_when(!%w[overview active stale].include?(@mode)) }> - = link_to s_('Branches|All'), project_branches_filtered_path(@project, state: 'all'), title: s_('Branches|Show all branches') + %li{ class: active_when(!%w[overview active stale].include?(@mode)) }> + = link_to s_('Branches|All'), project_branches_filtered_path(@project, state: 'all'), title: s_('Branches|Show all branches') - .nav-controls - = form_tag(project_branches_filtered_path(@project, state: 'all'), method: :get) do - = search_field_tag :search, params[:search], { placeholder: s_('Branches|Filter by branch name'), id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false } + .nav-controls + = form_tag(project_branches_filtered_path(@project, state: 'all'), method: :get) do + = search_field_tag :search, params[:search], { placeholder: s_('Branches|Filter by branch name'), id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false } - - unless @mode == 'overview' - .dropdown.inline> - %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } - %span.light - = branches_sort_options_hash[@sort] - = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable - %li.dropdown-header - = s_('Branches|Sort by') - - branches_sort_options_hash.each do |value, title| - %li - = link_to title, project_branches_filtered_path(@project, state: 'all', search: params[:search], sort: value), class: ("is-active" if @sort == value) + - unless @mode == 'overview' + .dropdown.inline> + %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } + %span.light + = branches_sort_options_hash[@sort] + = icon('chevron-down') + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable + %li.dropdown-header + = s_('Branches|Sort by') + - branches_sort_options_hash.each do |value, title| + %li + = link_to title, project_branches_filtered_path(@project, state: 'all', search: params[:search], sort: value), class: ("is-active" if @sort == value) - - if can? current_user, :push_code, @project - = link_to project_merged_branches_path(@project), - class: 'btn btn-inverted btn-remove has-tooltip qa-delete-merged-branches', - title: s_("Branches|Delete all branches that are merged into '%{default_branch}'") % { default_branch: @project.repository.root_ref }, - method: :delete, - data: { confirm: s_('Branches|Deleting the merged branches cannot be undone. Are you sure?'), - container: 'body' } do - = s_('Branches|Delete merged branches') - = link_to new_project_branch_path(@project), class: 'btn btn-success' do - = s_('Branches|New branch') + - if can? current_user, :push_code, @project + = link_to project_merged_branches_path(@project), + class: 'btn btn-inverted btn-remove has-tooltip qa-delete-merged-branches', + title: s_("Branches|Delete all branches that are merged into '%{default_branch}'") % { default_branch: @project.repository.root_ref }, + method: :delete, + data: { confirm: s_('Branches|Deleting the merged branches cannot be undone. Are you sure?'), + container: 'body' } do + = s_('Branches|Delete merged branches') + = link_to new_project_branch_path(@project), class: 'btn btn-success' do + = s_('Branches|New branch') - = render_if_exists 'projects/commits/mirror_status' += render_if_exists 'projects/commits/mirror_status' - .js-branch-list{ data: { diverging_counts_endpoint: diverging_commit_counts_namespace_project_branches_path(@project.namespace, @project, format: :json) } } - - if can?(current_user, :admin_project, @project) - - project_settings_link = link_to s_('Branches|project settings'), project_protected_branches_path(@project) - .row-content-block - %h5 - = s_('Branches|Protected branches can be managed in %{project_settings_link}.').html_safe % { project_settings_link: project_settings_link } +.js-branch-list{ data: { diverging_counts_endpoint: diverging_commit_counts_namespace_project_branches_path(@project.namespace, @project, format: :json) } } +- if can?(current_user, :admin_project, @project) + - project_settings_link = link_to s_('Branches|project settings'), project_protected_branches_path(@project) + .row-content-block + %h5 + = s_('Branches|Protected branches can be managed in %{project_settings_link}.').html_safe % { project_settings_link: project_settings_link } - - if @mode == 'overview' && (@active_branches.any? || @stale_branches.any?) - = render "projects/branches/panel", branches: @active_branches, state: 'active', panel_title: s_('Branches|Active branches'), show_more_text: s_('Branches|Show more active branches'), project: @project, overview_max_branches: @overview_max_branches - = render "projects/branches/panel", branches: @stale_branches, state: 'stale', panel_title: s_('Branches|Stale branches'), show_more_text: s_('Branches|Show more stale branches'), project: @project, overview_max_branches: @overview_max_branches +- if @mode == 'overview' && (@active_branches.any? || @stale_branches.any?) + = render "projects/branches/panel", branches: @active_branches, state: 'active', panel_title: s_('Branches|Active branches'), show_more_text: s_('Branches|Show more active branches'), project: @project, overview_max_branches: @overview_max_branches + = render "projects/branches/panel", branches: @stale_branches, state: 'stale', panel_title: s_('Branches|Stale branches'), show_more_text: s_('Branches|Show more stale branches'), project: @project, overview_max_branches: @overview_max_branches - - elsif @branches.any? - %ul.content-list.all-branches - - @branches.each do |branch| - = render "projects/branches/branch", branch: branch, merged: @merged_branch_names.include?(branch.name) - = paginate @branches, theme: 'gitlab' - - else - .nothing-here-block - = s_('Branches|No branches to show') +- elsif @branches.any? + %ul.content-list.all-branches + - @branches.each do |branch| + = render "projects/branches/branch", branch: branch, merged: @merged_branch_names.include?(branch.name) + = paginate @branches, theme: 'gitlab' +- else + .nothing-here-block + = s_('Branches|No branches to show') = render 'projects/branches/delete_protected_modal' diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 34226167288..40b96ca477e 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -1,3 +1,4 @@ +-# no_container is needed here because of full width side-by-side diff view - @no_container = true - add_to_breadcrumbs _('Commits'), project_commits_path(@project) - breadcrumb_title @commit.short_id diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 2db1efdd52f..e155e3758fb 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - breadcrumb_title _("Commits") - page_title _("Commits"), @ref @@ -6,33 +5,32 @@ = auto_discovery_link_tag(:atom, project_commits_path(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") .js-project-commits-show{ 'data-commits-limit' => @limit } - %div{ class: container_class } - .tree-holder - .nav-block - .tree-ref-container - .tree-ref-holder - = render 'shared/ref_switcher', destination: 'commits' - - %ul.breadcrumb.repo-breadcrumb - = commits_breadcrumbs - .tree-controls.d-none.d-sm-none.d-md-block - - if @merge_request.present? - .control - = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn' - - elsif create_mr_button?(@repository.root_ref, @ref) - .control - = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' + .tree-holder + .nav-block + .tree-ref-container + .tree-ref-holder + = render 'shared/ref_switcher', destination: 'commits' + %ul.breadcrumb.repo-breadcrumb + = commits_breadcrumbs + .tree-controls.d-none.d-sm-none.d-md-block + - if @merge_request.present? .control - = form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form js-signature-container', data: { 'signatures-path' => namespace_project_signatures_path }) do - = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false } + = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn' + - elsif create_mr_button?(@repository.root_ref, @ref) .control - = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do - = icon("rss") + = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' + + .control + = form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form js-signature-container', data: { 'signatures-path' => namespace_project_signatures_path }) do + = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false } + .control + = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do + = icon("rss") - = render_if_exists 'projects/commits/mirror_status' + = render_if_exists 'projects/commits/mirror_status' - %div{ id: dom_id(@project) } - %ol#commits-list.list-unstyled.content_list - = render 'commits', project: @project, ref: @ref - = spinner + %div{ id: dom_id(@project) } + %ol#commits-list.list-unstyled.content_list + = render 'commits', project: @project, ref: @ref + = spinner diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index 14c64b3534a..02f2b104ce3 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -1,18 +1,16 @@ -- @no_container = true - breadcrumb_title "Compare Revisions" - page_title "Compare" -%div{ class: container_class } - %h3.page-title - = _("Compare Git revisions") - .sub-header-block - - example_master = capture do - %code.ref-name master - - example_sha = capture do - %code.ref-name 4eedf23 - = (_("Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request.") % { master: example_master, sha: example_sha }).html_safe - %br - = (_("Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision.")).html_safe +%h3.page-title + = _("Compare Git revisions") +.sub-header-block + - example_master = capture do + %code.ref-name master + - example_sha = capture do + %code.ref-name 4eedf23 + = (_("Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request.") % { master: example_master, sha: example_sha }).html_safe + %br + = (_("Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision.")).html_safe - .prepend-top-20 - = render "form" +.prepend-top-20 + = render "form" diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index 5774b48a054..51cf95dc84b 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -1,25 +1,23 @@ -- @no_container = true - add_to_breadcrumbs _("Compare Revisions"), project_compare_index_path(@project) - page_title "#{params[:from]}...#{params[:to]}" -%div{ class: container_class } - .sub-header-block.no-bottom-space - = render "form" +.sub-header-block.no-bottom-space + = render "form" - - if @commits.present? - = render "projects/commits/commit_list" - = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, diff_page_context: "is-compare" - - else - .card.bg-light - .center - %h4 - = s_("CompareBranches|There isn't anything to compare.") - %p.slead - - if params[:to] == params[:from] - - source_branch = capture do - %span.ref-name= params[:from] - - target_branch = capture do - %span.ref-name= params[:to] - = (s_("CompareBranches|%{source_branch} and %{target_branch} are the same.") % { source_branch: source_branch, target_branch: target_branch }).html_safe - - else - = _("You'll need to use different branch names to get a valid comparison.") +- if @commits.present? + = render "projects/commits/commit_list" + = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, diff_page_context: "is-compare" +- else + .card.bg-light + .center + %h4 + = s_("CompareBranches|There isn't anything to compare.") + %p.slead + - if params[:to] == params[:from] + - source_branch = capture do + %span.ref-name= params[:from] + - target_branch = capture do + %span.ref-name= params[:to] + = (s_("CompareBranches|%{source_branch} and %{target_branch} are the same.") % { source_branch: source_branch, target_branch: target_branch }).html_safe + - else + = _("You'll need to use different branch names to get a valid comparison.") diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index 2b594c125f4..6b56a4ee7ab 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -1,7 +1,6 @@ -- @no_container = true - page_title "Cycle Analytics" -#cycle-analytics{ class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } } +#cycle-analytics{ "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } } - if @cycle_analytics_no_data %banner{ "v-if" => "!isOverviewDialogDismissed", "documentation-link": help_page_path('user/project/cycle_analytics'), diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 9fa31c147eb..a9b6b397968 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -1,10 +1,9 @@ - @content_class = "limit-container-width" unless fluid_layout -- @no_container = true - breadcrumb_title _("Details") = render partial: 'flash_messages', locals: { project: @project } -%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } +%div{ class: [("limit-container-width" unless fluid_layout)] } = render "home_panel" %h4.prepend-top-0.append-bottom-8 diff --git a/app/views/projects/environments/edit.html.haml b/app/views/projects/environments/edit.html.haml index d581bd3aeab..56af252d785 100644 --- a/app/views/projects/environments/edit.html.haml +++ b/app/views/projects/environments/edit.html.haml @@ -1,8 +1,6 @@ -- @no_container = true - page_title _("Edit"), @environment.name, _("Environments") -%div{ class: container_class } - %h3.page-title - = _('Edit environment') - %hr - = render 'form' +%h3.page-title + = _('Edit environment') +%hr += render 'form' diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml index aebd176af9b..f85c57d9aa1 100644 --- a/app/views/projects/environments/folder.html.haml +++ b/app/views/projects/environments/folder.html.haml @@ -1,5 +1,3 @@ -- @no_container = true - page_title _("Environments") -#environments-folder-list-view{ data: { environments_data: environments_folder_list_view_data, - "css-class" => container_class } } +#environments-folder-list-view{ data: { environments_data: environments_folder_list_view_data } } diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 3ec92676cde..2ba88da3375 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - page_title _("Environments") #environments-list-view{ data: { environments_data: environments_list_data, @@ -6,5 +5,4 @@ "can-create-environment" => can?(current_user, :create_environment, @project).to_s, "new-environment-path" => new_project_environment_path(@project), "help-page-path" => help_page_path("ci/environments"), - "deploy-boards-help-path" => help_page_path("user/project/deploy_boards", anchor: "enabling-deploy-boards"), - "css-class" => container_class } } + "deploy-boards-help-path" => help_page_path("user/project/deploy_boards", anchor: "enabling-deploy-boards") } } diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml index 7b847a85686..aab30af5ed4 100644 --- a/app/views/projects/environments/metrics.html.haml +++ b/app/views/projects/environments/metrics.html.haml @@ -1,5 +1,4 @@ -- @no_container = true - page_title _("Metrics for environment"), @environment.name -.prometheus-container{ class: container_class } +.prometheus-container #prometheus-graphs{ data: metrics_data(@project, @environment) } diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index c1067fdff78..96edd3f0bd7 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -1,9 +1,7 @@ -- @no_container = true - breadcrumb_title _("Environments") - page_title _("New Environment") -%div{ class: container_class } - %h3.page-title - = _("New environment") - %hr - = render 'form' +%h3.page-title + = _("New environment") +%hr += render 'form' diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 6100fd3ad37..75da151f329 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - add_to_breadcrumbs _("Environments"), project_environments_path(@project) - breadcrumb_title @environment.name - page_title _("Environments") @@ -6,67 +5,66 @@ - content_for :page_specific_javascripts do = stylesheet_link_tag 'page_bundles/xterm' -%div{ class: container_class } - - if can?(current_user, :stop_environment, @environment) - #stop-environment-modal.modal.fade{ tabindex: -1 } - .modal-dialog - .modal-content - .modal-header - %h4.modal-title.d-flex.mw-100 - = s_("Environments|Stopping") - %span.has-tooltip.text-truncate.ml-1.mr-1.flex-fill{ title: @environment.name, data: { container: '#stop-environment-modal' } } - = @environment.name - ? - .modal-body - %p= s_('Environments|Are you sure you want to stop this environment?') - - unless @environment.stop_action_available? - .warning_message - %p= s_('Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file.').html_safe % { emphasis_start: '<strong>'.html_safe, - emphasis_end: '</strong>'.html_safe, - ci_config_link_start: '<a href="https://docs.gitlab.com/ee/ci/yaml/" target="_blank" rel="noopener noreferrer">'.html_safe, - ci_config_link_end: '</a>'.html_safe } - %a{ href: 'https://docs.gitlab.com/ee/ci/environments.html#stopping-an-environment', - target: '_blank', - rel: 'noopener noreferrer' } - = s_('Environments|Learn more about stopping environments') - .modal-footer - = button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' } - = button_to stop_project_environment_path(@project, @environment), class: 'btn btn-danger has-tooltip', method: :post do - = s_('Environments|Stop environment') +- if can?(current_user, :stop_environment, @environment) + #stop-environment-modal.modal.fade{ tabindex: -1 } + .modal-dialog + .modal-content + .modal-header + %h4.modal-title.d-flex.mw-100 + = s_("Environments|Stopping") + %span.has-tooltip.text-truncate.ml-1.mr-1.flex-fill{ title: @environment.name, data: { container: '#stop-environment-modal' } } + = @environment.name + ? + .modal-body + %p= s_('Environments|Are you sure you want to stop this environment?') + - unless @environment.stop_action_available? + .warning_message + %p= s_('Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file.').html_safe % { emphasis_start: '<strong>'.html_safe, + emphasis_end: '</strong>'.html_safe, + ci_config_link_start: '<a href="https://docs.gitlab.com/ee/ci/yaml/" target="_blank" rel="noopener noreferrer">'.html_safe, + ci_config_link_end: '</a>'.html_safe } + %a{ href: 'https://docs.gitlab.com/ee/ci/environments.html#stopping-an-environment', + target: '_blank', + rel: 'noopener noreferrer' } + = s_('Environments|Learn more about stopping environments') + .modal-footer + = button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' } + = button_to stop_project_environment_path(@project, @environment), class: 'btn btn-danger has-tooltip', method: :post do + = s_('Environments|Stop environment') - .top-area - %h3.page-title= @environment.name - .nav-controls.ml-auto.my-2 - = render 'projects/environments/terminal_button', environment: @environment - = render 'projects/environments/external_url', environment: @environment - = render 'projects/environments/metrics_button', environment: @environment - - if can?(current_user, :update_environment, @environment) - = link_to _('Edit'), edit_project_environment_path(@project, @environment), class: 'btn' - - if can?(current_user, :stop_environment, @environment) - = button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal', - target: '#stop-environment-modal' } do - = sprite_icon('stop') - = s_('Environments|Stop') +.top-area + %h3.page-title= @environment.name + .nav-controls.ml-auto.my-2 + = render 'projects/environments/terminal_button', environment: @environment + = render 'projects/environments/external_url', environment: @environment + = render 'projects/environments/metrics_button', environment: @environment + - if can?(current_user, :update_environment, @environment) + = link_to _('Edit'), edit_project_environment_path(@project, @environment), class: 'btn' + - if can?(current_user, :stop_environment, @environment) + = button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal', + target: '#stop-environment-modal' } do + = sprite_icon('stop') + = s_('Environments|Stop') - .environments-container - - if @deployments.blank? - .empty-state - .text-content - %h4.state-title - = _("You don't have any deployments right now.") - %p.blank-state-text - = _("Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here.").html_safe - .text-center - = link_to _("Read more"), help_page_path("ci/environments"), class: "btn btn-success" - - else - .table-holder - .ci-table.environments{ role: 'grid' } - .gl-responsive-table-row.table-row-header{ role: 'row' } - .table-section.section-10{ role: 'columnheader' }= _('ID') - .table-section.section-30{ role: 'columnheader' }= _('Commit') - .table-section.section-25{ role: 'columnheader' }= _('Job') - .table-section.section-15{ role: 'columnheader' }= _('Created') +.environments-container + - if @deployments.blank? + .empty-state + .text-content + %h4.state-title + = _("You don't have any deployments right now.") + %p.blank-state-text + = _("Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here.").html_safe + .text-center + = link_to _("Read more"), help_page_path("ci/environments"), class: "btn btn-success" + - else + .table-holder + .ci-table.environments{ role: 'grid' } + .gl-responsive-table-row.table-row-header{ role: 'row' } + .table-section.section-10{ role: 'columnheader' }= _('ID') + .table-section.section-30{ role: 'columnheader' }= _('Commit') + .table-section.section-25{ role: 'columnheader' }= _('Job') + .table-section.section-15{ role: 'columnheader' }= _('Created') - = render @deployments + = render @deployments - = paginate @deployments, theme: 'gitlab' + = paginate @deployments, theme: 'gitlab' diff --git a/app/views/projects/environments/terminal.html.haml b/app/views/projects/environments/terminal.html.haml index e837d3d56ac..3a705d736f3 100644 --- a/app/views/projects/environments/terminal.html.haml +++ b/app/views/projects/environments/terminal.html.haml @@ -1,23 +1,21 @@ -- @no_container = true - page_title _("Terminal for environment"), @environment.name - content_for :page_specific_javascripts do = stylesheet_link_tag "xterm.css" -%div{ class: container_class } - .top-area - .row - .col-sm-6 - %h3.page-title - = _("Terminal for environment") - = @environment.name +.top-area + .row + .col-sm-6 + %h3.page-title + = _("Terminal for environment") + = @environment.name - .col-sm-6 - .nav-controls - - if @environment.external_url.present? - = link_to @environment.external_url, class: 'btn btn-default', target: '_blank', rel: 'noopener noreferrer nofollow' do - = sprite_icon('external-link') - = render 'projects/deployments/actions', deployment: @environment.last_deployment + .col-sm-6 + .nav-controls + - if @environment.external_url.present? + = link_to @environment.external_url, class: 'btn btn-default', target: '_blank', rel: 'noopener noreferrer nofollow' do + = sprite_icon('external-link') + = render 'projects/deployments/actions', deployment: @environment.last_deployment .terminal-container{ class: container_class } #terminal{ data: { project_path: "#{terminal_project_environment_path(@project, @environment)}.ws" } } diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml index 60160f521ad..2a2ccf8a6de 100644 --- a/app/views/projects/graphs/charts.html.haml +++ b/app/views/projects/graphs/charts.html.haml @@ -1,7 +1,6 @@ -- @no_container = true - page_title _("Contribution Charts") -.repo-charts{ class: container_class } +.repo-charts %h4.sub-header = _("Programming languages used in this repository") @@ -20,7 +19,7 @@ .col-md-8 %canvas#languages-chart{ height: 400 } -.repo-charts{ class: container_class } +.repo-charts .sub-header-block.border-top .row.tree-ref-header diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 4b2417ff43b..6e5e4607232 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -1,7 +1,6 @@ -- @no_container = true - page_title _('Contributors') -.js-graphs-show{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) } +.js-graphs-show{ 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) } .sub-header-block .tree-ref-holder.inline.vertical-align-middle = render 'shared/ref_switcher', destination: 'graphs' diff --git a/app/views/projects/imports/show.html.haml b/app/views/projects/imports/show.html.haml index 422a3a22f87..87b027a1802 100644 --- a/app/views/projects/imports/show.html.haml +++ b/app/views/projects/imports/show.html.haml @@ -1,5 +1,4 @@ - page_title import_in_progress_title -- @no_container = true - @content_class = "limit-container-width" unless fluid_layout .save-project-loader diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 39e9e9171cf..49e482ff1df 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - @can_bulk_update = can?(current_user, :admin_issue, @project) - page_title "Issues" @@ -8,18 +7,17 @@ = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues") - if project_issues(@project).exists? - %div{ class: (container_class) } - .top-area - = render 'shared/issuable/nav', type: :issues - = render "projects/issues/nav_btns" - = render 'shared/issuable/search_bar', type: :issues + .top-area + = render 'shared/issuable/nav', type: :issues + = render "projects/issues/nav_btns" + = render 'shared/issuable/search_bar', type: :issues - - if @can_bulk_update - = render 'shared/issuable/bulk_update_sidebar', type: :issues + - if @can_bulk_update + = render 'shared/issuable/bulk_update_sidebar', type: :issues - .issues-holder - = render 'issues' - - if new_issue_email - = render 'projects/issuable_by_email', email: new_issue_email, issuable_type: 'issue' + .issues-holder + = render 'issues' + - if new_issue_email + = render 'projects/issuable_by_email', email: new_issue_email, issuable_type: 'issue' - else = render 'shared/empty_states/issues', button_path: new_project_issue_path(@project), show_import_button: true diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml index afea5268006..5acb2af08e4 100644 --- a/app/views/projects/jobs/index.html.haml +++ b/app/views/projects/jobs/index.html.haml @@ -1,18 +1,16 @@ -- @no_container = true - page_title "Jobs" -%div{ class: container_class } - .top-area - - build_path_proc = ->(scope) { project_jobs_path(@project, scope: scope) } - = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope +.top-area + - build_path_proc = ->(scope) { project_jobs_path(@project, scope: scope) } + = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope - .nav-controls - - if can?(current_user, :update_build, @project) - - unless @repository.gitlab_ci_yml - = link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info' + .nav-controls + - if can?(current_user, :update_build, @project) + - unless @repository.gitlab_ci_yml + = link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info' - = link_to project_ci_lint_path(@project), class: 'btn btn-default' do - %span CI lint + = link_to project_ci_lint_path(@project), class: 'btn btn-default' do + %span CI lint - .content-list.builds-content-list - = render "table", builds: @builds, project: @project +.content-list.builds-content-list + = render "table", builds: @builds, project: @project diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index a3688c17041..2e322c7db23 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - add_to_breadcrumbs _("Jobs"), project_jobs_path(@project) - breadcrumb_title "##{@build.id}" - page_title "#{@build.name} (##{@build.id})", _("Jobs") @@ -6,11 +5,4 @@ - content_for :page_specific_javascripts do = stylesheet_link_tag 'page_bundles/xterm' -%div{ class: container_class } - #js-job-vue-app{ data: { endpoint: project_job_path(@project, @build, format: :json), project_path: @project.full_path, - deployment_help_url: help_page_path('user/project/clusters/index.html', anchor: 'troubleshooting-failed-deployment-jobs'), - runner_help_url: help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner'), - runner_settings_url: project_runners_path(@build.project, anchor: 'js-runners-settings'), - variables_settings_url: project_variables_path(@build.project, anchor: 'js-cicd-variables-settings'), - page_path: project_job_path(@project, @build), build_status: @build.status, build_stage: @build.stage, log_state: '', - build_options: javascript_build_options } } +#js-job-vue-app{ data: jobs_data } diff --git a/app/views/projects/jobs/terminal.html.haml b/app/views/projects/jobs/terminal.html.haml index f7e7535ee92..5439a4b5d5c 100644 --- a/app/views/projects/jobs/terminal.html.haml +++ b/app/views/projects/jobs/terminal.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - add_to_breadcrumbs 'Jobs', project_jobs_path(@project) - add_to_breadcrumbs "##{@build.id}", project_job_path(@project, @build) - breadcrumb_title 'Terminal' @@ -7,5 +6,5 @@ - content_for :page_specific_javascripts do = stylesheet_link_tag "xterm.css" -.terminal-container{ class: container_class } +.terminal-container #terminal{ data: { project_path: terminal_project_job_path(@project, @build, format: :ws) } } diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml index b9d45e83032..b7996f0dad1 100644 --- a/app/views/projects/labels/edit.html.haml +++ b/app/views/projects/labels/edit.html.haml @@ -1,10 +1,8 @@ -- @no_container = true - add_to_breadcrumbs "Labels", project_labels_path(@project) - breadcrumb_title "Edit" - page_title "Edit", @label.name, "Labels" -%div{ class: container_class } - %h3.page-title - Edit Label - %hr - = render 'shared/labels/form', url: project_label_path(@project, @label), back_path: project_labels_path(@project) +%h3.page-title + Edit Label +%hr += render 'shared/labels/form', url: project_label_path(@project, @label), back_path: project_labels_path(@project) diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 511d7a82d1b..0328751c68c 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - page_title "Labels" - can_admin_label = can?(current_user, :admin_label, @project) - search = params[:search] @@ -7,48 +6,47 @@ - if labels_or_filters #promote-label-modal - %div{ class: container_class } - = render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label + = render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label - .labels-container.prepend-top-10 - - if can_admin_label && search.blank? - %p.text-muted - = _('Labels can be applied to issues and merge requests.') - %br - = _('Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.') + .labels-container.prepend-top-10 + - if can_admin_label && search.blank? + %p.text-muted + = _('Labels can be applied to issues and merge requests.') + %br + = _('Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.') - -# Only show it in the first page - - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') - .prioritized-labels{ class: [('hide' if hide), ('is-not-draggable' unless can_admin_label)] } - %h5.prepend-top-10= _('Prioritized Labels') - .content-list.manage-labels-list.js-prioritized-labels{ data: { url: set_priorities_project_labels_path(@project), sortable: can_admin_label } } - #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" } - = render 'shared/empty_states/priority_labels' - - if @prioritized_labels.present? - = render partial: 'shared/label', collection: @prioritized_labels, as: :label, locals: { force_priority: true, subject: @project } - - elsif search.present? - .nothing-here-block - = _('No prioritised labels with such name or description') + -# Only show it in the first page + - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') + .prioritized-labels{ class: [('hide' if hide), ('is-not-draggable' unless can_admin_label)] } + %h5.prepend-top-10= _('Prioritized Labels') + .content-list.manage-labels-list.js-prioritized-labels{ data: { url: set_priorities_project_labels_path(@project), sortable: can_admin_label } } + #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" } + = render 'shared/empty_states/priority_labels' + - if @prioritized_labels.present? + = render partial: 'shared/label', collection: @prioritized_labels, as: :label, locals: { force_priority: true, subject: @project } + - elsif search.present? + .nothing-here-block + = _('No prioritised labels with such name or description') - - if @labels.present? - .other-labels - %h5{ class: ('hide' if hide) }= _('Other Labels') - .content-list.manage-labels-list.js-other-labels - = render partial: 'shared/label', collection: @labels, as: :label, locals: { subject: @project } - = paginate @labels, theme: 'gitlab' - - elsif search.present? - .other-labels - - if @available_labels.any? - %h5 - = _('Other Labels') - .nothing-here-block - = _('No other labels with such name or description') - - else - .nothing-here-block - = _('No labels with such name or description') - - elsif subscribed.present? - .nothing-here-block - = _('You do not have any subscriptions yet') + - if @labels.present? + .other-labels + %h5{ class: ('hide' if hide) }= _('Other Labels') + .content-list.manage-labels-list.js-other-labels + = render partial: 'shared/label', collection: @labels, as: :label, locals: { subject: @project } + = paginate @labels, theme: 'gitlab' + - elsif search.present? + .other-labels + - if @available_labels.any? + %h5 + = _('Other Labels') + .nothing-here-block + = _('No other labels with such name or description') + - else + .nothing-here-block + = _('No labels with such name or description') + - elsif subscribed.present? + .nothing-here-block + = _('You do not have any subscriptions yet') - else = render 'shared/empty_states/labels' diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index c6739231e36..96ce0eba2c6 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -1,10 +1,8 @@ -- @no_container = true - add_to_breadcrumbs "Labels", project_labels_path(@project) - breadcrumb_title "New" - page_title "New Label" -%div{ class: container_class } - %h3.page-title - New Label - %hr - = render 'shared/labels/form', url: project_labels_path(@project), back_path: project_labels_path(@project) +%h3.page-title + New Label +%hr += render 'shared/labels/form', url: project_labels_path(@project), back_path: project_labels_path(@project) diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 623380c9c61..4e30f09b9a2 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - @can_bulk_update = can?(current_user, :admin_merge_request, @project) - merge_project = merge_request_source_project_for_project(@project) - new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project @@ -6,24 +5,22 @@ - page_title "Merge Requests" - new_merge_request_email = @project.new_issuable_address(current_user, 'merge_request') -%div{ class: container_class } - = render 'projects/last_push' += render 'projects/last_push' - if @project.merge_requests.exists? - %div{ class: container_class } - .top-area - = render 'shared/issuable/nav', type: :merge_requests - .nav-controls - = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path + .top-area + = render 'shared/issuable/nav', type: :merge_requests + .nav-controls + = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path - = render 'shared/issuable/search_bar', type: :merge_requests + = render 'shared/issuable/search_bar', type: :merge_requests - - if @can_bulk_update - = render 'shared/issuable/bulk_update_sidebar', type: :merge_requests + - if @can_bulk_update + = render 'shared/issuable/bulk_update_sidebar', type: :merge_requests - .merge-requests-holder - = render 'merge_requests' - - if new_merge_request_email - = render 'projects/issuable_by_email', email: new_merge_request_email, issuable_type: 'merge_request' + .merge-requests-holder + = render 'merge_requests' + - if new_merge_request_email + = render 'projects/issuable_by_email', email: new_merge_request_email, issuable_type: 'merge_request' - else = render 'shared/empty_states/merge_requests', button_path: new_merge_request_path diff --git a/app/views/projects/milestones/_deprecation_message.html.haml b/app/views/projects/milestones/_deprecation_message.html.haml deleted file mode 100644 index b2cca3690d6..00000000000 --- a/app/views/projects/milestones/_deprecation_message.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -.banner-callout.compact.milestone-deprecation-message.prepend-top-20 - .banner-graphic= image_tag 'illustrations/milestone_removing-page.svg' - .banner-body.prepend-left-10.append-right-10 - %h5.banner-title.prepend-top-0 - = _('The tabs below will be removed in a future version') - %p.milestone-banner-text - = _('Learn more about %{issue_boards_url}, to keep track of issues in multiple lists, using labels, assignees, and milestones. If you’re missing something from issue boards, please create an issue on %{gitlab_issues_url}.').html_safe % { issue_boards_url: link_to(_('issue boards'), help_page_url('user/project/issue_board'), target: '_blank', rel: 'noopener noreferrer'), gitlab_issues_url: link_to(_('GitLab’s issue tracker'), 'https://gitlab.com/gitlab-org/gitlab-ce/issues', target: '_blank', rel: 'noopener noreferrer') } diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml index aa564e00af9..0d040a5cdb3 100644 --- a/app/views/projects/milestones/edit.html.haml +++ b/app/views/projects/milestones/edit.html.haml @@ -1,14 +1,10 @@ -- @no_container = true - breadcrumb_title _('Edit') - add_to_breadcrumbs _('Milestones'), project_milestones_path(@project) - page_title _('Edit'), @milestone.title, _('Milestones') +%h3.page-title + = _('Edit Milestone') -%div{ class: container_class } +%hr - %h3.page-title - = _('Edit Milestone') - - %hr - - = render 'form' += render 'form' diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index a3414c16d73..c89566dac90 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -1,26 +1,24 @@ -- @no_container = true - page_title _('Milestones') -%div{ class: container_class } - .top-area - = render 'shared/milestones_filter', counts: milestone_counts(@project.milestones) +.top-area + = render 'shared/milestones_filter', counts: milestone_counts(@project.milestones) - .nav-controls - = render 'shared/milestones/search_form' - = render 'shared/milestones_sort_dropdown' - - if can?(current_user, :admin_milestone, @project) - = link_to new_project_milestone_path(@project), class: 'btn btn-success qa-new-project-milestone', title: _('New milestone') do - = _('New milestone') + .nav-controls + = render 'shared/milestones/search_form' + = render 'shared/milestones_sort_dropdown' + - if can?(current_user, :admin_milestone, @project) + = link_to new_project_milestone_path(@project), class: 'btn btn-success qa-new-project-milestone', title: _('New milestone') do + = _('New milestone') - .milestones - #delete-milestone-modal - #promote-milestone-modal +.milestones + #delete-milestone-modal + #promote-milestone-modal - %ul.content-list - = render @milestones + %ul.content-list + = render @milestones - - if @milestones.blank? - %li - .nothing-here-block= _('No milestones to show') + - if @milestones.blank? + %li + .nothing-here-block= _('No milestones to show') - = paginate @milestones, theme: 'gitlab' + = paginate @milestones, theme: 'gitlab' diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml index 79207fd70b5..721506a2201 100644 --- a/app/views/projects/milestones/new.html.haml +++ b/app/views/projects/milestones/new.html.haml @@ -1,12 +1,10 @@ -- @no_container = true - add_to_breadcrumbs _('Milestones'), project_milestones_path(@project) - breadcrumb_title _('New') - page_title _('New Milestone') -%div{ class: container_class } - %h3.page-title - = _('New Milestone') +%h3.page-title + = _('New Milestone') - %hr +%hr - = render 'form' += render 'form' diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 1cee8be604a..49d3039d0c9 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -1,71 +1,68 @@ -- @no_container = true - add_to_breadcrumbs _('Milestones'), project_milestones_path(@project) - breadcrumb_title @milestone.title - page_title @milestone.title, _('Milestones') - page_description @milestone.description -%div{ class: container_class } - .detail-page-header.milestone-page-header - .status-box{ class: status_box_class(@milestone) } - - if @milestone.closed? - = _('Closed') - - elsif @milestone.expired? - = _('Past due') - - elsif @milestone.upcoming? - = _('Upcoming') - - else - = _('Open') - .header-text-content - %span.identifier - %strong - = _('Milestone') - - if @milestone.due_date || @milestone.start_date - = milestone_date_range(@milestone) - .milestone-buttons - - if can?(current_user, :admin_milestone, @project) - = link_to edit_project_milestone_path(@project, @milestone), class: 'btn btn-grouped btn-nr' do - = _('Edit') +.detail-page-header.milestone-page-header + .status-box{ class: status_box_class(@milestone) } + - if @milestone.closed? + = _('Closed') + - elsif @milestone.expired? + = _('Past due') + - elsif @milestone.upcoming? + = _('Upcoming') + - else + = _('Open') + .header-text-content + %span.identifier + %strong + = _('Milestone') + - if @milestone.due_date || @milestone.start_date + = milestone_date_range(@milestone) + .milestone-buttons + - if can?(current_user, :admin_milestone, @project) + = link_to edit_project_milestone_path(@project, @milestone), class: 'btn btn-grouped btn-nr' do + = _('Edit') - - if @project.group - %button.js-promote-project-milestone-button.btn.btn-grouped{ data: { toggle: 'modal', - target: '#promote-milestone-modal', - milestone_title: @milestone.title, - group_name: @project.group.name, - url: promote_project_milestone_path(@milestone.project, @milestone), - container: 'body' }, - disabled: true, - type: 'button' } - = _('Promote') - #promote-milestone-modal + - if @project.group + %button.js-promote-project-milestone-button.btn.btn-grouped{ data: { toggle: 'modal', + target: '#promote-milestone-modal', + milestone_title: @milestone.title, + group_name: @project.group.name, + url: promote_project_milestone_path(@milestone.project, @milestone), + container: 'body' }, + disabled: true, + type: 'button' } + = _('Promote') + #promote-milestone-modal - - if @milestone.active? - = link_to _('Close milestone'), project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: 'btn btn-close btn-nr btn-grouped' - - else - = link_to _('Reopen milestone'), project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: 'btn btn-reopen btn-nr btn-grouped' + - if @milestone.active? + = link_to _('Close milestone'), project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: 'btn btn-close btn-nr btn-grouped' + - else + = link_to _('Reopen milestone'), project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: 'btn btn-reopen btn-nr btn-grouped' - = render 'shared/milestones/delete_button' + = render 'shared/milestones/delete_button' - %a.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: '#' } - = icon('angle-double-left') + %a.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: '#' } + = icon('angle-double-left') - .detail-page-description.milestone-detail - %h2.title.qa-milestone-title - = markdown_field(@milestone, :title) +.detail-page-description.milestone-detail + %h2.title.qa-milestone-title + = markdown_field(@milestone, :title) - %div - - if @milestone.description.present? - .description.md - = markdown_field(@milestone, :description) + %div + - if @milestone.description.present? + .description.md + = markdown_field(@milestone, :description) - = render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project += render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project - - if can?(current_user, :read_issue, @project) && @milestone.total_issues_count(current_user).zero? - .alert.alert-success.prepend-top-default - %span= _('Assign some issues to this milestone.') - - elsif @milestone.complete?(current_user) && @milestone.active? - .alert.alert-success.prepend-top-default - %span= _('All issues for this milestone are closed. You may close this milestone now.') +- if can?(current_user, :read_issue, @project) && @milestone.total_issues_count(current_user).zero? + .alert.alert-success.prepend-top-default + %span= _('Assign some issues to this milestone.') +- elsif @milestone.complete?(current_user) && @milestone.active? + .alert.alert-success.prepend-top-default + %span= _('All issues for this milestone are closed. You may close this milestone now.') - = render 'deprecation_message' - = render 'shared/milestones/tabs', milestone: @milestone - = render 'shared/milestones/sidebar', milestone: @milestone, project: @project, affix_offset: 153 += render 'shared/milestones/tabs', milestone: @milestone += render 'shared/milestones/sidebar', milestone: @milestone, project: @project, affix_offset: 153 diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml index f08526f485e..701cb37a1c8 100644 --- a/app/views/projects/network/_head.html.haml +++ b/app/views/projects/network/_head.html.haml @@ -1,9 +1,6 @@ -- @no_container = true +.row-content-block.second-block.content-component-block + .tree-ref-holder + = render partial: 'shared/ref_switcher', locals: {destination: 'graph'} -%div{ class: container_class } - .row-content-block.second-block.content-component-block - .tree-ref-holder - = render partial: 'shared/ref_switcher', locals: {destination: 'graph'} - - .oneline - = _("You can move around the graph by using the arrow keys.") + .oneline + = _("You can move around the graph by using the arrow keys.") diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml index 0580c15ad15..4a0be9e67cb 100644 --- a/app/views/projects/pipeline_schedules/index.html.haml +++ b/app/views/projects/pipeline_schedules/index.html.haml @@ -1,22 +1,20 @@ - breadcrumb_title _("Schedules") -- @no_container = true - page_title _("Pipeline Schedules") -%div{ class: container_class } - #pipeline-schedules-callout{ data: { docs_url: help_page_path('user/project/pipelines/schedules') } } - .top-area - - schedule_path_proc = ->(scope) { pipeline_schedules_path(@project, scope: scope) } - = render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope +#pipeline-schedules-callout{ data: { docs_url: help_page_path('user/project/pipelines/schedules') } } +.top-area + - schedule_path_proc = ->(scope) { pipeline_schedules_path(@project, scope: scope) } + = render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope - - if can?(current_user, :create_pipeline_schedule, @project) - .nav-controls - = link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-success' do - %span= _('New schedule') + - if can?(current_user, :create_pipeline_schedule, @project) + .nav-controls + = link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-success' do + %span= _('New schedule') - - if @schedules.present? - %ul.content-list - = render partial: "table" - - else - .card.bg-light - .nothing-here-block= _("No schedules") +- if @schedules.present? + %ul.content-list + = render partial: "table" +- else + .card.bg-light + .nothing-here-block= _("No schedules") diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml index 6b4110e07d2..c9a50b97fea 100644 --- a/app/views/projects/pipelines/charts.html.haml +++ b/app/views/projects/pipelines/charts.html.haml @@ -1,10 +1,6 @@ -- @no_container = true - page_title _('CI / CD Charts') -%div{ class: container_class } - - #charts.ci-charts - = render 'projects/pipelines/charts/overall' - - %hr - = render 'projects/pipelines/charts/pipelines' +#charts.ci-charts + = render 'projects/pipelines/charts/overall' + %hr + = render 'projects/pipelines/charts/pipelines' diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 4e4638085fd..f64f07487fd 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -1,17 +1,15 @@ -- @no_container = true - page_title _('Pipelines') = render_if_exists "shared/shared_runners_minutes_limit_flash_message" -%div{ 'class' => container_class } - #pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json), - "help-page-path" => help_page_path('ci/quick_start/README'), - "help-auto-devops-path" => help_page_path('topics/autodevops/index.md'), - "empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'), - "error-state-svg-path" => image_path('illustrations/pipelines_failed.svg'), - "no-pipelines-svg-path" => image_path('illustrations/pipelines_pending.svg'), - "can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s, - "new-pipeline-path" => can?(current_user, :create_pipeline, @project) && new_project_pipeline_path(@project), - "ci-lint-path" => can?(current_user, :create_pipeline, @project) && project_ci_lint_path(@project), - "reset-cache-path" => can?(current_user, :admin_pipeline, @project) && reset_cache_project_settings_ci_cd_path(@project) , - "has-gitlab-ci" => (@project.has_ci? && @project.builds_enabled?).to_s } } +#pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json), + "help-page-path" => help_page_path('ci/quick_start/README'), + "help-auto-devops-path" => help_page_path('topics/autodevops/index.md'), + "empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'), + "error-state-svg-path" => image_path('illustrations/pipelines_failed.svg'), + "no-pipelines-svg-path" => image_path('illustrations/pipelines_pending.svg'), + "can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s, + "new-pipeline-path" => can?(current_user, :create_pipeline, @project) && new_project_pipeline_path(@project), + "ci-lint-path" => can?(current_user, :create_pipeline, @project) && project_ci_lint_path(@project), + "reset-cache-path" => can?(current_user, :admin_pipeline, @project) && reset_cache_project_settings_ci_cd_path(@project) , + "has-gitlab-ci" => (@project.has_ci? && @project.builds_enabled?).to_s } } diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml index 8a6d7b082e3..2b2133b8296 100644 --- a/app/views/projects/pipelines/show.html.haml +++ b/app/views/projects/pipelines/show.html.haml @@ -1,9 +1,8 @@ -- @no_container = true - add_to_breadcrumbs _('Pipelines'), project_pipelines_path(@project) - breadcrumb_title "##{@pipeline.id}" - page_title _('Pipeline') -.js-pipeline-container{ class: container_class, data: { controller_action: "#{controller.action_name}" } } +.js-pipeline-container{ data: { controller_action: "#{controller.action_name}" } } #js-pipeline-header-vue.pipeline-header-container - if @pipeline.commit.present? 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 d1c09e83fd3..a583eb39eb3 100644 --- a/app/views/projects/project_templates/_built_in_templates.html.haml +++ b/app/views/projects/project_templates/_built_in_templates.html.haml @@ -1,5 +1,5 @@ - Gitlab::ProjectTemplate.all.each do |template| - .template-option.d-flex.align-items-center + .template-option.d-flex.align-items-center{ data: { qa_selector: 'template_option_row' } } .logo.append-right-10.px-1 = image_tag template.logo, size: 32, class: "btn-template-icon icon-#{template.name}" .description @@ -13,5 +13,5 @@ = _("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: "template_use", track_property: template.name, track_event: "click_button", track_value: "" } } - %span + %span{ data: { qa_selector: 'use_template_button' } } = _("Use template") diff --git a/app/views/projects/releases/index.html.haml b/app/views/projects/releases/index.html.haml index 28bb4e032eb..326b83c856e 100644 --- a/app/views/projects/releases/index.html.haml +++ b/app/views/projects/releases/index.html.haml @@ -1,5 +1,3 @@ -- @no_container = true - page_title _('Releases') -%div{ class: container_class } - #js-releases-page{ data: { project_id: @project.id, illustration_path: image_path('illustrations/releases.svg'), documentation_path: help_page_path('user/project/releases/index') } } +#js-releases-page{ data: { project_id: @project.id, illustration_path: image_path('illustrations/releases.svg'), documentation_path: help_page_path('user/project/releases/index') } } diff --git a/app/views/projects/serverless/functions/index.html.haml b/app/views/projects/serverless/functions/index.html.haml index bac6c76684b..09f4e556949 100644 --- a/app/views/projects/serverless/functions/index.html.haml +++ b/app/views/projects/serverless/functions/index.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - @content_class = "limit-container-width" unless fluid_layout - breadcrumb_title 'Serverless' - page_title 'Serverless' @@ -10,7 +9,7 @@ clusters_path: clusters_path, help_path: help_page_path('user/project/clusters/serverless/index') } } -%div{ class: [container_class, ('limit-container-width' unless fluid_layout)] } +%div{ class: [('limit-container-width' unless fluid_layout)] } .js-serverless-functions-notice .flash-container diff --git a/app/views/projects/serverless/functions/show.html.haml b/app/views/projects/serverless/functions/show.html.haml index d1fe208ce60..79bb943d6ed 100644 --- a/app/views/projects/serverless/functions/show.html.haml +++ b/app/views/projects/serverless/functions/show.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - @content_class = "limit-container-width" unless fluid_layout - clusters_path = project_clusters_path(@project) - help_path = help_page_path('user/project/clusters/serverless/index') @@ -12,7 +11,7 @@ clusters_path: clusters_path, help_path: help_path } } -%div{ class: [container_class, ('limit-container-width' unless fluid_layout)] } +%div{ class: [('limit-container-width' unless fluid_layout)] } .serverless-function-details#js-serverless-function-details .js-serverless-function-notice diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml index 04b77fb987a..1d5d90593ae 100644 --- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml +++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml @@ -1,3 +1,14 @@ +- has_base_domain = @project.all_clusters.any? { |cluster| cluster.base_domain && !cluster.base_domain.empty? } + +- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe +- link_end = '</a>'.html_safe + +- kubernetes_cluster_path = help_page_path('user/project/clusters/index') +- kubernetes_cluster_link_start = link_start % { url: kubernetes_cluster_path } + +- base_domain_path = help_page_path('user/project/clusters/index', anchor: 'base-domain') +- base_domain_link_start = link_start % { url: base_domain_path } + .row .col-lg-12 = form_for @project, url: project_settings_ci_cd_path(@project, anchor: 'autodevops-settings') do |f| @@ -19,9 +30,10 @@ .card-footer.js-extra-settings{ class: auto_devops_enabled || 'hidden' } - if @project.all_clusters.empty? %p.settings-message.text-center - - kubernetes_cluster_link = help_page_path('user/project/clusters/index') - - kubernetes_cluster_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: kubernetes_cluster_link } - = s_('CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly.').html_safe % { kubernetes_cluster_start: kubernetes_cluster_start, kubernetes_cluster_end: '</a>'.html_safe } + = s_('CICD|You must add a %{kubernetes_cluster_link_start}Kubernetes cluster integration%{link_end} to this project with a domain in order for your deployment strategy to work correctly.').html_safe % { kubernetes_cluster_link_start: kubernetes_cluster_link_start, link_end: link_end } + - elsif !has_base_domain + %p.settings-message.text-center + = s_('CICD|You must add a %{base_domain_link_start}base domain%{link_end} to your %{kubernetes_cluster_link_start}Kubernetes cluster%{link_end} in order for your deployment strategy to work.').html_safe % { base_domain_link_start: base_domain_link_start, kubernetes_cluster_link_start: kubernetes_cluster_link_start, link_end: link_end } %label.prepend-top-10 %strong= s_('CICD|Deployment strategy') .form-check diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index c87a084740b..b58af545439 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - breadcrumb_title _("Details") - @content_class = "limit-container-width" unless fluid_layout @@ -11,7 +10,7 @@ - signatures_path = project_signatures_path(@project, @project.default_branch) .js-signature-container{ data: { 'signatures-path': signatures_path } } -%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } +%div{ class: [("limit-container-width" unless fluid_layout)] } = render "projects/last_push" = render "home_panel" diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 1f0de1e2603..6ad7cf1848f 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -1,10 +1,9 @@ -- @no_container = true - @sort ||= sort_value_recently_updated - page_title s_('TagsPage|Tags') = content_for :meta_tags do = auto_discovery_link_tag(:atom, project_tags_url(@project, rss_url_options), title: "#{@project.name} tags") -.flex-list{ class: container_class } +.flex-list .top-area.adjust .nav-text.row-main-content = s_('TagsPage|Tags give the ability to mark specific points in history as being important') diff --git a/app/views/projects/tags/releases/edit.html.haml b/app/views/projects/tags/releases/edit.html.haml index e4efeed04f0..40d886ff1af 100644 --- a/app/views/projects/tags/releases/edit.html.haml +++ b/app/views/projects/tags/releases/edit.html.haml @@ -1,22 +1,19 @@ -- @no_container = true - add_to_breadcrumbs "Tags", project_tags_path(@project) - breadcrumb_title @tag.name - page_title "Edit", @tag.name, "Tags" -%div{ class: container_class } - .sub-header-block.no-bottom-space - .oneline - .title - Release notes for tag - %strong= @tag.name +.sub-header-block.no-bottom-space + .oneline + .title + Release notes for tag + %strong= @tag.name - - = form_for(@release, method: :put, url: project_tag_release_path(@project, @tag.name), - html: { class: 'common-note-form release-form js-quick-submit' }) do |f| - = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do - = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here…" - = render 'shared/notes/hints' - .error-alert - .prepend-top-default - = f.submit 'Save changes', class: 'btn btn-success' - = link_to "Cancel", project_tag_path(@project, @tag.name), class: "btn btn-default btn-cancel" += form_for(@release, method: :put, url: project_tag_release_path(@project, @tag.name), + html: { class: 'common-note-form release-form js-quick-submit' }) do |f| + = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do + = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here…" + = render 'shared/notes/hints' + .error-alert + .prepend-top-default + = f.submit 'Save changes', class: 'btn btn-success' + = link_to "Cancel", project_tag_path(@project, @tag.name), class: "btn btn-default btn-cancel" diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index 78cce58938e..417cd7a8fee 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -1,45 +1,43 @@ -- @no_container = true - add_to_breadcrumbs s_('TagsPage|Tags'), project_tags_path(@project) - breadcrumb_title @tag.name - page_title @tag.name, s_('TagsPage|Tags') -%div{ class: container_class } - .top-area.multi-line.flex-wrap - .nav-text - .title - %span.item-title.ref-name - = icon('tag') - = @tag.name - - if protected_tag?(@project, @tag) - %span.badge.badge-success - = s_('TagsPage|protected') - - if @commit - = render 'projects/branches/commit', commit: @commit, project: @project - - else - = s_("TagsPage|Can't find HEAD commit for this tag") +.top-area.multi-line.flex-wrap + .nav-text + .title + %span.item-title.ref-name + = icon('tag') + = @tag.name + - if protected_tag?(@project, @tag) + %span.badge.badge-success + = s_('TagsPage|protected') + - if @commit + = render 'projects/branches/commit', commit: @commit, project: @project + - else + = s_("TagsPage|Can't find HEAD commit for this tag") - .nav-controls - - if can?(current_user, :admin_tag, @project) - = link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn btn-edit controls-item has-tooltip', title: s_('TagsPage|Edit release notes') do - = icon("pencil") - = link_to project_tree_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse files') do - = sprite_icon('folder-open') - = link_to project_commits_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse commits') do - = icon('history') - .btn-container.controls-item - = render 'projects/buttons/download', project: @project, ref: @tag.name - - if can?(current_user, :admin_tag, @project) - .btn-container.controls-item-full - = link_to project_tag_path(@project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: @tag.name } } do - %i.fa.fa-trash-o + .nav-controls + - if can?(current_user, :admin_tag, @project) + = link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn btn-edit controls-item has-tooltip', title: s_('TagsPage|Edit release notes') do + = icon("pencil") + = link_to project_tree_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse files') do + = sprite_icon('folder-open') + = link_to project_commits_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse commits') do + = icon('history') + .btn-container.controls-item + = render 'projects/buttons/download', project: @project, ref: @tag.name + - if can?(current_user, :admin_tag, @project) + .btn-container.controls-item-full + = link_to project_tag_path(@project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: @tag.name } } do + %i.fa.fa-trash-o - - if @tag.message.present? - %pre.wrap - = strip_gpg_signature(@tag.message) + - if @tag.message.present? + %pre.wrap + = strip_gpg_signature(@tag.message) - .append-bottom-default.prepend-top-default - - if @release.description.present? - .description.md - = markdown_field(@release, :description) - - else - = s_('TagsPage|This tag has no release notes.') +.append-bottom-default.prepend-top-default + - if @release.description.present? + .description.md + = markdown_field(@release, :description) + - else + = s_('TagsPage|This tag has no release notes.') diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 9d2aee7a8bd..39b29a20df6 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - breadcrumb_title _("Repository") - @content_class = "limit-container-width" unless fluid_layout - signatures_path = namespace_project_signatures_path(namespace_id: @project.namespace.full_path, project_id: @project.path, id: @last_commit) @@ -9,6 +8,5 @@ .js-signature-container{ data: { 'signatures-path': signatures_path } } -%div{ class: [(container_class), ("limit-container-width" unless fluid_layout)] } - = render 'projects/last_push' - = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id) += render 'projects/last_push' += render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id) diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml index 275dc5dbd23..d9dcd8f9acd 100644 --- a/app/views/projects/wikis/pages.html.haml +++ b/app/views/projects/wikis/pages.html.haml @@ -1,34 +1,32 @@ -- @no_container = true - add_to_breadcrumbs "Wiki", project_wiki_path(@project, :home) - breadcrumb_title s_("Wiki|Pages") - page_title s_("Wiki|Pages"), _("Wiki") - sort_title = wiki_sort_title(params[:sort]) -%div{ class: container_class } - .wiki-page-header.top-area.flex-column.flex-lg-row +.wiki-page-header.top-area.flex-column.flex-lg-row - .nav-text.flex-fill - %h2.wiki-page-title - = s_("Wiki|Wiki Pages") + .nav-text.flex-fill + %h2.wiki-page-title + = s_("Wiki|Wiki Pages") - .nav-controls.pb-md-3.pb-lg-0 - = link_to project_wikis_git_access_path(@project), class: 'btn' do - = icon('cloud-download') - = _("Clone repository") + .nav-controls.pb-md-3.pb-lg-0 + = link_to project_wikis_git_access_path(@project), class: 'btn' do + = icon('cloud-download') + = _("Clone repository") - .dropdown.inline.wiki-sort-dropdown + .dropdown.inline.wiki-sort-dropdown + .btn-group{ role: 'group' } .btn-group{ role: 'group' } - .btn-group{ role: 'group' } - %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' } - = sort_title - = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort - %li - = sortable_item(s_("Wiki|Title"), project_wikis_pages_path(@project, sort: ProjectWiki::TITLE_ORDER), sort_title) - = sortable_item(s_("Wiki|Created date"), project_wikis_pages_path(@project, sort: ProjectWiki::CREATED_AT_ORDER), sort_title) - = wiki_sort_controls(@project, params[:sort], params[:direction]) + %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' } + = sort_title + = icon('chevron-down') + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort + %li + = sortable_item(s_("Wiki|Title"), project_wikis_pages_path(@project, sort: ProjectWiki::TITLE_ORDER), sort_title) + = sortable_item(s_("Wiki|Created date"), project_wikis_pages_path(@project, sort: ProjectWiki::CREATED_AT_ORDER), sort_title) + = wiki_sort_controls(@project, params[:sort], params[:direction]) - %ul.wiki-pages-list.content-list - = render @wiki_entries, context: 'pages' +%ul.wiki-pages-list.content-list + = render @wiki_entries, context: 'pages' - = paginate @wiki_pages, theme: 'gitlab' += paginate @wiki_pages, theme: 'gitlab' diff --git a/app/views/shared/empty_states/_priority_labels.html.haml b/app/views/shared/empty_states/_priority_labels.html.haml index bba3475d244..a93f6e4c795 100644 --- a/app/views/shared/empty_states/_priority_labels.html.haml +++ b/app/views/shared/empty_states/_priority_labels.html.haml @@ -1,4 +1,6 @@ .text-center .svg-content.qa-label-svg = image_tag 'illustrations/priority_labels.svg' - %p Star labels to start sorting by priority + - if can?(current_user, :admin_label, @project) + %p + = _("Star labels to start sorting by priority") diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 73bee7c2586..e1c75d5d0f4 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -1,9 +1,9 @@ - @hide_top_links = true - @hide_breadcrumbs = true +- @no_container = true - page_title @user.name - page_description @user.bio - header_title @user.name, user_path(@user) -- @no_container = true = content_for :meta_tags do = auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity") diff --git a/app/workers/hashed_storage/base_worker.rb b/app/workers/hashed_storage/base_worker.rb index 816e0504db6..237e278c537 100644 --- a/app/workers/hashed_storage/base_worker.rb +++ b/app/workers/hashed_storage/base_worker.rb @@ -5,7 +5,7 @@ module HashedStorage include ExclusiveLeaseGuard LEASE_TIMEOUT = 30.seconds.to_i - LEASE_KEY_SEGMENT = 'project_migrate_hashed_storage_worker'.freeze + LEASE_KEY_SEGMENT = 'project_migrate_hashed_storage_worker' protected diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb index 30fba038937..7e002d8822c 100644 --- a/app/workers/stuck_ci_jobs_worker.rb +++ b/app/workers/stuck_ci_jobs_worker.rb @@ -4,7 +4,7 @@ class StuckCiJobsWorker include ApplicationWorker include CronjobQueue - EXCLUSIVE_LEASE_KEY = 'stuck_ci_builds_worker_lease'.freeze + EXCLUSIVE_LEASE_KEY = 'stuck_ci_builds_worker_lease' BUILD_RUNNING_OUTDATED_TIMEOUT = 1.hour BUILD_PENDING_OUTDATED_TIMEOUT = 1.day diff --git a/bin/rspec-stackprof b/bin/rspec-stackprof index 810863ea4a0..8058d165196 100755 --- a/bin/rspec-stackprof +++ b/bin/rspec-stackprof @@ -3,7 +3,7 @@ require 'bundler/setup' require 'stackprof' $:.unshift 'spec' -require 'rails_helper' +require 'spec_helper' filename = ARGV[0].split('/').last interval = ENV.fetch('INTERVAL', 1000).to_i diff --git a/changelogs/unreleased/36765-flash-notification.yml b/changelogs/unreleased/36765-flash-notification.yml new file mode 100644 index 00000000000..3229cf6235a --- /dev/null +++ b/changelogs/unreleased/36765-flash-notification.yml @@ -0,0 +1,5 @@ +--- +title: Make flash notifications sticky +merge_request: 30141 +author: +type: changed diff --git a/changelogs/unreleased/40096-allow-ci-token-to-delete-from-registry.yml b/changelogs/unreleased/40096-allow-ci-token-to-delete-from-registry.yml new file mode 100644 index 00000000000..3e5de08f6ae --- /dev/null +++ b/changelogs/unreleased/40096-allow-ci-token-to-delete-from-registry.yml @@ -0,0 +1,5 @@ +--- +title: Allow $CI_REGISTRY_USER to delete tags +merge_request: 31796 +author: +type: added diff --git a/changelogs/unreleased/51372-remove-milestone-tabs-deprecation-message.yml b/changelogs/unreleased/51372-remove-milestone-tabs-deprecation-message.yml new file mode 100644 index 00000000000..5f05b150b93 --- /dev/null +++ b/changelogs/unreleased/51372-remove-milestone-tabs-deprecation-message.yml @@ -0,0 +1,5 @@ +--- +title: Remove deprecation message for milestone tabs +merge_request: 32252 +author: +type: other diff --git a/changelogs/unreleased/60372-milestone-link-prevent-delete-issue-after-move-it-to-another-projec.yml b/changelogs/unreleased/60372-milestone-link-prevent-delete-issue-after-move-it-to-another-projec.yml new file mode 100644 index 00000000000..d9f4c17a668 --- /dev/null +++ b/changelogs/unreleased/60372-milestone-link-prevent-delete-issue-after-move-it-to-another-projec.yml @@ -0,0 +1,5 @@ +--- +title: Add service to transfer Group Milestones when transferring a Project +merge_request: 31778 +author: +type: added diff --git a/changelogs/unreleased/62402-milestone-release-be.yml b/changelogs/unreleased/62402-milestone-release-be.yml new file mode 100644 index 00000000000..3b1f6edfe6b --- /dev/null +++ b/changelogs/unreleased/62402-milestone-release-be.yml @@ -0,0 +1,5 @@ +--- +title: Allow milestones to be associated with a release (backend) +merge_request: 30816 +author: +type: added diff --git a/changelogs/unreleased/62591-fix-milestone-due-date-today-wording.yml b/changelogs/unreleased/62591-fix-milestone-due-date-today-wording.yml new file mode 100644 index 00000000000..532b582f407 --- /dev/null +++ b/changelogs/unreleased/62591-fix-milestone-due-date-today-wording.yml @@ -0,0 +1,5 @@ +--- +title: Fix wording on milestone due date when milestone is due today +merge_request: 32096 +author: +type: changed diff --git a/changelogs/unreleased/64009-show-a-meaningful-error-message-when-due-quick_actions-command-fail.yml b/changelogs/unreleased/64009-show-a-meaningful-error-message-when-due-quick_actions-command-fail.yml new file mode 100644 index 00000000000..8624b868686 --- /dev/null +++ b/changelogs/unreleased/64009-show-a-meaningful-error-message-when-due-quick_actions-command-fail.yml @@ -0,0 +1,5 @@ +--- +title: Show meaningful message on /due quick action with invalid date +merge_request: 32349 +author: Jacopo Beschi @jacopo-beschi +type: changed diff --git a/changelogs/unreleased/66454-base-components.yml b/changelogs/unreleased/66454-base-components.yml new file mode 100644 index 00000000000..7a40a66f122 --- /dev/null +++ b/changelogs/unreleased/66454-base-components.yml @@ -0,0 +1,5 @@ +--- +title: Creates base components for the new job log +merge_request: +author: +type: added diff --git a/changelogs/unreleased/66454-utils-parser.yml b/changelogs/unreleased/66454-utils-parser.yml new file mode 100644 index 00000000000..80cd699937f --- /dev/null +++ b/changelogs/unreleased/66454-utils-parser.yml @@ -0,0 +1,5 @@ +--- +title: Creates utility parser for the job log +merge_request: 32555 +author: +type: added diff --git a/changelogs/unreleased/ab-add-index-for-ci-builds-metrics.yml b/changelogs/unreleased/ab-add-index-for-ci-builds-metrics.yml new file mode 100644 index 00000000000..03a37dc0c04 --- /dev/null +++ b/changelogs/unreleased/ab-add-index-for-ci-builds-metrics.yml @@ -0,0 +1,5 @@ +--- +title: Create partial index for gitlab-monitor CI metrics +merge_request: 32546 +author: +type: performance diff --git a/changelogs/unreleased/ab-remove-support-bot-column.yml b/changelogs/unreleased/ab-remove-support-bot-column.yml new file mode 100644 index 00000000000..d256ded0410 --- /dev/null +++ b/changelogs/unreleased/ab-remove-support-bot-column.yml @@ -0,0 +1,5 @@ +--- +title: Remove Users.support_bot column +merge_request: 32554 +author: +type: other diff --git a/changelogs/unreleased/ab-routable-nplus1.yml b/changelogs/unreleased/ab-routable-nplus1.yml new file mode 100644 index 00000000000..7b59bc78dae --- /dev/null +++ b/changelogs/unreleased/ab-routable-nplus1.yml @@ -0,0 +1,5 @@ +--- +title: Preload routes information to fix N+1 issue +merge_request: 32352 +author: +type: performance diff --git a/changelogs/unreleased/ab-unconfirmed-email-index.yml b/changelogs/unreleased/ab-unconfirmed-email-index.yml new file mode 100644 index 00000000000..3887cd87e41 --- /dev/null +++ b/changelogs/unreleased/ab-unconfirmed-email-index.yml @@ -0,0 +1,5 @@ +--- +title: Create index for users.unconfirmed_email +merge_request: 32664 +author: +type: performance diff --git a/changelogs/unreleased/add-notification-reason-to-note-emails.yml b/changelogs/unreleased/add-notification-reason-to-note-emails.yml new file mode 100644 index 00000000000..c27247b22cb --- /dev/null +++ b/changelogs/unreleased/add-notification-reason-to-note-emails.yml @@ -0,0 +1,5 @@ +--- +title: Add X-GitLab-NotificationReason header to note emails +merge_request: 32422 +author: +type: fixed diff --git a/changelogs/unreleased/change-prioritized-labels-empty-state-message.yml b/changelogs/unreleased/change-prioritized-labels-empty-state-message.yml new file mode 100644 index 00000000000..d5df889d15c --- /dev/null +++ b/changelogs/unreleased/change-prioritized-labels-empty-state-message.yml @@ -0,0 +1,5 @@ +--- +title: Change prioritized labels empty state message +merge_request: 32338 +author: Lee Tickett +type: other diff --git a/changelogs/unreleased/change-role-system-hook.yml b/changelogs/unreleased/change-role-system-hook.yml new file mode 100644 index 00000000000..adc9e43b1f2 --- /dev/null +++ b/changelogs/unreleased/change-role-system-hook.yml @@ -0,0 +1,5 @@ +--- +title: Add system hooks for project/group membership updates +merge_request: 32371 +author: Brandon Williams +type: added diff --git a/changelogs/unreleased/cluster-form-ca-cert-larger.yml b/changelogs/unreleased/cluster-form-ca-cert-larger.yml new file mode 100644 index 00000000000..ed38d003a37 --- /dev/null +++ b/changelogs/unreleased/cluster-form-ca-cert-larger.yml @@ -0,0 +1,5 @@ +--- +title: Expand textarea for CA cert in cluster form +merge_request: 32508 +author: +type: fixed diff --git a/changelogs/unreleased/fix-regression-remove-installation-pod.yml b/changelogs/unreleased/fix-regression-remove-installation-pod.yml new file mode 100644 index 00000000000..1ed72f1189d --- /dev/null +++ b/changelogs/unreleased/fix-regression-remove-installation-pod.yml @@ -0,0 +1,5 @@ +--- +title: Fix removal of install pods +merge_request: 32667 +author: +type: fixed diff --git a/changelogs/unreleased/fj-66723-add-dns-rebinding-protection-check.yml b/changelogs/unreleased/fj-66723-add-dns-rebinding-protection-check.yml new file mode 100644 index 00000000000..c1372a4a73e --- /dev/null +++ b/changelogs/unreleased/fj-66723-add-dns-rebinding-protection-check.yml @@ -0,0 +1,5 @@ +--- +title: Allow not resolvable urls when dns rebind protection is disabled +merge_request: 32523 +author: +type: fixed diff --git a/changelogs/unreleased/fj-remove-dns-protection-when-validating.yml b/changelogs/unreleased/fj-remove-dns-protection-when-validating.yml new file mode 100644 index 00000000000..9c74f8d69c7 --- /dev/null +++ b/changelogs/unreleased/fj-remove-dns-protection-when-validating.yml @@ -0,0 +1,5 @@ +--- +title: Avoid checking dns rebind protection when validating +merge_request: 32577 +author: +type: fixed diff --git a/changelogs/unreleased/issue_54042.yml b/changelogs/unreleased/issue_54042.yml new file mode 100644 index 00000000000..465c7426e93 --- /dev/null +++ b/changelogs/unreleased/issue_54042.yml @@ -0,0 +1,5 @@ +--- +title: Let project reporters create issue from group boards +merge_request: 29866 +author: +type: fixed diff --git a/changelogs/unreleased/je-add-cluster-domain-warning.yml b/changelogs/unreleased/je-add-cluster-domain-warning.yml new file mode 100644 index 00000000000..e7d244f730f --- /dev/null +++ b/changelogs/unreleased/je-add-cluster-domain-warning.yml @@ -0,0 +1,5 @@ +--- +title: Add cluster domain warning +merge_request: 32260 +author: +type: changed diff --git a/changelogs/unreleased/je-add-group-deployments-page-fe.yml b/changelogs/unreleased/je-add-group-deployments-page-fe.yml new file mode 100644 index 00000000000..91333087eb5 --- /dev/null +++ b/changelogs/unreleased/je-add-group-deployments-page-fe.yml @@ -0,0 +1,5 @@ +--- +title: Add ability to see project deployments at cluster level (FE) +merge_request: 31575 +author: +type: added diff --git a/changelogs/unreleased/persist-needs-error.yml b/changelogs/unreleased/persist-needs-error.yml new file mode 100644 index 00000000000..96aaa4d11a3 --- /dev/null +++ b/changelogs/unreleased/persist-needs-error.yml @@ -0,0 +1,5 @@ +--- +title: Persist `needs:` validation as config error +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/remove-vue-resource-from-group-service.yml b/changelogs/unreleased/remove-vue-resource-from-group-service.yml new file mode 100644 index 00000000000..771d301cabf --- /dev/null +++ b/changelogs/unreleased/remove-vue-resource-from-group-service.yml @@ -0,0 +1,5 @@ +--- +title: Remove vue resource from group service +merge_request: +author: Lee Tickett +type: other diff --git a/changelogs/unreleased/remove-vue-resource-from-issue.yml b/changelogs/unreleased/remove-vue-resource-from-issue.yml new file mode 100644 index 00000000000..b2de1b0913a --- /dev/null +++ b/changelogs/unreleased/remove-vue-resource-from-issue.yml @@ -0,0 +1,5 @@ +--- +title: Remove vue resource from issue +merge_request: 32421 +author: Lee Tickett +type: other diff --git a/changelogs/unreleased/sh-suppress-diverging-count-commits-request.yml b/changelogs/unreleased/sh-suppress-diverging-count-commits-request.yml new file mode 100644 index 00000000000..68b95ff9318 --- /dev/null +++ b/changelogs/unreleased/sh-suppress-diverging-count-commits-request.yml @@ -0,0 +1,5 @@ +--- +title: Skip requesting diverging commit counts if no branches are listed +merge_request: 32496 +author: +type: performance diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 20b1020e025..e3693f612e3 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -158,6 +158,7 @@ production: &base # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + # Please be aware that a placeholder is required for the Service Desk feature to work. address: "gitlab-incoming+%{key}@gmail.com" # Email account username @@ -431,6 +432,7 @@ production: &base # key: config/registry.key # path: shared/registry # issuer: gitlab-issuer + # notification_secret: '' # only set it when you use Geo replication feature without built-in Registry # Add notification settings if you plan to use Geo Replication for the registry # notifications: diff --git a/config/routes.rb b/config/routes.rb index c333550f758..02a405a91f8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -67,7 +67,7 @@ Rails.application.routes.draw do get 'health_check(/:checks)' => 'health_check#index', as: :health_check scope path: '-' do - # '/-/health' implemented by BasicHealthMiddleware + # '/-/health' implemented by BasicHealthCheck middleware get 'liveness' => 'health#liveness' get 'readiness' => 'health#readiness' resources :metrics, only: [:index] diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index ea165508d29..7edec576f9a 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -102,6 +102,7 @@ - [create_github_webhook, 2] - [geo, 1] - [repository_update_mirror, 1] + - [repository_push_audit_event, 1] - [new_epic, 2] - [project_import_schedule, 1] - [project_update_repository_storage, 1] diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb index 78ceb74da65..9d293f425e6 100644 --- a/db/fixtures/development/17_cycle_analytics.rb +++ b/db/fixtures/development/17_cycle_analytics.rb @@ -18,6 +18,7 @@ class Gitlab::Seeder::CycleAnalytics # Milestones / Labels Timecop.travel 5.days.from_now + if index.even? issue_metrics.first_associated_with_milestone_at = rand(6..12).hours.from_now else @@ -146,7 +147,7 @@ class Gitlab::Seeder::CycleAnalytics commit_sha = issue.project.repository.create_file(@user, filename, "content", message: "Commit for #{issue.to_reference}", branch_name: branch_name) issue.project.repository.commit(commit_sha) - Git::BranchPushService.new( + ::Git::BranchPushService.new( issue.project, @user, oldrev: issue.project.repository.commit("master").sha, @@ -182,7 +183,8 @@ class Gitlab::Seeder::CycleAnalytics ref: "refs/heads/#{merge_request.source_branch}") pipeline = service.execute(:push, ignore_skip_ci: true, save_on_errors: false) - pipeline.builds.map(&:run!) + pipeline.builds.each(&:enqueue) # make sure all pipelines in pending state + pipeline.builds.each(&:run!) pipeline.update_status end end diff --git a/db/migrate/20190722144316_create_milestone_releases_table.rb b/db/migrate/20190722144316_create_milestone_releases_table.rb new file mode 100644 index 00000000000..55878bcec41 --- /dev/null +++ b/db/migrate/20190722144316_create_milestone_releases_table.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class CreateMilestoneReleasesTable < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + create_table :milestone_releases do |t| + t.references :milestone, foreign_key: { on_delete: :cascade }, null: false, index: false + t.references :release, foreign_key: { on_delete: :cascade }, null: false + end + + add_index :milestone_releases, [:milestone_id, :release_id], unique: true, name: 'index_miletone_releases_on_milestone_and_release' + end + + def down + drop_table :milestone_releases + end +end diff --git a/db/migrate/20190826100605_add_group_column_to_events.rb b/db/migrate/20190826100605_add_group_column_to_events.rb new file mode 100644 index 00000000000..cd7b2b1d96a --- /dev/null +++ b/db/migrate/20190826100605_add_group_column_to_events.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddGroupColumnToEvents < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def change + add_reference :events, :group, index: true, foreign_key: { to_table: :namespaces, on_delete: :cascade } + end +end diff --git a/db/migrate/20190828110802_add_not_null_constraints_to_prometheus_metrics_y_label_and_unit.rb b/db/migrate/20190828110802_add_not_null_constraints_to_prometheus_metrics_y_label_and_unit.rb new file mode 100644 index 00000000000..6f3650ca966 --- /dev/null +++ b/db/migrate/20190828110802_add_not_null_constraints_to_prometheus_metrics_y_label_and_unit.rb @@ -0,0 +1,8 @@ +class AddNotNullConstraintsToPrometheusMetricsYLabelAndUnit < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def change + change_column_null(:prometheus_metrics, :y_label, false) + change_column_null(:prometheus_metrics, :unit, false) + end +end diff --git a/db/migrate/20190902152329_add_index_for_ci_builds_metrics.rb b/db/migrate/20190902152329_add_index_for_ci_builds_metrics.rb new file mode 100644 index 00000000000..b755d60e311 --- /dev/null +++ b/db/migrate/20190902152329_add_index_for_ci_builds_metrics.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class AddIndexForCiBuildsMetrics < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + INDEX_NAME = 'ci_builds_gitlab_monitor_metrics' + + def up + add_concurrent_index(:ci_builds, [:status, :created_at, :project_id], where: "type = 'Ci::Build'", name: INDEX_NAME) + end + + def down + remove_concurrent_index_by_name(:ci_builds, INDEX_NAME) + end +end diff --git a/db/migrate/20190904173203_add_index_on_users_unconfirmed_email.rb b/db/migrate/20190904173203_add_index_on_users_unconfirmed_email.rb new file mode 100644 index 00000000000..e78d47f023f --- /dev/null +++ b/db/migrate/20190904173203_add_index_on_users_unconfirmed_email.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddIndexOnUsersUnconfirmedEmail < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :users, :unconfirmed_email, where: 'unconfirmed_email IS NOT NULL' + end + + def down + remove_concurrent_index :users, :unconfirmed_email, where: 'unconfirmed_email IS NOT NULL' + end +end diff --git a/db/post_migrate/20190902160015_remove_support_bot_column_from_users.rb b/db/post_migrate/20190902160015_remove_support_bot_column_from_users.rb new file mode 100644 index 00000000000..80d69b57f5d --- /dev/null +++ b/db/post_migrate/20190902160015_remove_support_bot_column_from_users.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class RemoveSupportBotColumnFromUsers < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + return unless column_exists?(:users, :support_bot) + + remove_column :users, :support_bot + end + + def down + # no-op because the column should not exist in the previous version + end +end diff --git a/db/schema.rb b/db/schema.rb index 5b89cdf0b98..98c4403efe1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_09_02_131045) do +ActiveRecord::Schema.define(version: 2019_09_04_173203) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -605,6 +605,7 @@ ActiveRecord::Schema.define(version: 2019_09_02_131045) do t.index ["scheduled_at"], name: "partial_index_ci_builds_on_scheduled_at_with_scheduled_jobs", where: "((scheduled_at IS NOT NULL) AND ((type)::text = 'Ci::Build'::text) AND ((status)::text = 'scheduled'::text))" t.index ["stage_id", "stage_idx"], name: "tmp_build_stage_position_index", where: "(stage_idx IS NOT NULL)" t.index ["stage_id"], name: "index_ci_builds_on_stage_id" + t.index ["status", "created_at", "project_id"], name: "ci_builds_gitlab_monitor_metrics", where: "((type)::text = 'Ci::Build'::text)" t.index ["status", "type", "runner_id"], name: "index_ci_builds_on_status_and_type_and_runner_id" t.index ["token"], name: "index_ci_builds_on_token", unique: true t.index ["token_encrypted"], name: "index_ci_builds_on_token_encrypted", unique: true, where: "(token_encrypted IS NOT NULL)" @@ -1310,9 +1311,11 @@ ActiveRecord::Schema.define(version: 2019_09_02_131045) do t.datetime_with_timezone "updated_at", null: false t.integer "action", limit: 2, null: false t.string "target_type" + t.bigint "group_id" t.index ["action"], name: "index_events_on_action" t.index ["author_id", "project_id"], name: "index_events_on_author_id_and_project_id" t.index ["created_at", "author_id"], name: "analytics_index_events_on_created_at_and_author_id" + t.index ["group_id"], name: "index_events_on_group_id" t.index ["project_id", "created_at"], name: "index_events_on_project_id_and_created_at" t.index ["project_id", "id"], name: "index_events_on_project_id_and_id" t.index ["target_type", "target_id"], name: "index_events_on_target_type_and_target_id" @@ -2158,6 +2161,13 @@ ActiveRecord::Schema.define(version: 2019_09_02_131045) do t.index ["user_id"], name: "index_merge_trains_on_user_id" end + create_table "milestone_releases", force: :cascade do |t| + t.bigint "milestone_id", null: false + t.bigint "release_id", null: false + t.index ["milestone_id", "release_id"], name: "index_miletone_releases_on_milestone_and_release", unique: true + t.index ["release_id"], name: "index_milestone_releases_on_release_id" + end + create_table "milestones", id: :serial, force: :cascade do |t| t.string "title", null: false t.integer "project_id" @@ -2869,8 +2879,8 @@ ActiveRecord::Schema.define(version: 2019_09_02_131045) do t.integer "project_id" t.string "title", null: false t.string "query", null: false - t.string "y_label" - t.string "unit" + t.string "y_label", null: false + t.string "unit", null: false t.string "legend" t.integer "group", null: false t.datetime_with_timezone "created_at", null: false @@ -3553,6 +3563,7 @@ ActiveRecord::Schema.define(version: 2019_09_02_131045) do t.index ["state"], name: "index_users_on_state" t.index ["state"], name: "index_users_on_state_and_internal", where: "(ghost IS NOT TRUE)" t.index ["state"], name: "index_users_on_state_and_internal_ee", where: "((ghost IS NOT TRUE) AND (bot_type IS NULL))" + t.index ["unconfirmed_email"], name: "index_users_on_unconfirmed_email", where: "(unconfirmed_email IS NOT NULL)" t.index ["username"], name: "index_users_on_username" t.index ["username"], name: "index_users_on_username_trigram", opclass: :gin_trgm_ops, using: :gin end @@ -3831,6 +3842,7 @@ ActiveRecord::Schema.define(version: 2019_09_02_131045) do add_foreign_key "epics", "users", column: "assignee_id", name: "fk_dccd3f98fc", on_delete: :nullify add_foreign_key "epics", "users", column: "author_id", name: "fk_3654b61b03", on_delete: :cascade add_foreign_key "epics", "users", column: "closed_by_id", name: "fk_aa5798e761", on_delete: :nullify + add_foreign_key "events", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "events", "projects", on_delete: :cascade add_foreign_key "events", "users", column: "author_id", name: "fk_edfd187b6f", on_delete: :cascade add_foreign_key "fork_network_members", "fork_networks", on_delete: :cascade @@ -3932,6 +3944,8 @@ ActiveRecord::Schema.define(version: 2019_09_02_131045) do add_foreign_key "merge_trains", "merge_requests", on_delete: :cascade add_foreign_key "merge_trains", "projects", column: "target_project_id", on_delete: :cascade add_foreign_key "merge_trains", "users", on_delete: :cascade + add_foreign_key "milestone_releases", "milestones", on_delete: :cascade + add_foreign_key "milestone_releases", "releases", on_delete: :cascade add_foreign_key "milestones", "namespaces", column: "group_id", name: "fk_95650a40d4", on_delete: :cascade add_foreign_key "milestones", "projects", name: "fk_9bd0a0c791", on_delete: :cascade add_foreign_key "namespace_aggregation_schedules", "namespaces", on_delete: :cascade diff --git a/doc/README.md b/doc/README.md index 9a0252cc334..c704bedc7d6 100644 --- a/doc/README.md +++ b/doc/README.md @@ -357,9 +357,10 @@ The following documentation relates to the DevOps **Secure** stage: | [Dependency List](user/application_security/dependency_list/index.md) **(ULTIMATE)** | View your project's dependencies and their known vulnerabilities. | | [Dependency Scanning](user/application_security/dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. | | [Dynamic Application Security Testing (DAST)](user/application_security/dast/index.md) **(ULTIMATE)** | Analyze running web applications for known vulnerabilities. | -| [Group Security Dashboard](user/application_security/security_dashboard/index.md) **(ULTIMATE)** | View vulnerabilities in all the projects in a group and its subgroups. | +| [Group Security Dashboard](user/application_security/security_dashboard/index.md#group-security-dashboard) **(ULTIMATE)** | View vulnerabilities in all the projects in a group and its subgroups. | | [License Compliance](user/application_security/license_compliance/index.md) **(ULTIMATE)** | Search your project's dependencies for their licenses. | -| [Project Security Dashboard](user/application_security/security_dashboard/index.md) **(ULTIMATE)** | View the latest security reports for your project. | +| [Pipeline Security Dashboard](user/application_security/security_dashboard/index.md#pipeline-security-dashboard) **(ULTIMATE)** | View the security reports for your project's pipelines. | +| [Project Security Dashboard](user/application_security/security_dashboard/index.md#project-security-dashboard) **(ULTIMATE)** | View the latest security reports for your project. | | [Static Application Security Testing (SAST)](user/application_security/sast/index.md) **(ULTIMATE)** | Analyze source code for known vulnerabilities. | ## New to Git and GitLab? diff --git a/doc/administration/geo/disaster_recovery/index.md b/doc/administration/geo/disaster_recovery/index.md index 407539885a6..7228cc6948e 100644 --- a/doc/administration/geo/disaster_recovery/index.md +++ b/doc/administration/geo/disaster_recovery/index.md @@ -315,7 +315,7 @@ section to resolve the error. Otherwise, the secret is lost and you'll need to [geo-limitations]: ../replication/index.md#current-limitations [planned-failover]: planned_failover.md [setup-geo]: ../replication/index.md#setup-instructions -[updating-geo]: ../replication/updating_the_geo_nodes.md#upgrading-to-gitlab-105 +[updating-geo]: ../replication/version_specific_updates.md#updating-to-gitlab-105 [sec-tfa]: ../../../security/two_factor_authentication.md#disabling-2fa-for-everyone [gitlab-org/omnibus-gitlab#3058]: https://gitlab.com/gitlab-org/omnibus-gitlab/issues/3058 [gitlab-org/gitlab-ee#4284]: https://gitlab.com/gitlab-org/gitlab-ee/issues/4284 diff --git a/doc/administration/geo/replication/updating_the_geo_nodes.md b/doc/administration/geo/replication/updating_the_geo_nodes.md index 8c27c4dac4f..fda0ebbbeac 100644 --- a/doc/administration/geo/replication/updating_the_geo_nodes.md +++ b/doc/administration/geo/replication/updating_the_geo_nodes.md @@ -1,6 +1,26 @@ # Updating the Geo nodes **(PREMIUM ONLY)** -Depending on which version of Geo you are updating to/from, there may be different steps. +Updating Geo nodes involves performing: + +1. [Version-specific update steps](#version-specific-update-steps), depending on the + version being updated to or from. +1. [General update steps](#general-update-steps), for all updates. + +## Version specific update steps + +Depending on which version of Geo you are updating to/from, there may be +different steps. + +- [Updating to GitLab 12.1](version_specific_updates.md#updating-to-gitlab-121) +- [Updating to GitLab 10.8](version_specific_updates.md#updating-to-gitlab-108) +- [Updating to GitLab 10.6](version_specific_updates.md#updating-to-gitlab-106) +- [Updating to GitLab 10.5](version_specific_updates.md#updating-to-gitlab-105) +- [Updating to GitLab 10.3](version_specific_updates.md#updating-to-gitlab-103) +- [Updating to GitLab 10.2](version_specific_updates.md#updating-to-gitlab-102) +- [Updating to GitLab 10.1](version_specific_updates.md#updating-to-gitlab-101) +- [Updating to GitLab 10.0](version_specific_updates.md#updating-to-gitlab-100) +- [Updating from GitLab 9.3 or older](version_specific_updates.md#updating-from-gitlab-93-or-older) +- [Updating to GitLab 9.0](version_specific_updates.md#updating-to-gitlab-90) ## General update steps @@ -31,428 +51,3 @@ everything is working correctly: is received by **secondary** nodes. If you encounter any issues, please consult the [Geo troubleshooting guide](troubleshooting.md). - -## Upgrading to GitLab 12.1 - -By default, GitLab 12.1 will attempt to automatically upgrade the embedded PostgreSQL server to 10.7 from 9.6. Please see [the omnibus documentation](https://docs.gitlab.com/omnibus/settings/database.html#upgrading-a-geo-instance) for the recommended procedure. - -This can be temporarily disabled by running the following before ugprading: - -```sh -sudo touch /etc/gitlab/disable-postgresql-upgrade -``` - -## Upgrading to GitLab 10.8 - -Before 10.8, broadcast messages would not propagate without flushing the cache on the **secondary** nodes. This has been fixed in 10.8, but requires one last cache flush on each **secondary** node: - -```sh -sudo gitlab-rake cache:clear -``` - -## Upgrading to GitLab 10.6 - -In 10.4, we started to recommend that you define a password for database user (`gitlab`). - -We now require this change as we use this password to enable the Foreign Data Wrapper, as a way to optimize -the Geo Tracking Database. We are also improving security by disabling the use of **trust** -authentication method. - -1. **(primary)** Login to your **primary** node and run: - - ```sh - gitlab-ctl pg-password-md5 gitlab - # Enter password: <your_password_here> - # Confirm password: <your_password_here> - # fca0b89a972d69f00eb3ec98a5838484 - ``` - - Copy the generated hash and edit `/etc/gitlab/gitlab.rb`: - - ```ruby - # Fill with the hash generated by `gitlab-ctl pg-password-md5 gitlab` - postgresql['sql_user_password'] = '<md5_hash_of_your_password>' - - # Every node that runs Unicorn or Sidekiq needs to have the database - # password specified as below. If you have a high-availability setup, this - # must be present in all application nodes. - gitlab_rails['db_password'] = '<your_password_here>' - ``` - - Still in the configuration file, locate and remove the `trust_auth_cidr_address`: - - ```ruby - postgresql['trust_auth_cidr_addresses'] = ['127.0.0.1/32','1.2.3.4/32'] # <- Remove this - ``` - -1. **(primary)** Reconfigure and restart: - - ```sh - sudo gitlab-ctl reconfigure - sudo gitlab-ctl restart - ``` - -1. **(secondary)** Login to all **secondary** nodes and edit `/etc/gitlab/gitlab.rb`: - - ```ruby - # Fill with the hash generated by `gitlab-ctl pg-password-md5 gitlab` - postgresql['sql_user_password'] = '<md5_hash_of_your_password>' - - # Every node that runs Unicorn or Sidekiq needs to have the database - # password specified as below. If you have a high-availability setup, this - # must be present in all application nodes. - gitlab_rails['db_password'] = '<your_password_here>' - - # Enable Foreign Data Wrapper - geo_secondary['db_fdw'] = true - - # Secondary address in CIDR format, for example '5.6.7.8/32' - postgresql['md5_auth_cidr_addresses'] = ['<secondary_node_ip>/32'] - ``` - - Still in the configuration file, locate and remove the `trust_auth_cidr_address`: - - ```ruby - postgresql['trust_auth_cidr_addresses'] = ['127.0.0.1/32','5.6.7.8/32'] # <- Remove this - ``` - -1. **(secondary)** Reconfigure and restart: - - ```sh - sudo gitlab-ctl reconfigure - sudo gitlab-ctl restart - ``` - -## Upgrading to GitLab 10.5 - -For Geo Disaster Recovery to work with minimum downtime, your **secondary** node -should use the same set of secrets as the **primary** node. However, setup instructions -prior to the 10.5 release only synchronized the `db_key_base` secret. - -To rectify this error on existing installations, you should **overwrite** the -contents of `/etc/gitlab/gitlab-secrets.json` on each **secondary** node with the -contents of `/etc/gitlab/gitlab-secrets.json` on the **primary** node, then run the -following command on each **secondary** node: - -```sh -sudo gitlab-ctl reconfigure -``` - -If you do not perform this step, you may find that two-factor authentication -[is broken following DR](../disaster_recovery/index.html#i-followed-the-disaster-recovery-instructions-and-now-two-factor-auth-is-broken). - -To prevent SSH requests to the newly promoted **primary** node from failing -due to SSH host key mismatch when updating the **primary** node domain's DNS record -you should perform the step to [Manually replicate **primary** SSH host keys](configuration.md#step-2-manually-replicate-the-primary-nodes-ssh-host-keys) in each -**secondary** node. - -## Upgrading to GitLab 10.4 - -There are no Geo-specific steps to take! - -## Upgrading to GitLab 10.3 - -### Support for SSH repository synchronization removed - -In GitLab 10.2, synchronizing secondaries over SSH was deprecated. In 10.3, -support is removed entirely. All installations will switch to the HTTP/HTTPS -cloning method instead. Before upgrading, ensure that all your Geo nodes are -configured to use this method and that it works for your installation. In -particular, ensure that [Git access over HTTP/HTTPS is enabled](configuration.md#step-6-enable-git-access-over-httphttps). - -Synchronizing repositories over the public Internet using HTTP is insecure, so -you should ensure that you have HTTPS configured before upgrading. Note that -file synchronization is **also** insecure in these cases! - -## Upgrading to GitLab 10.2 - -### Secure PostgreSQL replication - -Support for TLS-secured PostgreSQL replication has been added. If you are -currently using PostgreSQL replication across the open internet without an -external means of securing the connection (e.g., a site-to-site VPN), then you -should immediately reconfigure your **primary** and **secondary** PostgreSQL instances -according to the [updated instructions][database]. - -If you *are* securing the connections externally and wish to continue doing so, -ensure you include the new option `--sslmode=prefer` in future invocations of -`gitlab-ctl replicate-geo-database`. - -### HTTPS repository sync - -Support for replicating repositories and wikis over HTTP/HTTPS has been added. -Replicating over SSH has been deprecated, and support for this option will be -removed in a future release. - -To switch to HTTP/HTTPS replication, log into the **primary** node as an admin and visit -**Admin Area > Geo** (`/admin/geo/nodes`). For each **secondary** node listed, -press the "Edit" button, change the "Repository cloning" setting from -"SSH (deprecated)" to "HTTP/HTTPS", and press "Save changes". This should take -effect immediately. - -Any new secondaries should be created using HTTP/HTTPS replication - this is the -default setting. - -After you've verified that HTTP/HTTPS replication is working, you should remove -the now-unused SSH keys from your secondaries, as they may cause problems if the -**secondary** node if ever promoted to a **primary** node: - -1. **(secondary)** Login to **all** your **secondary** nodes and run: - - ```ruby - sudo -u git -H rm ~git/.ssh/id_rsa ~git/.ssh/id_rsa.pub - ``` - -### Hashed Storage - -CAUTION: **Warning:** -Hashed storage is in **Alpha**. It is considered experimental and not -production-ready. See [Hashed Storage] for more detail. - -If you previously enabled Hashed Storage and migrated all your existing -projects to Hashed Storage, disabling hashed storage will not migrate projects -to their previous project based storage path. As such, once enabled and -migrated we recommend leaving Hashed Storage enabled. - -## Upgrading to GitLab 10.1 - -CAUTION: **Warning:** -Hashed storage is in **Alpha**. It is considered experimental and not -production-ready. See [Hashed Storage] for more detail. - -[Hashed storage] was introduced in GitLab 10.0, and a [migration path][hashed-migration] -for existing repositories was added in GitLab 10.1. - -## Upgrading to GitLab 10.0 - -Since GitLab 10.0, we require all **Geo** systems to [use SSH key lookups via -the database][ssh-fast-lookup] to avoid having to maintain consistency of the -`authorized_keys` file for SSH access. Failing to do this will prevent users -from being able to clone via SSH. - -Note that in older versions of Geo, attachments downloaded on the **secondary** -nodes would be saved to the wrong directory. We recommend that you do the -following to clean this up. - -On the **secondary** Geo nodes, run as root: - -```sh -mv /var/opt/gitlab/gitlab-rails/working /var/opt/gitlab/gitlab-rails/working.old -mkdir /var/opt/gitlab/gitlab-rails/working -chmod 700 /var/opt/gitlab/gitlab-rails/working -chown git:git /var/opt/gitlab/gitlab-rails/working -``` - -You may delete `/var/opt/gitlab/gitlab-rails/working.old` any time. - -Once this is done, we advise restarting GitLab on the **secondary** nodes for the -new working directory to be used: - -```sh -sudo gitlab-ctl restart -``` - -## Upgrading from GitLab 9.3 or older - -If you started running Geo on GitLab 9.3 or older, we recommend that you -resync your **secondary** PostgreSQL databases to use replication slots. If you -started using Geo with GitLab 9.4 or 10.x, no further action should be -required because replication slots are used by default. However, if you -started with GitLab 9.3 and upgraded later, you should still follow the -instructions below. - -When in doubt, it does not hurt to do a resync. The easiest way to do this in -Omnibus is the following: - -1. Make sure you have Omnibus GitLab on the **primary** server. -1. Run `gitlab-ctl reconfigure` and `gitlab-ctl restart postgresql`. This will enable replication slots on the **primary** database. -1. Check the steps about defining `postgresql['sql_user_password']`, `gitlab_rails['db_password']`. -1. Make sure `postgresql['max_replication_slots']` matches the number of **secondary** Geo nodes locations. -1. Install GitLab on the **secondary** server. -1. Re-run the [database replication process](database.md#step-3-initiate-the-replication-process). - -## Special update notes for 9.0.x - -> **IMPORTANT**: -With GitLab 9.0, the PostgreSQL version is upgraded to 9.6 and manual steps are -required in order to update the **secondary** nodes and keep the Streaming -Replication working. Downtime is required, so plan ahead. - -The following steps apply only if you upgrade from a 8.17 GitLab version to -9.0+. For previous versions, update to GitLab 8.17 first before attempting to -upgrade to 9.0+. - ---- - -Make sure to follow the steps in the exact order as they appear below and pay -extra attention in what node (either **primary** or **secondary**) you execute them! Each step -is prepended with the relevant node for better clarity: - -1. **(secondary)** Login to **all** your **secondary** nodes and stop all services: - - ```ruby - sudo gitlab-ctl stop - ``` - -1. **(secondary)** Make a backup of the `recovery.conf` file on **all** - **secondary** nodes to preserve PostgreSQL's credentials: - - ```sh - sudo cp /var/opt/gitlab/postgresql/data/recovery.conf /var/opt/gitlab/ - ``` - -1. **(primary)** Update the **primary** node to GitLab 9.0 following the - [regular update docs][update]. At the end of the update, the **primary** node - will be running with PostgreSQL 9.6. - -1. **(primary)** To prevent a de-synchronization of the repository replication, - stop all services except `postgresql` as we will use it to re-initialize the - **secondary** node's database: - - ```sh - sudo gitlab-ctl stop - sudo gitlab-ctl start postgresql - ``` - -1. **(secondary)** Run the following steps on each of the **secondary** nodes: - - 1. **(secondary)** Stop all services: - - ```sh - sudo gitlab-ctl stop - ``` - - 1. **(secondary)** Prevent running database migrations: - - ```sh - sudo touch /etc/gitlab/skip-auto-migrations - ``` - - 1. **(secondary)** Move the old database to another directory: - - ```sh - sudo mv /var/opt/gitlab/postgresql{,.bak} - ``` - - 1. **(secondary)** Update to GitLab 9.0 following the [regular update docs][update]. - At the end of the update, the node will be running with PostgreSQL 9.6. - - 1. **(secondary)** Make sure all services are up: - - ```sh - sudo gitlab-ctl start - ``` - - 1. **(secondary)** Reconfigure GitLab: - - ```sh - sudo gitlab-ctl reconfigure - ``` - - 1. **(secondary)** Run the PostgreSQL upgrade command: - - ```sh - sudo gitlab-ctl pg-upgrade - ``` - - 1. **(secondary)** See the stored credentials for the database that you will - need to re-initialize the replication: - - ```sh - sudo grep -s primary_conninfo /var/opt/gitlab/recovery.conf - ``` - - 1. **(secondary)** Save the snippet below in a file, let's say `/tmp/replica.sh`. Modify the - embedded paths if necessary: - - ``` - #!/bin/bash - - PORT="5432" - USER="gitlab_replicator" - echo --------------------------------------------------------------- - echo WARNING: Make sure this script is run from the secondary server - echo --------------------------------------------------------------- - echo - echo Enter the IP or FQDN of the primary PostgreSQL server - read HOST - echo Enter the password for $USER@$HOST - read -s PASSWORD - echo Enter the required sslmode - read SSLMODE - - echo Stopping PostgreSQL and all GitLab services - sudo service gitlab stop - sudo service postgresql stop - - echo Backing up postgresql.conf - sudo -u postgres mv /var/opt/gitlab/postgresql/data/postgresql.conf /var/opt/gitlab/postgresql/ - - echo Cleaning up old cluster directory - sudo -u postgres rm -rf /var/opt/gitlab/postgresql/data - - echo Starting base backup as the replicator user - echo Enter the password for $USER@$HOST - sudo -u postgres /opt/gitlab/embedded/bin/pg_basebackup -h $HOST -D /var/opt/gitlab/postgresql/data -U gitlab_replicator -v -x -P - - echo Writing recovery.conf file - sudo -u postgres bash -c "cat > /var/opt/gitlab/postgresql/data/recovery.conf <<- _EOF1_ - standby_mode = 'on' - primary_conninfo = 'host=$HOST port=$PORT user=$USER password=$PASSWORD sslmode=$SSLMODE' - _EOF1_ - " - - echo Restoring postgresql.conf - sudo -u postgres mv /var/opt/gitlab/postgresql/postgresql.conf /var/opt/gitlab/postgresql/data/ - - echo Starting PostgreSQL - sudo service postgresql start - ``` - - 1. **(secondary)** Run the recovery script using the credentials from the - previous step: - - ```sh - sudo bash /tmp/replica.sh - ``` - - 1. **(secondary)** Reconfigure GitLab: - - ```sh - sudo gitlab-ctl reconfigure - ``` - - 1. **(secondary)** Start all services: - - ```sh - sudo gitlab-ctl start - ``` - - 1. **(secondary)** Repeat the steps for the remaining **secondary** nodes. - -1. **(primary)** After all **secondary** nodes are updated, start all services in - **primary** node: - - ```sh - sudo gitlab-ctl start - ``` - -### Update tracking database on **secondary** node - -After updating a **secondary** node, you might need to run migrations on -the tracking database. The tracking database was added in GitLab 9.1, -and it is required since 10.0. - -1. Run database migrations on tracking database: - - ```sh - sudo gitlab-rake geo:db:migrate - ``` - -1. Repeat this step for each **secondary** node. - -[update]: ../../../update/README.md -[database]: database.md -[Hashed Storage]: ../../repository_storage_types.md -[hashed-migration]: ../../raketasks/storage.md -[ssh-fast-lookup]: ../../operations/fast_ssh_key_lookup.md diff --git a/doc/administration/geo/replication/version_specific_updates.md b/doc/administration/geo/replication/version_specific_updates.md new file mode 100644 index 00000000000..6d550a49df4 --- /dev/null +++ b/doc/administration/geo/replication/version_specific_updates.md @@ -0,0 +1,426 @@ +# Version specific update instructions + +Check this document if it includes instructions for the version you are updating. +These steps go together with the [general steps](updating_the_geo_nodes.md#general-update-steps) +for updating Geo nodes. + +## Updating to GitLab 12.1 + +By default, GitLab 12.1 will attempt to automatically update the +embedded PostgreSQL server to 10.7 from 9.6. Please see +[the omnibus documentation](https://docs.gitlab.com/omnibus/settings/database.html#upgrading-a-geo-instance) +for the recommended procedure. + +This can be temporarily disabled by running the following before updating: + +```sh +sudo touch /etc/gitlab/disable-postgresql-upgrade +``` + +## Updating to GitLab 10.8 + +Before 10.8, broadcast messages would not propagate without flushing +the cache on the **secondary** nodes. This has been fixed in 10.8, but +requires one last cache flush on each **secondary** node: + +```sh +sudo gitlab-rake cache:clear +``` + +## Updating to GitLab 10.6 + +In 10.4, we started to recommend that you define a password for database user (`gitlab`). + +We now require this change as we use this password to enable the Foreign Data Wrapper, as a way to optimize +the Geo Tracking Database. We are also improving security by disabling the use of **trust** +authentication method. + +1. **(primary)** Login to your **primary** node and run: + + ```sh + gitlab-ctl pg-password-md5 gitlab + # Enter password: <your_password_here> + # Confirm password: <your_password_here> + # fca0b89a972d69f00eb3ec98a5838484 + ``` + + Copy the generated hash and edit `/etc/gitlab/gitlab.rb`: + + ```ruby + # Fill with the hash generated by `gitlab-ctl pg-password-md5 gitlab` + postgresql['sql_user_password'] = '<md5_hash_of_your_password>' + + # Every node that runs Unicorn or Sidekiq needs to have the database + # password specified as below. If you have a high-availability setup, this + # must be present in all application nodes. + gitlab_rails['db_password'] = '<your_password_here>' + ``` + + Still in the configuration file, locate and remove the `trust_auth_cidr_address`: + + ```ruby + postgresql['trust_auth_cidr_addresses'] = ['127.0.0.1/32','1.2.3.4/32'] # <- Remove this + ``` + +1. **(primary)** Reconfigure and restart: + + ```sh + sudo gitlab-ctl reconfigure + sudo gitlab-ctl restart + ``` + +1. **(secondary)** Login to all **secondary** nodes and edit `/etc/gitlab/gitlab.rb`: + + ```ruby + # Fill with the hash generated by `gitlab-ctl pg-password-md5 gitlab` + postgresql['sql_user_password'] = '<md5_hash_of_your_password>' + + # Every node that runs Unicorn or Sidekiq needs to have the database + # password specified as below. If you have a high-availability setup, this + # must be present in all application nodes. + gitlab_rails['db_password'] = '<your_password_here>' + + # Enable Foreign Data Wrapper + geo_secondary['db_fdw'] = true + + # Secondary address in CIDR format, for example '5.6.7.8/32' + postgresql['md5_auth_cidr_addresses'] = ['<secondary_node_ip>/32'] + ``` + + Still in the configuration file, locate and remove the `trust_auth_cidr_address`: + + ```ruby + postgresql['trust_auth_cidr_addresses'] = ['127.0.0.1/32','5.6.7.8/32'] # <- Remove this + ``` + +1. **(secondary)** Reconfigure and restart: + + ```sh + sudo gitlab-ctl reconfigure + sudo gitlab-ctl restart + ``` + +## Updating to GitLab 10.5 + +For Geo Disaster Recovery to work with minimum downtime, your **secondary** node +should use the same set of secrets as the **primary** node. However, setup instructions +prior to the 10.5 release only synchronized the `db_key_base` secret. + +To rectify this error on existing installations, you should **overwrite** the +contents of `/etc/gitlab/gitlab-secrets.json` on each **secondary** node with the +contents of `/etc/gitlab/gitlab-secrets.json` on the **primary** node, then run the +following command on each **secondary** node: + +```sh +sudo gitlab-ctl reconfigure +``` + +If you do not perform this step, you may find that two-factor authentication +[is broken following DR](../disaster_recovery/index.html#i-followed-the-disaster-recovery-instructions-and-now-two-factor-auth-is-broken). + +To prevent SSH requests to the newly promoted **primary** node from failing +due to SSH host key mismatch when updating the **primary** node domain's DNS record +you should perform the step to [Manually replicate **primary** SSH host keys](configuration.md#step-2-manually-replicate-the-primary-nodes-ssh-host-keys) in each +**secondary** node. + +## Updating to GitLab 10.3 + +### Support for SSH repository synchronization removed + +In GitLab 10.2, synchronizing secondaries over SSH was deprecated. In 10.3, +support is removed entirely. All installations will switch to the HTTP/HTTPS +cloning method instead. Before updating, ensure that all your Geo nodes are +configured to use this method and that it works for your installation. In +particular, ensure that [Git access over HTTP/HTTPS is enabled](configuration.md#step-6-enable-git-access-over-httphttps). + +Synchronizing repositories over the public Internet using HTTP is insecure, so +you should ensure that you have HTTPS configured before updating. Note that +file synchronization is **also** insecure in these cases! + +## Updating to GitLab 10.2 + +### Secure PostgreSQL replication + +Support for TLS-secured PostgreSQL replication has been added. If you are +currently using PostgreSQL replication across the open internet without an +external means of securing the connection (e.g., a site-to-site VPN), then you +should immediately reconfigure your **primary** and **secondary** PostgreSQL instances +according to the [updated instructions](database.md). + +If you *are* securing the connections externally and wish to continue doing so, +ensure you include the new option `--sslmode=prefer` in future invocations of +`gitlab-ctl replicate-geo-database`. + +### HTTPS repository sync + +Support for replicating repositories and wikis over HTTP/HTTPS has been added. +Replicating over SSH has been deprecated, and support for this option will be +removed in a future release. + +To switch to HTTP/HTTPS replication, log into the **primary** node as an admin and visit +**Admin Area > Geo** (`/admin/geo/nodes`). For each **secondary** node listed, +press the "Edit" button, change the "Repository cloning" setting from +"SSH (deprecated)" to "HTTP/HTTPS", and press "Save changes". This should take +effect immediately. + +Any new secondaries should be created using HTTP/HTTPS replication - this is the +default setting. + +After you've verified that HTTP/HTTPS replication is working, you should remove +the now-unused SSH keys from your secondaries, as they may cause problems if the +**secondary** node if ever promoted to a **primary** node: + +1. **(secondary)** Login to **all** your **secondary** nodes and run: + + ```ruby + sudo -u git -H rm ~git/.ssh/id_rsa ~git/.ssh/id_rsa.pub + ``` + +### Hashed Storage + +CAUTION: **Warning:** +Hashed storage is in **Alpha**. It is considered experimental and not +production-ready. See [Hashed Storage](../../repository_storage_types.md) for more detail. + +If you previously enabled Hashed Storage and migrated all your existing +projects to Hashed Storage, disabling hashed storage will not migrate projects +to their previous project based storage path. As such, once enabled and +migrated we recommend leaving Hashed Storage enabled. + +## Updating to GitLab 10.1 + +CAUTION: **Warning:** +Hashed storage is in **Alpha**. It is considered experimental and not +production-ready. See [Hashed Storage](../../repository_storage_types.md) for more detail. + +[Hashed storage](../../repository_storage_types.md) was introduced in +GitLab 10.0, and a [migration path](../../raketasks/storage.md) for +existing repositories was added in GitLab 10.1. + +## Updating to GitLab 10.0 + +Since GitLab 10.0, we require all **Geo** systems to [use SSH key lookups via +the database](../../operations/fast_ssh_key_lookup.md) to avoid having to maintain consistency of the +`authorized_keys` file for SSH access. Failing to do this will prevent users +from being able to clone via SSH. + +Note that in older versions of Geo, attachments downloaded on the **secondary** +nodes would be saved to the wrong directory. We recommend that you do the +following to clean this up. + +On the **secondary** Geo nodes, run as root: + +```sh +mv /var/opt/gitlab/gitlab-rails/working /var/opt/gitlab/gitlab-rails/working.old +mkdir /var/opt/gitlab/gitlab-rails/working +chmod 700 /var/opt/gitlab/gitlab-rails/working +chown git:git /var/opt/gitlab/gitlab-rails/working +``` + +You may delete `/var/opt/gitlab/gitlab-rails/working.old` any time. + +Once this is done, we advise restarting GitLab on the **secondary** nodes for the +new working directory to be used: + +```sh +sudo gitlab-ctl restart +``` + +## Updating from GitLab 9.3 or older + +If you started running Geo on GitLab 9.3 or older, we recommend that you +resync your **secondary** PostgreSQL databases to use replication slots. If you +started using Geo with GitLab 9.4 or 10.x, no further action should be +required because replication slots are used by default. However, if you +started with GitLab 9.3 and updated later, you should still follow the +instructions below. + +When in doubt, it does not hurt to do a resync. The easiest way to do this in +Omnibus is the following: + +1. Make sure you have Omnibus GitLab on the **primary** server. +1. Run `gitlab-ctl reconfigure` and `gitlab-ctl restart postgresql`. This will enable replication slots on the **primary** database. +1. Check the steps about defining `postgresql['sql_user_password']`, `gitlab_rails['db_password']`. +1. Make sure `postgresql['max_replication_slots']` matches the number of **secondary** Geo nodes locations. +1. Install GitLab on the **secondary** server. +1. Re-run the [database replication process](database.md#step-3-initiate-the-replication-process). + +## Updating to GitLab 9.0 + +> **IMPORTANT**: +With GitLab 9.0, the PostgreSQL version is updated to 9.6 and manual steps are +required in order to update the **secondary** nodes and keep the Streaming +Replication working. Downtime is required, so plan ahead. + +The following steps apply only if you update from a 8.17 GitLab version to +9.0+. For previous versions, update to GitLab 8.17 first before attempting to +update to 9.0+. + +--- + +Make sure to follow the steps in the exact order as they appear below and pay +extra attention in what node (either **primary** or **secondary**) you execute them! Each step +is prepended with the relevant node for better clarity: + +1. **(secondary)** Log in to **all** your **secondary** nodes and stop all services: + + ```ruby + sudo gitlab-ctl stop + ``` + +1. **(secondary)** Make a backup of the `recovery.conf` file on **all** + **secondary** nodes to preserve PostgreSQL's credentials: + + ```sh + sudo cp /var/opt/gitlab/postgresql/data/recovery.conf /var/opt/gitlab/ + ``` + +1. **(primary)** Update the **primary** node to GitLab 9.0 following the + [regular update docs](../../../update/README.md). At the end of the + update, the **primary** node will be running with PostgreSQL 9.6. + +1. **(primary)** To prevent a de-synchronization of the repository replication, + stop all services except `postgresql` as we will use it to re-initialize the + **secondary** node's database: + + ```sh + sudo gitlab-ctl stop + sudo gitlab-ctl start postgresql + ``` + +1. **(secondary)** Run the following steps on each of the **secondary** nodes: + + 1. **(secondary)** Stop all services: + + ```sh + sudo gitlab-ctl stop + ``` + + 1. **(secondary)** Prevent running database migrations: + + ```sh + sudo touch /etc/gitlab/skip-auto-migrations + ``` + + 1. **(secondary)** Move the old database to another directory: + + ```sh + sudo mv /var/opt/gitlab/postgresql{,.bak} + ``` + + 1. **(secondary)** Update to GitLab 9.0 following the [regular update docs](../../../update/README.md). + At the end of the update, the node will be running with PostgreSQL 9.6. + + 1. **(secondary)** Make sure all services are up: + + ```sh + sudo gitlab-ctl start + ``` + + 1. **(secondary)** Reconfigure GitLab: + + ```sh + sudo gitlab-ctl reconfigure + ``` + + 1. **(secondary)** Run the PostgreSQL upgrade command: + + ```sh + sudo gitlab-ctl pg-upgrade + ``` + + 1. **(secondary)** See the stored credentials for the database that you will + need to re-initialize the replication: + + ```sh + sudo grep -s primary_conninfo /var/opt/gitlab/recovery.conf + ``` + + 1. **(secondary)** Save the snippet below in a file, let's say `/tmp/replica.sh`. Modify the + embedded paths if necessary: + + ``` + #!/bin/bash + + PORT="5432" + USER="gitlab_replicator" + echo --------------------------------------------------------------- + echo WARNING: Make sure this script is run from the secondary server + echo --------------------------------------------------------------- + echo + echo Enter the IP or FQDN of the primary PostgreSQL server + read HOST + echo Enter the password for $USER@$HOST + read -s PASSWORD + echo Enter the required sslmode + read SSLMODE + + echo Stopping PostgreSQL and all GitLab services + sudo service gitlab stop + sudo service postgresql stop + + echo Backing up postgresql.conf + sudo -u postgres mv /var/opt/gitlab/postgresql/data/postgresql.conf /var/opt/gitlab/postgresql/ + + echo Cleaning up old cluster directory + sudo -u postgres rm -rf /var/opt/gitlab/postgresql/data + + echo Starting base backup as the replicator user + echo Enter the password for $USER@$HOST + sudo -u postgres /opt/gitlab/embedded/bin/pg_basebackup -h $HOST -D /var/opt/gitlab/postgresql/data -U gitlab_replicator -v -x -P + + echo Writing recovery.conf file + sudo -u postgres bash -c "cat > /var/opt/gitlab/postgresql/data/recovery.conf <<- _EOF1_ + standby_mode = 'on' + primary_conninfo = 'host=$HOST port=$PORT user=$USER password=$PASSWORD sslmode=$SSLMODE' + _EOF1_ + " + + echo Restoring postgresql.conf + sudo -u postgres mv /var/opt/gitlab/postgresql/postgresql.conf /var/opt/gitlab/postgresql/data/ + + echo Starting PostgreSQL + sudo service postgresql start + ``` + + 1. **(secondary)** Run the recovery script using the credentials from the + previous step: + + ```sh + sudo bash /tmp/replica.sh + ``` + + 1. **(secondary)** Reconfigure GitLab: + + ```sh + sudo gitlab-ctl reconfigure + ``` + + 1. **(secondary)** Start all services: + + ```sh + sudo gitlab-ctl start + ``` + + 1. **(secondary)** Repeat the steps for the remaining **secondary** nodes. + +1. **(primary)** After all **secondary** nodes are updated, start all services in + **primary** node: + + ```sh + sudo gitlab-ctl start + ``` + +### Update tracking database on **secondary** node + +After updating a **secondary** node, you might need to run migrations on +the tracking database. The tracking database was added in GitLab 9.1, +and it is required since 10.0. + +1. Run database migrations on tracking database: + + ```sh + sudo gitlab-rake geo:db:migrate + ``` + +1. Repeat this step for each **secondary** node. diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md index eab4b2c6eea..53b354d2f92 100644 --- a/doc/administration/gitaly/index.md +++ b/doc/administration/gitaly/index.md @@ -148,7 +148,7 @@ Check the directory layout on your Gitaly server to be sure. <!-- updates to following example must also be made at - https://gitlab.com/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab + https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab --> ```ruby diff --git a/doc/administration/index.md b/doc/administration/index.md index 650cb10a64a..d557068e6c8 100644 --- a/doc/administration/index.md +++ b/doc/administration/index.md @@ -139,6 +139,7 @@ Learn how to install, configure, update, and maintain your GitLab instance. - [Repository checks](repository_checks.md): Periodic Git repository checks. - [Repository storage paths](repository_storage_paths.md): Manage the paths used to store repositories. +- [Repository storage types](repository_storage_types.md): Information about the different repository storage types. - [Repository storage rake tasks](raketasks/storage.md): A collection of rake tasks to list and migrate existing projects and attachments associated with it from Legacy storage to Hashed storage. - [Limit repository size](../user/admin_area/settings/account_and_limit_settings.md): Set a hard limit for your repositories' size. **(STARTER ONLY)** @@ -186,13 +187,29 @@ Learn how to install, configure, update, and maintain your GitLab instance. - [Debugging tips](troubleshooting/debug.md): Tips to debug problems when things go wrong - [Log system](logs.md): Where to look for logs. - [Sidekiq Troubleshooting](troubleshooting/sidekiq.md): Debug when Sidekiq appears hung and is not processing jobs. -- Useful [diagnostics tools](troubleshooting/diagnostics_tools.md) that are sometimes used by the GitLab - Support team. -- [Troubleshooting ElasticSearch](troubleshooting/elasticsearch.md): Tips to troubleshoot ElasticSearch. -- [Kubernetes troubleshooting](troubleshooting/kubernetes_cheat_sheet.md): Commands and tips useful - for troubleshooting Kubernetes-related issues. -- Useful links from the Support Team: - - [GitLab Developer Docs](https://docs.gitlab.com/ee/development/README.html). - - [Repairing and recovering broken Git repositories](https://git.seveas.net/repairing-and-recovering-broken-git-repositories.html). - - [Testing with OpenSSL](https://www.feistyduck.com/library/openssl-cookbook/online/ch-testing-with-openssl.html). - - [Strace zine](https://wizardzines.com/zines/strace/). +- [Troubleshooting ElasticSearch](troubleshooting/elasticsearch.md) + +### Support Team Docs + +The GitLab Support Team has collected a lot of information about troubleshooting GitLab +instances. These documents are normally used by the Support Team itself, or by customers +with direct guidance from a Support Team member. GitLab administrators may find the +information useful for troubleshooting, but if you are experiencing trouble with your +GitLab instance, you should check your [support options](https://about.gitlab.com/support/) +before referring to these documents. + +CAUTION: **Warning:** +Using the commands listed in the documentation below could result in data loss or +other damage to a GitLab instance, and should only be used by experienced administrators +who are aware of the risks. + +- [Useful diagnostics tools](troubleshooting/diagnostics_tools.md) +- [Useful Linux commands](troubleshooting/linux_cheat_sheet.md) +- [Troubleshooting Kubernetes](troubleshooting/kubernetes_cheat_sheet.md) +- [Guide to test environments](troubleshooting/test_environments.md) (for Support Engineers) +- [GitLab rails console commands](troubleshooting/gitlab_rails_cheat_sheet.md) (for Support Engineers) +- Useful links: + - [GitLab Developer Docs](../development/README.md) + - [Repairing and recovering broken Git repositories](https://git.seveas.net/repairing-and-recovering-broken-git-repositories.html) + - [Testing with OpenSSL](https://www.feistyduck.com/library/openssl-cookbook/online/ch-testing-with-openssl.html) + - [Strace zine](https://wizardzines.com/zines/strace/) diff --git a/doc/administration/logs.md b/doc/administration/logs.md index 9b1efb610f8..4c43a434817 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -341,9 +341,10 @@ installations from source. ## `database_load_balancing.log` -Introduced in GitLab 12.3 for observability of [Database Load -Balancing](https://docs.gitlab.com/ee/administration/database_load_balancing.html) -when enabled. This file lives in -`/var/log/gitlab/gitlab-rails/database_load_balancing.log` for Omnibus GitLab -packages or in `/home/git/gitlab/log/database_load_balancing.log` for -installations from source. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/15442) in GitLab 12.3. + +This log is used for observability of [Database Load Balancing](database_load_balancing.md). +It is stored at: + +- `/var/log/gitlab/gitlab-rails/database_load_balancing.log` for Omnibus GitLab packages. +- `/home/git/gitlab/log/database_load_balancing.log` for installations from source. diff --git a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md new file mode 100644 index 00000000000..0c5611aa6cd --- /dev/null +++ b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md @@ -0,0 +1,1040 @@ +--- +type: reference +--- + +# GitLab Rails Console Cheat Sheet + +This is the GitLab Support Team's collection of information regarding the GitLab rails +console, for use while troubleshooting. It is listed here for transparency, +and it may be useful for users with experience with these tools. If you are currently +having an issue with GitLab, it is highly recommended that you check your +[support options](https://about.gitlab.com/support/) first, before attempting to use +this information. + +CAUTION: **CAUTION:** +Please note that some of these scripts could be damaging if not run correctly, +or under the right conditions. We highly recommend running them under the +guidance of a Support Engineer, or running them in a test environment with a +backup of the instance ready to be restored, just in case. + +CAUTION: **CAUTION:** +Please also note that as GitLab changes, changes to the code are inevitable, +and so some scripts may not work as they once used to. These are not kept +up-to-date as these scripts/commands were added as they were found/needed. As +mentioned above, we recommend running these scripts under the supervision of a +Support Engineer, who can also verify that they will continue to work as they +should and, if needed, update the script for the latest version of GitLab. + +## Use the Rails Runner + +If the script you want to run is short, you can use the Rails Runner to avoid +entering the rails console in the first place. Here's an example of its use: + +```bash +gitlab-rails runner "RAILS_COMMAND" + +# Example with a 2-line script +gitlab-rails runner "user = User.first; puts user.username" +``` + +## Enable debug logging on rails console + +```ruby +Rails.logger.level = 0 +``` + +## Enable debug logging for ActiveRecord (db issues) + +```ruby +ActiveRecord::Base.logger = Logger.new(STDOUT) +``` + +## Temporarily Disable Timeout + +```ruby +ActiveRecord::Base.connection.execute('SET statement_timeout TO 0') +``` + +## Find specific methods for an object + +```ruby +Array.methods.select { |m| m.to_s.include? "sing" } +Array.methods.grep(/sing/) +``` + +## Find method source + +Works for [non-instrumented methods](https://docs.gitlab.com/ce/development/instrumentation.html#checking-instrumented-methods): + +```ruby +instance_of_object.method(:foo).source_location + +# Example for when we would call project.private? +project.method(:private?).source_location +``` + +## Query an object + +```ruby +o = Object.where('attribute like ?', 'ex') +``` + +## View all keys in cache + +```ruby +Rails.cache.instance_variable_get(:@data).keys +``` + +## Rails console history + +```ruby +puts Readline::HISTORY.to_a +``` + +## Profile a page + +```ruby +# Before 11.6.0 +logger = Logger.new(STDOUT) +admin_token = User.find_by_username('ADMIN_USERNAME').personal_access_tokens.first.token +app.get("URL/?private_token=#{admin_token}") + +# From 11.6.0 +admin = User.find_by_username('ADMIN_USERNAME') +url = "/url/goes/here" +Gitlab::Profiler.with_user(admin) { app.get(url) } +``` + +## Using the GitLab profiler inside console (used as of 10.5) + +```ruby +logger = Logger.new(STDOUT) +admin = User.find_by_username('ADMIN_USERNAME') +Gitlab::Profiler.profile('URL', logger: logger, user: admin) +``` + +## Time an operation + +```ruby +# A single operation +Benchmark.measure { <operation> } + +# A breakdown of multiple operations +Benchmark.bm do |x| + x.report(:label1) { <operation_1> } + x.report(:label2) { <operation_2> } +end +``` + +## Command Line + +### Check the GitLab version fast + +```bash +grep -m 1 gitlab /opt/gitlab/version-manifest.txt +``` + +### Debugging SSH + +```bash +GIT_SSH_COMMAND="ssh -vvv" git clone <repository> +``` + +### Debugging over HTTPS + +```bash +GIT_CURL_VERBOSE=1 GIT_TRACE=1 git clone <repository> +``` + +## Projects + +### Find projects + +```ruby +# A single project +project = Project.find_by_full_path('PROJECT_PATH') + +# All projects in a particular namespace. Can be a username, a group +# ('gitlab-org'), or even include subgroups ('gitlab-org/distribution') +namespace = Namespace.find_by_full_path('NAMESPACE_PATH') +projects = namespace.all_projects +``` + +### Clear a project's cache + +```ruby +ProjectCacheWorker.perform_async(project.id) +``` + +### Expire the .exists? cache + +```ruby +project.repository.expire_exists_cache +``` + +### Make all projects private + +```ruby +Project.update_all(visibility_level: 0) +``` + +### Find & remove projects that are pending deletion + +```ruby +# +# This section will list all the projects which are pending deletion +# +projects = Project.where(pending_delete: true) +projects.each do |p| + puts "Project name: #{p.id}" + puts "Project name: #{p.name}" + puts "Repository path: #{p.repository.storage_path}" +end + +# +# Assign a user (the root user will do) +# +user = User.find_by_username('root') + +# +# For each project listed repeat these two commands +# + +# Find the project, update the xxx-changeme values from above +project = Project.find_by_full_path('group-changeme/project-changeme') + +# Delete the project +::Projects::DestroyService.new(project, user, {}).execute +``` + +Next, run `sudo gitlab-rake gitlab:cleanup:repos` on the command line to finish. + +### Destroy a project + +```ruby +project = Project.find_by_full_path('') +user = User.find_by_username('') +ProjectDestroyWorker.perform_async(project.id, user.id, {}) +# or ProjectDestroyWorker.new.perform(project.id, user.id, {}) +# or Projects::DestroyService.new(project, user).execute +``` + +### Remove fork relationship manually + +```ruby +p = Project.find_by_full_path('') +u = User.find_by_username('') +::Projects::UnlinkForkService.new(p, u).execute +``` + +### Make a project read-only (can only be done in the console) + +```ruby +# Make a project read-only +project.repository_read_only = true; project.save + +# OR +project.update!(repository_read_only: true) +``` + +### Bulk update service integration password for _all_ projects + +For example, change the Jira user's password for all projects that have the Jira +integration active: + +```ruby +p = Project.find_by_sql("SELECT p.id FROM projects p LEFT JOIN services s ON p.id = s.project_id WHERE s.type = 'JiraService' AND s.active = true") + +p.each do |project| + project.jira_service.update_attribute(:password, '<your-new-password>') +end +``` + +### Identify un-indexed projects + +```ruby +Project.find_each do |project| + puts "id #{project.id}: #{project.namespace.name.to_s}/#{project.name.to_s}" if project.index_status.nil? +end +``` + +## Imports / Exports + +```ruby +# Find the project and get the error +p = Project.find_by_full_path('<username-or-group>/<project-name>') + +p.import_error + +# To finish the import on GitLab running version before 11.6 +p.import_finish + +# To finish the import on GitLab running version 11.6 or after +p.import_state.mark_as_failed("Failed manually through console.") +``` + +### Rename imported repository + +In a specific situation, an imported repository needed to be renamed. The Support +Team was informed of a backup restore that failed on a single repository, which created +the project with an empty repository. The project was successfully restored to a dev +instance, then exported, and imported into a new project under a different name. + +The Support Team was able to transfer the incorrectly named imported project into the +correctly named empty project using the steps below. + +Move the new repository to the empty repository: + +```bash +mv /var/opt/gitlab/git-data/repositories/<group>/<new-project> /var/opt/gitlab/git-data/repositories/<group>/<empty-project> +``` + +Make sure the permissions are correct: + +```bash +chown -R git:git <path-to-directory>.git +``` + +Clear the cache: + +```bash +sudo gitlab-rake cache:clear +``` + +## Repository + +### Search sequence of pushes to a repository + +If it seems that a commit has gone "missing", search the sequence of pushes to a repository. +[This StackOverflow article](https://stackoverflow.com/questions/13468027/the-mystery-of-the-missing-commit-across-merges) +describes how you can end up in this state without a force push. + +If you look at the output from the sample code below for the target branch, you will +see a discontinuity in the from/to commits as you step through the output. Each new +push should be "from" the "to" SHA of the previous push. When this discontinuity happens, +you will see two pushes with the same "from" SHA: + +```ruby +p = Project.find_with_namespace('u/p') +p.events.code_push.last(100).each do |e| + printf "%-20.20s %8s...%8s (%s)\n", e.data[:ref], e.data[:before], e.data[:after], e.author.try(:username) +end +``` + +GitLab 9.5 and above: + +```ruby +p = Project.find_by_full_path('u/p') +p.events.code_push.last(100).each do |e| + printf "%-20.20s %8s...%8s (%s)\n", e.push_event_payload[:ref], e.push_event_payload[:commit_from], e.push_event_payload[:commit_to], e.author.try(:username) +end +``` + +## Mirrors + +### Find mirrors with "bad decrypt" errors + +```ruby +total = 0 +bad = [] +ProjectImportData.find_each do |data| + begin + total += 1 + data.credentials + rescue => e + bad << data + end +end + +puts "Bad count: #{bad.count} / #{total}" +bad.each do |repo| + puts Project.find(repo.project_id).full_path +end; bad.count +``` + +### Transfer mirror users and tokens to a single service account + +Use case: If you have multiple users using their own GitHub credentials to set up +repository mirroring, mirroring breaks when people leave the company. Use this +script to migrate disparate mirroring users and tokens into a single service account: + +```ruby +svc_user = User.find_by(username: 'ourServiceUser') +token = 'githubAccessToken' + +Project.where(mirror: true).each do |project| + import_url = project.import_url + + # The url we want is https://token@project/path.git + repo_url = if import_url.include?('@') + # Case 1: The url is something like https://23423432@project/path.git + import_url.split('@').last + elsif import_url.include?('//') + # Case 2: The url is something like https://project/path.git + import_url.split('//').last + end + + next unless repo_url + + final_url = "https://#{token}@#{repo_url}" + + project.mirror_user = svc_user + project.import_url = final_url + project.username_only_import_url = final_url + project.save +end +``` + +## Users + +### Finding users + +```ruby +# By username +user = User.find_by(username: '') + +# By primary email +user = User.find_by(email: '') + +# By any email (primary or secondary) +user = User.find_by_any_email('') + +# Admins +User.admins +admin = User.admins.first +``` + +### Block + +```ruby +User.find_by_username().block! +``` + +### Unblock + +```ruby +User.find_by_username().active +``` + +### Skip reconfirmation + +```ruby +user = User.find_by_username '' +user.skip_reconfirmation! +``` + +### Get an admin token + +```ruby +# Get the first admin's first access token (no longer works on 11.9+. see: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22743) +User.where(admin:true).first.personal_access_tokens.first.token + +# Get the first admin's private token (no longer works on 10.2+) +User.where(admin:true).private_token +``` + +### Create personal access token + +```ruby +personal_access_token = User.find(123).personal_access_tokens.create( + name: 'apitoken', + impersonation: false, + scopes: [:api] +) + +personal_access_token.token +``` + +You might also want to manually set the token string: + +```ruby +User.find(123).personal_access_tokens.create( + name: 'apitoken', + token_digest: Gitlab::CryptoHelper.sha256('some-token-string-here'), + impersonation: false, + scopes: [:api] +) +``` + +### Disable 2FA on a user + +```ruby +user = User.find_by_username('username') +user.disable_two_factor! +``` + +### Active users & Historical users + +```ruby +# Active users on the instance, now +User.active.count + +# The historical max on the instance as of the past year +::HistoricalData.max_historical_user_count +``` + +```bash +# Using curl and jq (up to a max 100, see [pagination](https://docs.gitlab.com/ee/api/#pagination) +curl --silent --header "Private-Token: ********************" "https://gitlab.example.com/api/v4/users?per_page=100&active" | jq --compact-output '.[] | [.id,.name,.username]' +``` + +### Block or Delete Users that have no projects or groups + +```ruby +users = User.where('id NOT IN (select distinct(user_id) from project_authorizations)') + +# How many users will be removed? +users.count + +# If that count looks sane: + +# You can either block the users: +users.each { |user| user.block! } + +# Or you can delete them: + # need 'current user' (your user) for auditing purposes +current_user = User.find_by(username: '<your username>') + +users.each do |user| + DeleteUserWorker.perform_async(current_user.id, user.id) +end +``` + +### Block Users that have no recent activity + +```ruby +days_inactive = 60 +inactive_users = User.active.where("last_activity_on <= ?", days_inactive.days.ago) + +inactive_users.each do |user| + puts "user '#{user.username}': #{user.last_activity_on}" + user.block! +end +``` + +### Find Max permissions for project/group + +```ruby +user = User.find_by_username 'username' +project = Project.find_by_full_path 'group/project' +user.max_member_access_for_project project.id +``` + +```ruby +user = User.find_by_username 'username' +group = Group.find_by_full_path 'group' +user.max_member_access_for_group group.id +``` + +## Groups + +### Count unique users in a group and sub-groups + +```ruby +group = Group.find_by_path_or_name("groupname") +members = [] +for member in group.members_with_descendants + members.push(member.user_name) +end + +members.uniq.length +``` + +```ruby +group = Group.find_by_path_or_name("groupname") + +# Count users from subgroup and up (inherited) +group.members_with_parents.count + +# Count users from parent group and down (specific grants) +parent.members_with_descendants.count +``` + +### Delete a group + +```ruby +GroupDestroyWorker.perform_async(group_id, user_id) +``` + +## LDAP + +### LDAP commands in the rails console + +TIP: **TIP:** +Use the rails runner to avoid entering the rails console in the first place. +This is great when only a single command (such as a UserSync or GroupSync) +is needed. + +```ruby +# Get debug output +Rails.logger.level = Logger::DEBUG + +# Run a UserSync (normally performed once a day) +LdapSyncWorker.new.perform + +# Run a GroupSync for all groups (9.3-) +LdapGroupSyncWorker.new.perform + +# Run a GroupSync for all groups (9.3+) +LdapAllGroupsSyncWorker.new.perform + +# Run a GroupSync for a single group (10.6-) +group = Group.find_by(name: 'my_gitlab_group') +EE::Gitlab::LDAP::Sync::Group.execute_all_providers(group) + +# Run a GroupSync for a single group (10.6+) +group = Group.find_by(name: 'my_gitlab_group') +EE::Gitlab::Auth::LDAP::Sync::Group.execute_all_providers(group) + +# Query an LDAP group directly (10.6-) +adapter = Gitlab::LDAP::Adapter.new('ldapmain') # If `main` is the LDAP provider +ldap_group = EE::Gitlab::LDAP::Group.find_by_cn('group_cn_here', adapter) +ldap_group.member_dns +ldap_group.member_uids + +# Query an LDAP group directly (10.6+) +adapter = Gitlab::Auth::LDAP::Adapter.new('ldapmain') # If `main` is the LDAP provider +ldap_group = EE::Gitlab::Auth::LDAP::Group.find_by_cn('group_cn_here', adapter) +ldap_group.member_dns +ldap_group.member_uids + +# Lookup a particular user (10.6+) +# This could expose potential errors connecting to and/or querying LDAP that may seem to +# fail silently in the GitLab UI +adapter = Gitlab::Auth::LDAP::Adapter.new('ldapmain') # If `main` is the LDAP provider +user = Gitlab::Auth::LDAP::Person.find_by_uid('<username>',adapter) + +# Query the LDAP server directly (10.6+) +## For an example, see https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/ee/gitlab/auth/ldap/adapter.rb +adapter = Gitlab::Auth::LDAP::Adapter.new('ldapmain') +options = { + # the :base is required + # use adapter.config.base for the base or .group_base for the group_base + base: adapter.config.group_base, + + # :filter is optional + # 'cn' looks for all "cn"s under :base + # '*' is the search string - here, it's a wildcard + filter: Net::LDAP::Filter.eq('cn', '*'), + + # :attributes is optional + # the attributes we want to get returned + attributes: %w(dn cn memberuid member submember uniquemember memberof) +} +adapter.ldap_search(options) +``` + +### Update user accounts when the `dn` and email change + +The following will require that any accounts with the new email address are removed. +Emails have to be unique in GitLab. This is expected to work but unverified as of yet: + +```ruby +# Here's an example with a couple users. +# Each entry will have to include the old username and the new email +emails = { + 'ORIGINAL_USERNAME' => 'NEW_EMAIL_ADDRESS', + ... +} + +emails.each do |username, email| + user = User.find_by_username(username) + user.email = email + user.skip_reconfirmation! + user.save! +end + +# Run the UserSync to update the above users' data +LdapSyncWorker.new.perform +``` + +## Routes + +### Remove redirecting routes + +See <https://gitlab.com/gitlab-org/gitlab-ce/issues/41758#note_54828133>. + +```ruby +path = 'foo' +conflicting_permanent_redirects = RedirectRoute.matching_path_and_descendants(path) + +# Check that conflicting_permanent_redirects is as expected +conflicting_permanent_redirects.destroy_all +``` + +## Merge Requests + +### Find Merge Request + +```ruby +m = project.merge_requests.find_by(iid: <IID>) +m = MergeRequest.find_by_title('NEEDS UNIQUE TITLE!!!') +``` + +### Close a merge request properly (if merged but still marked as open) + +```ruby +p = Project.find_by_full_path('') +m = project.merge_requests.find_by(iid: ) +u = User.find_by_username('') +MergeRequests::PostMergeService.new(p, u).execute(m) +``` + +### Rebase manually + +```ruby +p = Project.find_by_full_path('') +m = project.merge_requests.find_by(iid: ) +u = User.find_by_username('') +MergeRequests::RebaseService.new(m.target_project, u).execute(m) +``` + +## CI + +### Cancel stuck pending pipelines + +See <https://gitlab.com/gitlab-com/support-forum/issues/2449#note_41929707>. + +```ruby +Ci::Pipeline.where(project_id: p.id).where(status: 'pending').count +Ci::Pipeline.where(project_id: p.id).where(status: 'pending').each {|p| p.cancel} +Ci::Pipeline.where(project_id: p.id).where(status: 'pending').count +``` + +### Manually modify runner minutes + +```ruby +Namespace.find_by_full_path("user/proj").namespace_statistics.update(shared_runners_seconds: 27360) +``` + +### Remove artifacts more than a week old + +```ruby +### SELECTING THE BUILDS TO CLEAR +# For a single project: +project = Project.find_by_full_path('') +builds_with_artifacts = project.builds.with_artifacts_archive + +# Prior to 10.6 the above line would be: +# builds_with_artifacts = project.builds.with_artifacts + +# Instance-wide: +builds_with_artifacts = Ci::Build.with_artifacts + +### CLEAR THEM OUT +builds_to_clear = builds_with_artifacts.where("finished_at < ?", 1.week.ago) +builds_to_clear.each do |build| + build.artifacts_expire_at = Time.now + build.erase_erasable_artifacts! +end +``` + +### Find reason failure (for when build trace is empty) (Introduced in 10.3.0) + +See <https://gitlab.com/gitlab-org/gitlab-ce/issues/41111>. + +```ruby +build = Ci::Build.find(78420) + +build.failure_reason + +build.dependencies.each do |d| { puts "status: #{d.status}, finished at: #{d.finished_at}, + completed: #{d.complete?}, artifacts_expired: #{d.artifacts_expired?}, erased: #{d.erased?}" } +``` + +### Disable strict artifact checking (Introduced in GitLab 10.3.0) + +See <https://docs.gitlab.com/ee/administration/job_artifacts.html#validation-for-dependencies>. + +```ruby +Feature.enable('ci_disable_validates_dependencies') +``` + +### Remove CI traces older than 6 months + +```ruby +current_user = User.find_by_email('cindy@gitlap.com') +Ci::Build.where("finished_at < ?", 6.months.ago.to_date).each {|b| puts b.id; b.erase(erased_by: current_user) if b.erasable?};nil +``` + +### Try CI service + +```ruby +p = Project.find_by_full_path('') +m = project.merge_requests.find_by(iid: ) +m.project.try(:ci_service) +``` + +### Disable AutoDevOps on Existing Projects + +```ruby +Project.all.each do |p| + p.auto_devops_attributes={"enabled"=>"0"} + p.save +end +``` + +## License + +### See license plan name (since v9.3.0-ee) + +```ruby +License.current.plan +``` + +### Check if a project feature is available on the instance + +Features listed in <https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/app/models/license.rb>. + +```ruby +License.current.feature_available?(:jira_dev_panel_integration) +``` + +### Check if a project feature is available in a project + +Features listed in <https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/app/models/license.rb>. + +```ruby +p = Project.find_by_full_path('<group>/<project>') +p.feature_available?(:jira_dev_panel_integration) +``` + +### Add a license through the console + +```ruby +key = "<key>" +license = License.new(data: key) +license.save +License.current # check to make sure it applied +``` + +## Unicorn + +From [Zendesk ticket #91083](https://gitlab.zendesk.com/agent/tickets/91083) (internal) + +### Poll unicorn requests by seconds + +```ruby +require 'rubygems' +require 'unicorn' + +# Usage for this program +def usage + puts "ruby unicorn_status.rb <path to unix socket> <poll interval in seconds>" + puts "Polls the given Unix socket every interval in seconds. Will not allow you to drop below 3 second poll intervals." + puts "Example: /opt/gitlab/embedded/bin/ruby poll_unicorn.rb /var/opt/gitlab/gitlab-rails/sockets/gitlab.socket 10" +end + +# Look for required args. Throw usage and exit if they don't exist. +if ARGV.count < 2 + usage + exit 1 +end + +# Get the socket and threshold values. +socket = ARGV[0] +threshold = (ARGV[1]).to_i + +# Check threshold - is it less than 3? If so, set to 3 seconds. Safety first! +if threshold.to_i < 3 + threshold = 3 +end + +# Check - does that socket exist? +unless File.exist?(socket) + puts "Socket file not found: #{socket}" + exit 1 +end + +# Poll the given socket every THRESHOLD seconds as specified above. +puts "Running infinite loop. Use CTRL+C to exit." +puts "------------------------------------------" +loop do + Raindrops::Linux.unix_listener_stats([socket]).each do |addr, stats| + puts DateTime.now.to_s + " Active: " + stats.active.to_s + " Queued: " + stats.queued.to_s + end + sleep threshold +end +``` + +## Sidekiq + +### Size of a queue + +```ruby +Sidekiq::Queue.new('background_migration').size +``` + +### Kill a worker's Sidekiq jobs + +```ruby +queue = Sidekiq::Queue.new('repository_import') +queue.each { |job| job.delete if <condition>} +``` + +### Enable debug logging of Sidekiq + +```ruby +gitlab_rails['env'] = { + 'SIDEKIQ_LOG_ARGUMENTS' => "1" +} +``` + +Then `gitlab-ctl reconfigure; gitlab-ctl restart sidekiq`. The Sidekiq logs will now include additional data for troubleshooting. + +### Sidekiq kill signals + +See <https://github.com/mperham/sidekiq/wiki/Signals#ttin>. + +## Redis + +### Connect to redis (omnibus) + +```sh +/opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket +``` + +### Connect to redis (HA) + +```sh +/opt/gitlab/embedded/bin/redis-cli -h <host ip> -a <password> +``` + +## LFS + +### Get info about LFS objects and associated project + +```ruby +o=LfsObject.find_by(oid: "<oid>") +p=Project.find(LfsObjectsProject.find_by_lfs_object_id(o.id).project_id) +``` + +You can then delete these records from the database with: + +```ruby +LfsObjectsProject.find_by_lfs_object_id(o.id).destroy +o.destroy +``` + +You would also want to combine this with deleting the LFS file in the LFS storage +area on disk. It remains to be seen exactly how or whether the deletion is useful, however. + +## Decryption Problems + +### Bad Decrypt Script (for encrypted variables) + +See <https://gitlab.com/snippets/1730735/raw>. + +This script will go through all the encrypted variables and count how many are not able +to be decrypted. Might be helpful to run on multiple nodes to see which `gitlab-secrets.json` +file is most up to date: + +```bash +wget -O /tmp/bad-decrypt.rb https://gitlab.com/snippets/1730735/raw +gitlab-rails runner /tmp/bad-decrypt.rb +``` + +If `ProjectImportData Bad count:` is detected and the decision is made to delete the +encrypted credentials to allow manual reentry: + +```ruby + # Find the ids of the corrupt ProjectImportData objects + total = 0 + bad = [] + ProjectImportData.find_each do |data| + begin + total += 1 + data.credentials + rescue => e + bad << data.id + end + end + + puts "Bad count: #{bad.count} / #{total}" + + # See the bad ProjectImportData ids + bad + + # Remove the corrupted credentials + import_data = ProjectImportData.where(id: bad) + import_data.each do |data| + data.update_columns({ encrypted_credentials: nil, encrypted_credentials_iv: nil, encrypted_credentials_salt: nil}) + end +``` + +If `User OTP Secret Bad count:` is detected. For each user listed disable/enable +two-factor authentication. + +### Decrypt Script for encrypted tokens + +This script will search for all encrypted tokens that are causing decryption errors, +and update or reset as needed: + +```bash +wget -O /tmp/encrypted-tokens.rb https://gitlab.com/snippets/1876342/raw +gitlab-rails runner /tmp/encrypted-tokens.rb +``` + +## Geo + +### Artifacts + +#### Find failed artifacts + +```ruby +Geo::JobArtifactRegistry.failed +``` + +#### Download artifact + +```ruby +Gitlab::Geo::JobArtifactDownloader.new(:job_artifact, <artifact_id>).execute +``` + +#### Get a count of the synced artifacts + +```ruby +Geo::JobArtifactRegistry.synced.count +``` + +#### Find `ID` of synced artifacts that are missing on primary + +```ruby +Geo::JobArtifactRegistry.synced.missing_on_primary.pluck(:artifact_id) +``` + +### Repository verification failures + +#### Get the number of verification failed repositories + +```ruby +Geo::ProjectRegistryFinder.new.count_verification_failed_repositories +``` + +#### Find the verification failed repositories + +```ruby +Geo::ProjectRegistry.verification_failed_repos +``` + +### Find repositories that failed to sync + +```ruby +Geo::ProjectRegistryFinder.new.find_failed_project_registries('repository') +``` + +### Resync repositories + +#### Queue up all repositories for resync. Sidekiq will handle each sync + +```ruby +Geo::ProjectRegistry.update_all(resync_repository: true, resync_wiki: true) +``` + +#### Sync individual repository now + +```ruby +project = Project.find_by_full_path('<group/project>') + +Geo::RepositorySyncService.new(project).execute +``` diff --git a/doc/administration/troubleshooting/kubernetes_cheat_sheet.md b/doc/administration/troubleshooting/kubernetes_cheat_sheet.md index 260af333e8e..6bcd4c48e5a 100644 --- a/doc/administration/troubleshooting/kubernetes_cheat_sheet.md +++ b/doc/administration/troubleshooting/kubernetes_cheat_sheet.md @@ -75,7 +75,7 @@ and they will assist you with any issues you are having. ## GitLab-specific kubernetes information - Minimal config that can be used to test a Kubernetes helm chart can be found - [here](https://gitlab.com/charts/gitlab/issues/620). + [here](https://gitlab.com/gitlab-org/charts/gitlab/issues/620). - Tailing logs of a separate pod. An example for a unicorn pod: @@ -176,7 +176,7 @@ and they will assist you with any issues you are having. helm upgrade <release name> <chart path> -f gitlab.yaml ``` - After <https://canary.gitlab.com/charts/gitlab/issues/780> is fixed, it should + After <https://gitlab.com/gitlab-org/charts/gitlab/issues/780> is fixed, it should be possible to use [Updating GitLab using the Helm Chart](https://docs.gitlab.com/ee/install/kubernetes/gitlab_chart.html#updating-gitlab-using-the-helm-chart) for upgrades. @@ -191,8 +191,8 @@ and they will assist you with any issues you are having. ## Installation of minimal GitLab config via Minukube on macOS -This section is based on [Developing for Kubernetes with Minikube](https://gitlab.com/charts/gitlab/blob/master/doc/minikube/index.md) -and [Helm](https://gitlab.com/charts/gitlab/blob/master/doc/helm/index.md). Refer +This section is based on [Developing for Kubernetes with Minikube](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/minikube/index.md) +and [Helm](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/helm/index.md). Refer to those documents for details. - Install Kubectl via Homebrew: @@ -223,7 +223,7 @@ to those documents for details. helm init --service-account tiller ``` -- Copy the file <https://gitlab.com/charts/gitlab/raw/master/examples/values-minikube-minimum.yaml> +- Copy the file <https://gitlab.com/gitlab-org/charts/gitlab/raw/master/examples/values-minikube-minimum.yaml> to your workstation. - Find the IP address in the output of `minikube ip` and update the yaml file with diff --git a/doc/administration/troubleshooting/linux_cheat_sheet.md b/doc/administration/troubleshooting/linux_cheat_sheet.md new file mode 100644 index 00000000000..2bbb498f020 --- /dev/null +++ b/doc/administration/troubleshooting/linux_cheat_sheet.md @@ -0,0 +1,339 @@ +--- +type: reference +--- + +# Linux Cheat Sheet + +This is the GitLab Support Team's collection of information regarding Linux, that they +sometimes use while troubleshooting. It is listed here for transparency, +and it may be useful for users with experience with Linux. If you are currently +having an issue with GitLab, you may want to check your [support options](https://about.gitlab.com/support/) +first, before attempting to use this information. + +CAUTION: **CAUTION:** +If you are administering GitLab you are expected to know these commands for your distribution +of choice. If you are a GitLab Support Engineer, consider this a cross-reference to +translate `yum` -> `apt-get` and the like. + +Note: **Note:** +Most of the commands below have not been labeled as to which distribution they work +on. Contributions are welcome to help add them. + +## System Commands + +### Distro Information + +```bash +# Debian/Ubuntu +uname -a +lsb_release -a + +# CentOS/RedHat +cat /etc/centos-release +cat /etc/redhat-release + +# This will provide a lot more information +cat /etc/os-release +``` + +### Shut down or Reboot + +```bash +shutdown -h now +reboot +``` + +### Permissions + +```bash +# change the user:group ownership of a file/dir +chown root:git <file_or_dir> + +# make a file executable +chmod u+x <file> +``` + +### Files & Dirs + +```bash +# create a new directory and all subdirectories +mkdir -p dir/dir2/dir3 + +# Send a command's output to file.txt, no STDOUT +ls > file.txt + +# Send a command's output to file.txt AND see it in STDOUT +ls | tee /tmp/file.txt + +# Search and Replace within a file +sed -i 's/original-text/new-text/g' <filename> +``` + +### See all set environment variables + +```bash +env +``` + +## Searching + +### File names + +```bash +# search for a file in a filesystem +find . -name 'filename.rb' -print + +# locate a file +locate <filename> + +# see command history +history + +# search CLI history +<ctrl>-R +``` + +### File contents + +```bash +# -B/A = show 2 lines before/after search_term +grep -B 2 -A 2 search_term <filename> + +# -<number> shows both before and after +grep -2 search_term <filename> + +# Search on all files in directory (recursively) +grep -r search_term <directory> + +# search through *.gz files is the same except with zgrep +zgrep search_term <filename> + +# Fast grep printing lines containing a string pattern +fgrep -R string_pattern <filename or directory> +``` + +### CLI + +```bash +# View command history +history + +# Run last command that started with 'his' (3 letters min) +!his + +# Search through command history +<ctrl>-R + +# Execute last command with sudo +sudo !! +``` + +## Managing resources + +### Memory, Disk, & CPU usage + +```bash +# disk space info. The '-h' gives the data in human-readable values +df -h + +# size of each file/dir and its contents in the current dir +du -hd 1 + +# or alternative +du -h --max-depth=1 + +# find files greater than certain size(k, M, G) and list them in order +# get rid of the + for exact, - for less than +find / -type f -size +100M -print0 | xargs -0 du -hs | sort -h + +# Find free memory on a system +free -m + +# Find what processes are using memory/CPU and organize by it +# Load average is 1/CPU for 1, 5, and 15 minutes +top -o %MEM +top -o %CPU +``` + +### Strace + +```bash +# strace a process +strace -tt -T -f -y -s 1024 -p <pid> + +# -tt print timestamps with microsecond accuracy + +# -T print the time spent in each syscall + +# -f also trace any child processes that forked + +# -y print the path associated with file handles + +# -s max string length to print for an event + +# -o output file + +# run strace on all unicorn processes +ps auwx | grep unicorn | awk '{ print " -p " $2}' | xargs strace -tt -T -f -y -s 1024 -o /tmp/unicorn.txt +``` + +See the [strace zine](https://wizardzines.com/zines/strace/) for a quick walkthrough. + +Brendan Gregg has a more detailed explanation of [how to use strace](http://www.brendangregg.com/blog/2014-05-11/strace-wow-much-syscall.html). + +Be aware that strace can have major impacts to system performance when it is running. + +### The Strace Parser tool + +Our [strace-parser tool](https://gitlab.com/wchandler/strace-parser) can be used to +provide a high level summary of the `strace` output. It is similar to `strace -C`, +but provides much more detailed statistics. + +MacOS and Linux binaries [are available](https://gitlab.com/gitlab-com/support/toolbox/strace-parser/-/tags), +or you can build it from source if you have the Rust compiler. + +#### How to use the tool + +First run the tool with no arguments other than the strace output file name to get +a summary of the top processes sorted by time spent actively performing tasks. You +can also sort based on total time, # of syscalls made, PID #, and # of child processes +using the `-S` or `--sort` flag. The number of results defaults to 25 processes, but +can be changed using the `-c`/`--count` option. See `--help` for full details. + +```sh +$ ./strace-parser strace.txt + +Top 25 PIDs +----------- + + pid active (ms) wait (ms) total (ms) % active syscalls + ---------- ---------- --------- --------- --------- --------- + 8795 689.072 45773.832 46462.902 16.89% 23018 + 13408 679.432 55910.891 56590.320 16.65% 28593 + 6423 554.822 13175.485 13730.308 13.60% 13735 +... +``` + +Based on the summary, you can then view the details of syscalls made by one or more +procsses using the `-p`/`--pid` for a specific process, or `-s`/`--stats` flags for +a sorted list. `--stats` takes the same sorting and count options as summary. + +```sh +$ ./strace-parse strace.text -p 6423 + +PID 6423 +13735 syscalls, active time: 554.822ms, total time: 13730.308ms + + syscall count total max avg min errors + (ms) (ms) (ms) (ms) + --------------- -------- ---------- ---------- ---------- ---------- -------- + epoll_wait 628 13175.485 21.259 20.980 0.020 + clock_gettime 7326 199.500 0.249 0.027 0.013 + stat 2101 110.768 19.056 0.053 0.017 ENOENT: 2076 + ... + --------------- + + Parent PID: 495 + Child PIDs: 8383, 8418, 8419, 8420, 8421 + + Slowest file access times for PID 6423: + + open (ms) timestamp error file name + ----------- --------------- --------------- ---------- + 29.818 10:53:11.528954 /srv/gitlab-data/builds/2018_08/6174/954448.log + 12.309 10:53:46.708274 /srv/gitlab-data/builds/2018_08/5342/954186.log + 0.039 10:53:49.222110 /opt/gitlab/embedded/service/gitlab-rails/app/views/events/event/_note.html.haml + 0.035 10:53:49.125115 /opt/gitlab/embedded/service/gitlab-rails/app/views/events/event/_push.html.haml + ... +``` + +In the example above, we can see that file opening times on `/srv/gitlab-data` are +extremely slow, about 100X slower than `/opt/gitlab`. + +When nothing stands out in the results, a good way to get more context is to run `strace` +on your own GitLab instance while performing the action performed by the customer, +then compare summaries of both results and dive into the differences. + +#### Stats for the open syscall + +Rough numbers for calls to `open` and `openat` (used to access files) on various configurations. +Slow storage can cause the dreaded `DeadlineExceeded` error in Gitaly. + +Also [see this entry](https://docs.gitlab.com/ee/administration/operations/filesystem_benchmarking.html) +in the handbook for quick tests customers can perform to check their filesystem performance. + +Keep in mind that timing information from `strace` is often somewhat inaccurate, so +small differences should not be considered significant. + +|Setup | access times | +|:--------------|:--------------| +| EFS | 10 - 30ms | +| Local Storage | 0.01 - 1ms | + +## Networking + +### Ports + +```bash +# Find the programs that are listening on ports +netstat -plnt +ss -plnt +lsof -i -P | grep <port> +``` + +### Internet/DNS + +```bash +# Show domain IP address +dig +short example.com +nslookup example.com + +# Check DNS using specific nameserver +# 8.8.8.8 = google, 1.1.1.1 = cloudflare, 208.67.222.222 = opendns +dig @8.8.8.8 example.com +nslookup example.com 1.1.1.1 + +# Find host provider +whois <ip_address> | grep -i "orgname\|netname" + +# Curl headers with redirect +curl --head --location https://example.com +``` + +## Package Management + +```bash +# Debian/Ubuntu + +# List packages +dpkg -l +apt list --installed + +# Find an installed package +dpkg -l | grep <package> +apt list --installed | grep <package> + +# Install a package +dpkg -i <package_name>.deb +apt-get install <package> +apt install <package> + +# CentOS/RedHat + +# Install a package +yum install <package> +dnf install <package> # RHEL/CentOS 8+ + +rpm -ivh <package_name>.rpm + +# Find an installed package +rpm -qa | grep <package> +``` + +## Logs + +```bash +# Print last lines in log file where 'n' +# is the number of lines to print +tail -n /path/to/log/file +``` diff --git a/doc/administration/troubleshooting/test_environments.md b/doc/administration/troubleshooting/test_environments.md new file mode 100644 index 00000000000..075effc5dc3 --- /dev/null +++ b/doc/administration/troubleshooting/test_environments.md @@ -0,0 +1,126 @@ +--- +type: reference +--- + +# Apps for a Testing Environment + +This is the GitLab Support Team's collection of information regarding testing environments, +for use while troubleshooting. It is listed here for transparency, and it may be useful +for users with experience with these tools. If you are currently having an issue with +GitLab, you may want to check your [support options](https://about.gitlab.com/support/) +first, before attempting to use this information. + +NOTE: **Note:** +This page was initially written for Support Engineers, so some of the links +are only available internally at GitLab. + +## Docker + +The following were tested on docker containers running in the cloud. Support Engineers, +please see [these docs](https://gitlab.com/gitlab-com/dev-resources/tree/master/dev-resources#running-docker-containers) +on how to run Docker containers on `dev-resources`. Other setups haven't been tested, +but contributions are welcome. + +### GitLab + +Please see [our Docker test environment docs](https://docs.gitlab.com/ee/install/digitaloceandocker.html#create-new-gitlab-container) +for how to run GitLab on Docker. When spinning this up with `docker-machine`, ensure +you change a few things: + +1. Update the name of the `docker-machine` host. You can see a list of hosts + with `docker-machine ls`. +1. Expose the necessary ports using the `-p` flag. Docker normally doesn't + allow access to any ports it uses outside of the container, so they must be + explicitly exposed. +1. Add any necessary `gitlab.rb` configuration to the + `GITLAB_OMNIBUS_CONFIG` variable. + +For example, when the `docker-machine` host we want to use is `do-docker`: + +```sh +docker run --detach --name gitlab \ +--env GITLAB_OMNIBUS_CONFIG="external_url 'http://$(docker-machine ip do-docker)'; gitlab_rails['gitlab_shell_ssh_port'] = 2222;" \ +--hostname $(docker-machine ip do-docker) \ +-p 80:80 -p 2222:22 \ +gitlab/gitlab-ee:11.5.3-ee.0 +``` + +### SAML + +#### SAML for Authentication + +We can use the [test-saml-idp Docker image](https://hub.docker.com/r/jamedjo/test-saml-idp) +to do the work for us: + +```sh +docker run --name gitlab_saml -p 8080:8080 -p 8443:8443 \ +-e SIMPLESAMLPHP_SP_ENTITY_ID=<GITLAB_IP_OR_DOMAIN> \ +-e SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE=<GITLAB_IP_OR_DOMAIN>/users/auth/saml/callback \ +-d jamedjo/test-saml-idp +``` + +The following will also need to go in your `/etc/gitlab/gitlab.rb`. See [our SAML docs](https://docs.gitlab.com/ee/integration/saml.html) +for more, as well as the list of [default usernames, passwords, and emails](https://hub.docker.com/r/jamedjo/test-saml-idp/#usage). + +```ruby +gitlab_rails['omniauth_enabled'] = true +gitlab_rails['omniauth_allow_single_sign_on'] = ['saml'] +gitlab_rails['omniauth_sync_email_from_provider'] = 'saml' +gitlab_rails['omniauth_sync_profile_from_provider'] = ['saml'] +gitlab_rails['omniauth_sync_profile_attributes'] = ['email'] +gitlab_rails['omniauth_auto_sign_in_with_provider'] = 'saml' +gitlab_rails['omniauth_block_auto_created_users'] = false +gitlab_rails['omniauth_auto_link_ldap_user'] = false +gitlab_rails['omniauth_auto_link_saml_user'] = true +gitlab_rails['omniauth_providers'] = [ + { + "name" => "saml", + "label" => "SAML", + "args" => { + assertion_consumer_service_url: '<GITLAB_IP_OR_DOMAIN>/users/auth/saml/callback', + idp_cert_fingerprint: '119b9e027959cdb7c662cfd075d9e2ef384e445f', + idp_sso_target_url: '<SAML_IP_OR_DOMAIN>:8080/simplesaml/saml2/idp/SSOService.php', + issuer: '<GITLAB_IP_OR_DOMAIN>', + name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent' + } + } +] +``` + +#### GroupSAML for GitLab.com + +See [the GDK SAML documentation](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/saml.md). + +### ElasticSearch + +```sh +docker run -d --name elasticsearch \ +-p 9200:9200 -p 9300:9300 \ +-e "discovery.type=single-node" \ +docker.elastic.co/elasticsearch/elasticsearch:5.5.1 +``` + +Then confirm it works in the browser at `curl http://<IP_ADDRESS>:9200/_cat/health`. +ElasticSearch's default username is `elastic` and password is `changeme`. + +### PlantUML + +See [our PlantUML docs](../integration/plantuml.md#docker) +on running PlantUML in Docker. + +### Jira + +```sh +docker run -d -p 8081:8080 cptactionhank/atlassian-jira:latest +``` + +Then go to `<IP_ADDRESS>:8081` in the browser to set it up. This requires a +Jira license. + +### Grafana + +```sh +docker run -d --name grafana -e "GF_SECURITY_ADMIN_PASSWORD=gitlab" -p 3000:3000 grafana/grafana +``` + +Access it at `<IP_ADDRESS>:3000`. diff --git a/doc/api/groups.md b/doc/api/groups.md index d7f5b1b463b..8b13462b887 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -156,7 +156,8 @@ Parameters: | `with_issues_enabled` | boolean | no | Limit by projects with issues feature enabled. Default is `false` | | `with_merge_requests_enabled` | boolean | no | Limit by projects with merge requests feature enabled. Default is `false` | | `with_shared` | boolean | no | Include projects shared to this group. Default is `true` | -| `include_subgroups` | boolean | no | Include projects in subgroups of this group. Default is `false` | +| `include_subgroups` | boolean | no | Include projects in subgroups of this group. Default is `false` | +| `min_access_level` | integer | no | Limit to projects where current user has at least this [access level](members.md) | | `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | | `with_security_reports` | boolean | no | **(ULTIMATE)** Return only projects that have security reports artifacts present in any of their builds. This means "projects with security reports enabled". Default is `false` | diff --git a/doc/api/issues.md b/doc/api/issues.md index 7498d2d840b..a89a6e7c5cc 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -573,6 +573,18 @@ the `weight` parameter: } ``` +Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) will additionally see +the `epic_iid` property: + +```json +{ + "project_id" : 4, + "description" : "Omnis vero earum sunt corporis dolor et placeat.", + "epic_iid" : 42, + ... +} +``` + **Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API. **Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists. diff --git a/doc/api/releases/index.md b/doc/api/releases/index.md index 850cf57a06f..8d5b3a65789 100644 --- a/doc/api/releases/index.md +++ b/doc/api/releases/index.md @@ -57,6 +57,19 @@ Example response: "committer_email":"admin@example.com", "committed_date":"2019-01-03T01:55:38.000Z" }, + "milestone":{ + "id":51, + "iid":1, + "project_id":24, + "title":"v1.0-rc", + "description":"Voluptate fugiat possimus quis quod aliquam expedita.", + "state":"closed", + "created_at":"2019-07-12T19:45:44.256Z", + "updated_at":"2019-07-12T19:45:44.256Z", + "due_date":"2019-08-16T11:00:00.256Z", + "start_date":"2019-07-30T12:00:00.256Z", + "web_url":"http://localhost:3000/root/awesome-app/-/milestones/1" + }, "assets":{ "count":6, "sources":[ @@ -205,6 +218,19 @@ Example response: "committer_email":"admin@example.com", "committed_date":"2019-01-03T01:53:28.000Z" }, + "milestone":{ + "id":51, + "iid":1, + "project_id":24, + "title":"v1.0-rc", + "description":"Voluptate fugiat possimus quis quod aliquam expedita.", + "state":"closed", + "created_at":"2019-07-12T19:45:44.256Z", + "updated_at":"2019-07-12T19:45:44.256Z", + "due_date":"2019-08-16T11:00:00.256Z", + "start_date":"2019-07-30T12:00:00.256Z", + "web_url":"http://localhost:3000/root/awesome-app/-/milestones/1" + }, "assets":{ "count":4, "sources":[ @@ -240,23 +266,24 @@ Create a Release. You need push access to the repository to create a Release. POST /projects/:id/releases ``` -| Attribute | Type | Required | Description | -| -------------------| -------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). | -| `name` | string | yes | The release name. | -| `tag_name` | string | yes | The tag where the release will be created from. | -| `description` | string | yes | The description of the release. You can use [markdown](../../user/markdown.md). | -| `ref` | string | no | If `tag_name` doesn't exist, the release will be created from `ref`. It can be a commit SHA, another tag name, or a branch name. | -| `assets:links` | array of hash | no | An array of assets links. | -| `assets:links:name`| string | required by: `assets:links` | The name of the link. | -| `assets:links:url` | string | required by: `assets:links` | 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`). | +| Attribute | Type | Required | Description | +| -------------------| --------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). | +| `name` | string | yes | The release name. | +| `tag_name` | string | yes | The tag where the release will be created from. | +| `description` | string | yes | The description of the release. You can use [markdown](../../user/markdown.md). | +| `ref` | string | no | If `tag_name` doesn't exist, the release will be created from `ref`. It can be a commit SHA, another tag name, or a branch name. | +| `milestone` | string | no | The title of the milestone the release is associated with. | +| `assets:links` | array of hash | no | An array of assets links. | +| `assets:links:name`| string | required by: `assets:links` | The name of the link. | +| `assets:links:url` | string | required by: `assets:links` | 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: ```sh curl --header 'Content-Type: application/json' --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" \ - --data '{ "name": "New release", "tag_name": "v0.3", "description": "Super nice release", "assets": { "links": [{ "name": "hoge", "url": "https://google.com" }] } }' \ + --data '{ "name": "New release", "tag_name": "v0.3", "description": "Super nice release", "milestone": "v1.0-rc", "assets": { "links": [{ "name": "hoge", "url": "https://google.com" }] } }' \ --request POST https://gitlab.example.com/api/v4/projects/24/releases ``` @@ -294,6 +321,19 @@ Example response: "committer_email":"admin@example.com", "committed_date":"2019-01-03T01:55:38.000Z" }, + "milestone":{ + "id":51, + "iid":1, + "project_id":24, + "title":"v1.0-rc", + "description":"Voluptate fugiat possimus quis quod aliquam expedita.", + "state":"active", + "created_at":"2019-07-12T19:45:44.256Z", + "updated_at":"2019-07-12T19:45:44.256Z", + "due_date":"2019-08-16T11:00:00.256Z", + "start_date":"2019-07-30T12:00:00.256Z", + "web_url":"http://localhost:3000/root/awesome-app/-/milestones/1" + }, "assets":{ "count":5, "sources":[ @@ -334,18 +374,19 @@ Update a Release. PUT /projects/:id/releases/:tag_name ``` -| Attribute | Type | Required | Description | -| ------------- | -------------- | -------- | -------------------------------------------------------------------------------------------------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). | -| `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`). | +| Attribute | Type | Required | Description | +| ------------- | -------------- | -------- | --------------------------------------------------------------------------------------------------------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). | +| `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). | +| `milestone` | string | no | The title of the milestone to associate with the release (`""` to remove the milestone from the release). | +| `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: ```sh -curl --request PUT --data name="new name" --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1" +curl --header 'Content-Type: application/json' --request PUT --data '{"name": "new name", "milestone": "v1.0"}' --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1" ``` Example response: @@ -382,6 +423,19 @@ Example response: "committer_email":"admin@example.com", "committed_date":"2019-01-03T01:53:28.000Z" }, + "milestone":{ + "id":53, + "iid":2, + "project_id":24, + "title":"v1.0", + "description":"Voluptate fugiat possimus quis quod aliquam expedita.", + "state":"active", + "created_at":"2019-09-01T13:00:00.256Z", + "updated_at":"2019-09-01T13:00:00.256Z", + "due_date":"2019-09-20T13:00:00.256Z", + "start_date":"2019-09-05T12:00:00.256Z", + "web_url":"http://localhost:3000/root/awesome-app/-/milestones/3" + }, "assets":{ "count":4, "sources":[ diff --git a/doc/api/templates/dockerfiles.md b/doc/api/templates/dockerfiles.md index a08b8d33693..ee271c31b49 100644 --- a/doc/api/templates/dockerfiles.md +++ b/doc/api/templates/dockerfiles.md @@ -1,5 +1,13 @@ +--- +type: reference +--- + # Dockerfiles API +In GitLab, there is an API endpoint available for Dockerfiles. For more +information on Dockerfiles, see the +[Docker documentation](https://docs.docker.com/engine/reference/builder/). + ## List Dockerfile templates Get all Dockerfile templates. @@ -111,3 +119,15 @@ Example response: "content": "# This file is a template, and might need editing before it works on your project.\n# This Dockerfile installs a compiled binary into a bare system.\n# You must either commit your compiled binary into source control (not recommended)\n# or build the binary first as part of a CI/CD pipeline.\n\nFROM buildpack-deps:jessie\n\nWORKDIR /usr/local/bin\n\n# Change `app` to whatever your binary is called\nAdd app .\nCMD [\"./app\"]\n" } ``` + +<!-- ## 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/api/templates/gitignores.md b/doc/api/templates/gitignores.md index bf6a914e120..0b6d944cb62 100644 --- a/doc/api/templates/gitignores.md +++ b/doc/api/templates/gitignores.md @@ -1,4 +1,12 @@ -# `.gitignore` API +--- +type: reference +--- + +# .gitignore API + +In GitLab, there is an API endpoint available for `.gitignore`. For more +information on `gitignore`, see the +[Git documentation](https://git-scm.com/docs/gitignore). ## List `.gitignore` templates @@ -123,3 +131,15 @@ Example response: "content": "*.gem\n*.rbc\n/.config\n/coverage/\n/InstalledFiles\n/pkg/\n/spec/reports/\n/spec/examples.txt\n/test/tmp/\n/test/version_tmp/\n/tmp/\n\n# Used by dotenv library to load environment variables.\n# .env\n\n## Specific to RubyMotion:\n.dat*\n.repl_history\nbuild/\n*.bridgesupport\nbuild-iPhoneOS/\nbuild-iPhoneSimulator/\n\n## Specific to RubyMotion (use of CocoaPods):\n#\n# We recommend against adding the Pods directory to your .gitignore. However\n# you should judge for yourself, the pros and cons are mentioned at:\n# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control\n#\n# vendor/Pods/\n\n## Documentation cache and generated files:\n/.yardoc/\n/_yardoc/\n/doc/\n/rdoc/\n\n## Environment normalization:\n/.bundle/\n/vendor/bundle\n/lib/bundler/man/\n\n# for a library or gem, you might want to ignore these files since the code is\n# intended to run in multiple environments; otherwise, check them in:\n# Gemfile.lock\n# .ruby-version\n# .ruby-gemset\n\n# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:\n.rvmrc\n" } ``` + +<!-- ## 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/api/templates/gitlab_ci_ymls.md b/doc/api/templates/gitlab_ci_ymls.md index 11ec7360e06..80f94b953e3 100644 --- a/doc/api/templates/gitlab_ci_ymls.md +++ b/doc/api/templates/gitlab_ci_ymls.md @@ -1,5 +1,13 @@ +--- +type: reference +--- + # GitLab CI YMLs API +In GitLab, there is an API endpoint available to work with CI YMLs. For more +information on CI/CD pipeline configuration in GitLab, see the +[configuration reference documentation](../../ci/yaml/README.md). + ## List GitLab CI YML templates Get all GitLab CI YML templates. @@ -123,3 +131,15 @@ Example response: "content": "# This file is a template, and might need editing before it works on your project.\n# Official language image. Look for the different tagged releases at:\n# https://hub.docker.com/r/library/ruby/tags/\nimage: \"ruby:2.5\"\n\n# Pick zero or more services to be used on all builds.\n# Only needed when using a docker container to run your tests in.\n# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service\nservices:\n - mysql:latest\n - redis:latest\n - postgres:latest\n\nvariables:\n POSTGRES_DB: database_name\n\n# Cache gems in between builds\ncache:\n paths:\n - vendor/ruby\n\n# This is a basic example for a gem or script which doesn't use\n# services such as redis or postgres\nbefore_script:\n - ruby -v # Print out ruby version for debugging\n # Uncomment next line if your rails app needs a JS runtime:\n # - apt-get update -q && apt-get install nodejs -yqq\n - bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby\n\n# Optional - Delete if not using `rubocop`\nrubocop:\n script:\n - rubocop\n\nrspec:\n script:\n - rspec spec\n\nrails:\n variables:\n DATABASE_URL: \"postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB\"\n script:\n - rails db:migrate\n - rails db:seed\n - rails test\n\n# This deploy job uses a simple deploy flow to Heroku, other providers, e.g. AWS Elastic Beanstalk\n# are supported too: https://github.com/travis-ci/dpl\ndeploy:\n type: deploy\n environment: production\n script:\n - gem install dpl\n - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_PRODUCTION_KEY\n" } ``` + +<!-- ## 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/api/templates/licenses.md b/doc/api/templates/licenses.md index 5feb1e498bd..92466c73bd3 100644 --- a/doc/api/templates/licenses.md +++ b/doc/api/templates/licenses.md @@ -1,5 +1,14 @@ +--- +type: reference +--- + # Licenses API +In GitLab, there is an API endpoint available for working with various open +source license templates. For more information on the terms of various +licenses, see [this site](https://choosealicense.com/) or any of the many other +resources available online. + ## List license templates Get all license templates. @@ -145,3 +154,15 @@ Example response: "content": "The MIT License (MIT)\n\nCopyright (c) 2016 John Doe\n [...]" } ``` + +<!-- ## 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/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md index 730e46f994e..a48da557e09 100644 --- a/doc/ci/docker/using_docker_build.md +++ b/doc/ci/docker/using_docker_build.md @@ -580,9 +580,10 @@ For private and internal projects: If you want to use your own Docker images for docker-in-docker there are a few things you need to do in addition to the steps in the [docker-in-docker](#use-docker-in-docker-workflow-with-docker-executor) section: 1. Update the `image` and `service` to point to your registry. -1. Add a service [alias](https://docs.gitlab.com/ee/ci/yaml/#servicesalias) +1. Add a service [alias](https://docs.gitlab.com/ee/ci/yaml/#servicesalias). -Below is an example of how your `.gitlab-ci.yml` should look like, assuming you have it configured with [TLS enabled](#tls-enabled): +Below is an example of what your `.gitlab-ci.yml` should look like, +assuming you have it configured with [TLS enabled](#tls-enabled): ```yaml build: @@ -603,7 +604,7 @@ Below is an example of how your `.gitlab-ci.yml` should look like, assuming you - docker run my-docker-image /script/to/run/tests ``` -If you forget to set the service alias the `docker:19.03.1` image won't find the +If you forget to set the service alias, the `docker:19.03.1` image won't find the `dind` service, and an error like the following is thrown: ```sh diff --git a/doc/ci/environments.md b/doc/ci/environments.md index f6c47a99712..c2d444cb1d6 100644 --- a/doc/ci/environments.md +++ b/doc/ci/environments.md @@ -675,9 +675,10 @@ fetch line: fetch = +refs/environments/*:refs/remotes/origin/environments/* ``` -### Scoping environments with specs **(PREMIUM)** +### Scoping environments with specs -> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2112) in [GitLab Premium](https://about.gitlab.com/pricing/) 9.4. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2112) in [GitLab Premium](https://about.gitlab.com/pricing/) 9.4. +> - [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/30779) to Core in Gitlab 12.2. You can limit the environment scope of a variable by defining which environments it can be available for. diff --git a/doc/ci/large_repositories/index.md b/doc/ci/large_repositories/index.md index 29d649ad717..b1359537fca 100644 --- a/doc/ci/large_repositories/index.md +++ b/doc/ci/large_repositories/index.md @@ -84,7 +84,7 @@ Fork workflow from GitLab Runner's perspective is stored as a separate repositor with separate worktree. That means that GitLab Runner cannot optimize the usage of worktrees and you might have to instruct GitLab Runner to use that. -In such cases, ideally you want to make the GitLab Runner executor be used only used only +In such cases, ideally you want to make the GitLab Runner executor be used only for the given project and not shared across different projects to make this process more efficient. diff --git a/doc/ci/multi_project_pipelines.md b/doc/ci/multi_project_pipelines.md index 61f260cb70d..377ae9717b2 100644 --- a/doc/ci/multi_project_pipelines.md +++ b/doc/ci/multi_project_pipelines.md @@ -176,6 +176,18 @@ Upstream pipelines take precedence over downstream ones. If there are two variables with the same name defined in both upstream and downstream projects, the ones defined in the upstream project will take precedence. +### Mirroring status from triggered pipeline + +You can mirror the pipeline status from the triggered pipeline to the source +bridge job by using `strategy: depend`. For example: + +```yaml +trigger_job: + trigger: + project: my/project + strategy: depend +``` + ### Mirroring status from upstream pipeline You can mirror the pipeline status from an upstream pipeline to a bridge job by diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 5a15b907da0..438b7c03b51 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -401,7 +401,7 @@ Once you set them, they will be available for all subsequent pipelines. You can limit the environment scope of a variable by [defining which environments][envs] it can be available for. -To learn more about about scoping environments, see [Scoping environments with specs](../environments.md#scoping-environments-with-specs-premium). +To learn more about about scoping environments, see [Scoping environments with specs](../environments.md#scoping-environments-with-specs). ### Deployment environment variables diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 06bd9e68a18..bfba21646b5 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -100,7 +100,7 @@ The following table lists available parameters for jobs: | [`stage`](#stage) | Defines a job stage (default: `test`). | | [`only`](#onlyexcept-basic) | Limit when jobs are created. Also available: [`only:refs`, `only:kubernetes`, `only:variables`, and `only:changes`](#onlyexcept-advanced). | | [`except`](#onlyexcept-basic) | Limit when jobs are not created. Also available: [`except:refs`, `except:kubernetes`, `except:variables`, and `except:changes`](#onlyexcept-advanced). | -| [`rules`](#rules) | List of conditions to evaluate and determine selected attributes of a build and whether or not it is created. May not be used alongside `only`/`except`. +| [`rules`](#rules) | List of conditions to evaluate and determine selected attributes of a job, and whether or not it is created. May not be used alongside `only`/`except`. | | [`tags`](#tags) | List of tags which are used to select Runner. | | [`allow_failure`](#allow_failure) | Allow job to fail. Failed job doesn't contribute to commit status. | | [`when`](#when) | When to run job. Also available: `when:manual` and `when:delayed`. | @@ -113,7 +113,7 @@ The following table lists available parameters for jobs: | [`parallel`](#parallel) | How many instances of a job should be run in parallel. | | [`trigger`](#trigger-premium) | Defines a downstream pipeline trigger. | | [`include`](#include) | Allows this job to include external YAML files. Also available: `include:local`, `include:file`, `include:template`, and `include:remote`. | -| [`extends`](#extends) | Configuration entries that this job is going to inherit from. | +| [`extends`](#extends) | Configuration entries that this job is going to inherit from. | | [`pages`](#pages) | Upload the result of a job to use with GitLab Pages. | | [`variables`](#variables) | Define job variables on a job level. | @@ -693,31 +693,41 @@ and triggers the `docker build service one` job. ### `rules` -Using `rules` allows for a list of individual rule objects to be evaluated +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29011) in GitLab 12.3. + +`rules` allows for a list of individual rule objects to be evaluated *in order*, until one matches and dynamically provides attributes to the job. Available rule clauses include: -- `if` (similar to [`only:variables`](#onlyvariablesexceptvariables)). -- `changes` (same as [`only:changes`](#onlychangesexceptchanges)). +- [`if`](#rulesif) + (similar to [`only:variables`](#onlyvariablesexceptvariables)). +- [`changes`](#ruleschanges) + (same as [`only:changes`](#onlychangesexceptchanges)). -For example, using `if`: +For example, using `if`. This configuration specifies that `job` should be built +and run for every pipeline on merge requests targeting `master`, regardless of +the status of other builds: ```yaml job: script: "echo Hello, Rules!" rules: - - if: '$CI_MERGE_REQUEST_TARGET_BRANCH == "master"' # This rule will be evaluated + - if: '$CI_MERGE_REQUEST_TARGET_BRANCH == "master"' when: always - - if: '$VAR =~ /pattern/' # This rule will only be evaluated if the first does not match + - if: '$VAR =~ /pattern/' when: manual - - when: on_success # A Rule entry with no conditional clauses evaluates to true. If neither of the first two Rules match, this one will and set job:when to "on_success" + - when: on_success ``` -If the first rule does not match, further rules will be evaluated sequentially -until a match is found. The above configuration will specify that `job` should -be built and run for every pipeline on merge requests targeting `master`, -regardless of the status of other builds. +In this example, if the first rule: + +- Matches, the job will be given the `when:always` attribute. +- Does not match, the second and third rules will be evaluated sequentially + until a match is found. That is, the job will be given either the: + - `when: manual` attribute if the second rule matches. + - `when: on_success` attribute if the second rule does not match. The third + rule will always match when reached because it has no conditional clauses. #### `rules:if` @@ -744,10 +754,9 @@ at all, the behavior defaults to `job:when`, which continues to default to #### `rules:changes` `changes` works exactly the same way as [`only`/`except`](#onlychangesexceptchanges), -accepting an array of paths. The following configuration configures a job to be -run manually if `Dockerfile` has changed OR `$VAR == "string value"`. Otherwise -it is set to `when:on_success` by the last rule, where 0 clauses evaluate as -vacuously true. +accepting an array of paths. + +For example: ```yaml docker build: @@ -762,12 +771,20 @@ docker build: ``` -#### Complex Rule Clauses +In this example, a job either set to: + +- Run manually if `Dockerfile` has changed OR `$VAR == "string value"`. +- `when:on_success` by the last rule, where no earlier clauses evaluate to true. + +#### Complex rule clauses To conjoin `if` and `changes` clauses with an AND, use them in the same rule. -Here we run the job manually if `Dockerfile` or any file in `docker/scripts/` -has changed AND `$VAR == "string value"`. Otherwise, the job will not be -included in the pipeline. + +In the following example: + +- We run the job manually if `Dockerfile` or any file in `docker/scripts/` + has changed AND `$VAR == "string value"`. +- Otherwise, the job will not be included in the pipeline. ```yaml docker build: @@ -781,18 +798,27 @@ docker build: # - when: never would be redundant here, this is implied any time rules are listed. ``` -The only clauses currently available are `if` and `changes`. Keywords such as -`branches` or `refs` that are currently available for `only`/`except` are not -yet available in `rules` as they are being individually considered for their -usage and behavior in the newer context. +The only clauses currently available are: + +- `if` +- `changes` + +Keywords such as `branches` or `refs` that are currently available for +`only`/`except` are not yet available in `rules` as they are being individually +considered for their usage and behavior in this context. #### Permitted attributes -The only job attributes currently set by `rules` are `when` and `start_in`, if -`when` is set to `delayed`. A job will be included in a pipeline if `when` is -evaluated to any value except `never`. +The only job attributes currently set by `rules` are: + +- `when`. +- `start_in`, if `when` is set to `delayed`. + +A job will be included in a pipeline if `when` is evaluated to any value +except `never`. -Delayed jobs require a `start_in` value, so rule objects do as well. For example: +Delayed jobs require a `start_in` value, so rule objects do as well. For +example: ```yaml docker build: @@ -806,9 +832,9 @@ docker build: ``` -Additional Job configuration may be added to rules in the future, if something -useful isn't available, please open an issue on -[Gitlab CE](https://www.gitlab.com/gitlab-org/gitlab-ce/issues). +Additional job configuration may be added to rules in the future. If something +useful isn't available, please +[open an issue](https://www.gitlab.com/gitlab-org/gitlab-ce/issues). ### `tags` @@ -1801,7 +1827,8 @@ and bring back the old behavior. ### `needs` -> Introduced in GitLab 12.2. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/47063) in GitLab 12.2. +> - In GitLab 12.3, maximum number of jobs in `needs` array raised from five to 50. The `needs:` keyword enables executing jobs out-of-order, allowing you to implement a [directed acyclic graph](../directed_acyclic_graph/index.md) in your `.gitlab-ci.yml`. @@ -1852,30 +1879,26 @@ This example creates three paths of execution: #### Requirements and limitations -1. If `needs:` is set to point to a job that is not instantiated - because of `only/except` rules or otherwise does not exist, the - job will fail. -1. Note that on day one of the launch, we are temporarily limiting the - maximum number of jobs that a single job can need in the `needs:` array. Track - our [infrastructure issue](https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/7541) - for details on the current limit. -1. If you use `dependencies:` with `needs:`, it's important that you - do not mark a job as having a dependency on something that won't - have been run at the time it needs it. It's better to use both - keywords in this case so that GitLab handles the ordering appropriately. -1. It is impossible for now to have `needs: []` (empty needs), - the job always needs to depend on something, unless this is the job - in the first stage (see [gitlab-ce#65504](https://gitlab.com/gitlab-org/gitlab-ce/issues/65504)). -1. If `needs:` refers to a job that is marked as `parallel:`. - the current job will depend on all parallel jobs created. -1. `needs:` is similar to `dependencies:` in that it needs to use jobs from - prior stages, meaning it is impossible to create circular - dependencies or depend on jobs in the current stage (see [gitlab-ce#65505](https://gitlab.com/gitlab-org/gitlab-ce/issues/65505)). -1. Related to the above, stages must be explicitly defined for all jobs - that have the keyword `needs:` or are referred to by one. -1. For self-managed users, the feature must be turned on using the `ci_dag_support` - feature flag. The `ci_dag_limit_needs` option, if set, will limit the number of - jobs that a single job can need to `50`. If unset, the limit is `5`. +- If `needs:` is set to point to a job that is not instantiated + because of `only/except` rules or otherwise does not exist, the + pipeline will be created with YAML error. +- We are temporarily limiting the maximum number of jobs that a single job can + need in the `needs:` array: + - For GitLab.com, the limit is five. For more information, see our + [infrastructure issue](https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/7541). + - For self-managed instances, the limit is: + - Five by default (`ci_dag_limit_needs` feature flag is enabled). + - 50 if the `ci_dag_limit_needs` feature flag is disabled. +- It is impossible for now to have `needs: []` (empty needs), + the job always needs to depend on something, unless this is the job + in the first stage (see [gitlab-ce#65504](https://gitlab.com/gitlab-org/gitlab-ce/issues/65504)). +- If `needs:` refers to a job that is marked as `parallel:`. + the current job will depend on all parallel jobs created. +- `needs:` is similar to `dependencies:` in that it needs to use jobs from + prior stages, meaning it is impossible to create circular + dependencies or depend on jobs in the current stage (see [gitlab-ce#65505](https://gitlab.com/gitlab-org/gitlab-ce/issues/65505)). +- Related to the above, stages must be explicitly defined for all jobs + that have the keyword `needs:` or are referred to by one. ### `coverage` diff --git a/doc/development/README.md b/doc/development/README.md index 3912a828dec..bbe73570f49 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -22,6 +22,7 @@ description: 'Learn how to contribute to GitLab.' - [Guidelines for implementing Enterprise Edition features](ee_features.md) - [Security process for developers](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#security-releases-critical-non-critical-as-a-developer) - [Requesting access to Chatops on GitLab.com](chatops_on_gitlabcom.md#requesting-access) (for GitLabbers) +- [Danger bot](dangerbot.md) ## UX and Frontend guides diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 2adca2dae28..ee5fc553e27 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -651,7 +651,7 @@ We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/ha [shell-charts]: https://docs.gitlab.com/charts/charts/gitlab/gitlab-shell/ [shell-source]: ../install/installation.md#install-gitlab-shell [pages-omnibus]: ../administration/pages/index.md -[pages-charts]: https://gitlab.com/charts/gitlab/issues/37 +[pages-charts]: https://gitlab.com/gitlab-org/charts/gitlab/issues/37 [pages-source]: ../install/installation.md#install-gitlab-pages [pages-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/pages.md [registry-omnibus]: ../administration/container_registry.md#container-registry-domain-configuration @@ -673,9 +673,9 @@ We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/ha [grafana-omnibus]: ../administration/monitoring/performance/grafana_configuration.md [grafana-charts]: https://github.com/helm/charts/tree/master/stable/grafana [sentry-omnibus]: https://docs.gitlab.com/omnibus/settings/configuration.html#error-reporting-and-logging-with-sentry -[sentry-charts]: https://gitlab.com/charts/gitlab/issues/1319 +[sentry-charts]: https://gitlab.com/gitlab-org/charts/gitlab/issues/1319 [jaeger-omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4104 -[jaeger-charts]: https://gitlab.com/charts/gitlab/issues/1320 +[jaeger-charts]: https://gitlab.com/gitlab-org/charts/gitlab/issues/1320 [jaeger-source]: ../development/distributed_tracing.md#enabling-distributed-tracing [jaeger-gdk]: ../development/distributed_tracing.html#using-jaeger-in-the-gitlab-development-kit [redis-exporter-omnibus]: ../administration/monitoring/prometheus/redis_exporter.md @@ -687,7 +687,7 @@ We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/ha [gitlab-monitor-omnibus]: ../administration/monitoring/prometheus/gitlab_monitor_exporter.md [gitlab-monitor-charts]: https://docs.gitlab.com/charts/charts/gitlab/gitlab-monitor/index.html [node-exporter-omnibus]: ../administration/monitoring/prometheus/node_exporter.md -[node-exporter-charts]: https://gitlab.com/charts/gitlab/issues/1332 +[node-exporter-charts]: https://gitlab.com/gitlab-org/charts/gitlab/issues/1332 [mattermost-omnibus]: https://docs.gitlab.com/omnibus/gitlab-mattermost/ [mattermost-charts]: https://docs.mattermost.com/install/install-mmte-helm-gitlab-helm.html [minio-omnibus]: https://min.io/download @@ -705,7 +705,7 @@ We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/ha [certificate-management-source]: ../install/installation.md#using-https [certificate-management-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/https.md [geo-omnibus]: ../administration/geo/replication/index.md#setup-instructions -[geo-charts]: https://gitlab.com/charts/gitlab/issues/8 +[geo-charts]: https://gitlab.com/gitlab-org/charts/gitlab/issues/8 [geo-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/geo.md [ldap-omnibus]: ../administration/auth/ldap.md [ldap-charts]: https://docs.gitlab.com/charts/charts/globals.html#ldap diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md index 3fd95537eaa..a456bbc781f 100644 --- a/doc/development/background_migrations.md +++ b/doc/development/background_migrations.md @@ -61,8 +61,8 @@ migration classes must be defined in the namespace ## Scheduling -Scheduling a migration can be done in either a regular migration or a -post-deployment migration. To do so, simply use the following code while +Scheduling a background migration should be done in a post-deployment migration. +To do so, simply use the following code while replacing the class name and arguments with whatever values are necessary for your migration: @@ -283,10 +283,13 @@ the `services.properties` column. ## Testing -It is required to write tests for background migrations' scheduling migration -(either a regular migration or a post deployment migration), background -migration itself and a cleanup migration. You can use the `:migration` RSpec -tag when testing a regular / post deployment migration. +It is required to write tests for: + +- The background migrations' scheduling migration. +- The background migration itself. +- A cleanup migration. + +You can use the `:migration` RSpec tag when testing the migrations. See [README][migrations-readme]. When you do that, keep in mind that `before` and `after` RSpec hooks are going diff --git a/doc/development/code_review.md b/doc/development/code_review.md index b7d74b17eb3..bcfc0734c06 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -34,7 +34,7 @@ more than one approval, the last maintainer to review and approve it will also m ### Reviewer roulette -The `danger-review` CI job will randomly pick a reviewer and a maintainer for +The [Danger bot](dangerbot.md) randomly picks a reviewer and a maintainer for each area of the codebase that your merge request seems to touch. It only makes recommendations - feel free to override it if you think someone else is a better fit! @@ -116,8 +116,13 @@ warrant a comment could be: - Any benchmarking performed to complement the change - Potentially insecure code -Do not add these comments directly to the source code, unless the -reviewer requires you to do so. +Avoid: + +- Adding comments (referenced above, or TODO items) directly to the source code unless the reviewer requires you to do so. If the comments are added due to an actionable task, +a link to an issue must be included. +- Assigning merge requests with failed tests to maintainers. If the tests are failing and you have to assign, ensure you leave a comment with an explanation. +- Excessively mentioning maintainers through email or Slack (if the maintainer is reachable +through Slack). If you can't assign a merge request, `@` mentioning a maintainer in a comment is acceptable and in all other cases assigning the merge request is sufficient. This [saves reviewers time and helps authors catch mistakes earlier](https://www.ibm.com/developerworks/rational/library/11-proven-practices-for-peer-review/index.html#__RefHeading__97_174136755). diff --git a/doc/development/dangerbot.md b/doc/development/dangerbot.md new file mode 100644 index 00000000000..5fc5886e3a2 --- /dev/null +++ b/doc/development/dangerbot.md @@ -0,0 +1,116 @@ +# Danger bot + +The GitLab CI pipeline includes a `danger-review` job that uses [Danger](https://github.com/danger/danger) +to perform a variety of automated checks on the code under test. + +Danger is a gem that runs in the CI environment, like any other analysis tool. +What sets it apart from, e.g., Rubocop, is that it's designed to allow you to +easily write arbitrary code to test properties of your code or changes. To this +end, it provides a set of common helpers and access to information about what +has actually changed in your environment, then simply runs your code! + +If Danger is asking you to change something about your merge request, it's best +just to make the change. If you want to learn how Danger works, or make changes +to the existing rules, then this is the document for you. + +## Operation + +On startup, Danger reads a [`Dangerfile`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/Dangerfile) +from the project root. GitLab's Danger code is decomposed into a set of helpers +and plugins, all within the [`danger/`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/danger/) +subdirectory, so ours just tells Danger to load it all. Danger will then run +each plugin against the merge request, collecting the output from each. A plugin +may output notifications, warnings, or errors, all of which are copied to the +CI job's log. If an error happens, the CI job (and so the entire pipeline) will +be failed. + +On merge requests, Danger will also copy the output to a comment on the MR +itself, increasing visibility. + +## Development guidelines + +Danger code is Ruby code, so all our [usual backend guidelines](README.md#backend-guides) +continue to apply. However, there are a few things that deserve special emphasis. + +### When to use Danger + +Danger is a powerful tool and flexible tool, but not always the most appropriate +way to solve a given problem or workflow. + +First, be aware of GitLab's [commitment to dogfooding](https://about.gitlab.com/handbook/engineering/#dogfooding). +The code we write for Danger is GitLab-specific, and it **may not** be most +appropriate place to implement functionality that addresses a need we encounter. +Our users, customers, and even our own satellite projects, such as [Gitaly](https://gitlab.com/gitlab-org/gitaly), +often face similar challenges, after all. Think about how you could fulfil the +same need while ensuring everyone can benefit from the work, and do that instead +if you can. + +If a standard tool (e.g. `rubocop`) exists for a task, it is better to use it +directly, rather than calling it via Danger. Running and debugging the results +of those tools locally is easier if Danger isn't involved, and unless you're +using some Danger-specific functionality, there's no benefit to including it in +the Danger run. + +Danger is well-suited to prototyping and rapidly iterating on solutions, so if +what we want to build is unclear, a solution in Danger can be thought of as a +trial run to gather information about a product area. If you're doing this, make +sure the problem you're trying to solve, and the outcomes of that prototyping, +are captured in an issue or epic as you go along. This will help us to address +the need as part of the product in a future version of GitLab! + +### Implementation details + +Implement each task as an isolated piece of functionality and place it in its +own directory under `danger` as `danger/<task-name>/Dangerfile`. + +Add a line to the top-level `Dangerfile` to ensure it is loaded like: + +```ruby +danger.import_dangerfile('danger/<task-name>') +``` + +Each task should be isolated from the others, and able to function in isolation. +If there is code that should be shared between multiple tasks, add a plugin to +`danger/plugins/...` and require it in each task that needs it. You can also +create plugins that are specific to a single task, which is a natural place for +complex logic related to that task. + +Danger code is just Ruby code. It should adhere to our coding standards, and +needs tests, like any other piece of Ruby in our codebase. However, we aren't +able to test a `Dangerfile` directly! So, to maximise test coverage, try to +minimize the number of lines of code in `danger/`. A non-trivial `Dangerfile` +should mostly call plugin code with arguments derived from the methods provided +by Danger. The plugin code itself should have unit tests. + +At present, we do this by putting the code in a module in `lib/gitlab/danger/...`, +and including it in the matching `danger/plugins/...` file. Specs can then be +added in `spec/lib/gitlab/danger/...`. + +You'll only know if your `Dangerfile` works by pushing the branch that contains +it to GitLab. This can be quite frustrating, as it significantly increases the +cycle time when developing a new task, or trying to debug something in an +existing one. If you've followed the guidelines above, most of your code can +be exercised locally in RSpec, minimizing the number of cycles you need to go +through in CI. However, you can speed these cycles up somewhat by emptying the +`.gitlab/ci/rails.gitlab-ci.yml` file in your merge request. Just don't forget +to revert the change before merging! + +You should add the `~Danger bot` label to the merge request before sending it +for review. + +## Current uses + +Here is a (non-exhaustive) list of the kinds of things Danger has been used for +at GitLab so far: + +- Coding style +- Database review workflow +- Documentation review workflow +- Merge request metrics +- Reviewer roulette workflow +- Single codebase effort + +## Limitations + +- [`danger local` does not work on GitLab](https://github.com/danger/danger/issues/458) +- Danger output is not added to a merge request comment if working on a fork. diff --git a/doc/development/database_review.md b/doc/development/database_review.md index 367a481ee11..157c64b514c 100644 --- a/doc/development/database_review.md +++ b/doc/development/database_review.md @@ -38,6 +38,7 @@ A Merge Request author's role is to: - If database review is needed, add the ~database label. - Use the [database changes](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/merge_request_templates/Database%20changes.md) merge request template, or include the appropriate items in the MR description. +- [Prepare the merge request for a database review](#how-to-prepare-the-merge-request-for-a-database-review). A database **reviewer**'s role is to: @@ -68,7 +69,7 @@ make sure you have applied the ~database label and rerun the `danger-review` CI job, or pick someone from the [`@gl-database` team](https://gitlab.com/groups/gl-database/-/group_members). -### How to prepare for speedy database reviews +### How to prepare the merge request for a database review In order to make reviewing easier and therefore faster, please consider preparing a comment and details for a database reviewer: diff --git a/doc/development/documentation/site_architecture/global_nav.md b/doc/development/documentation/site_architecture/global_nav.md index 753a636a779..be4d5b5353f 100644 --- a/doc/development/documentation/site_architecture/global_nav.md +++ b/doc/development/documentation/site_architecture/global_nav.md @@ -4,30 +4,74 @@ description: "Learn how GitLab docs' global navigation works and how to add new # Global navigation -> - [Introduced](https://gitlab.com/gitlab-org/gitlab-docs/merge_requests/362) -in GitLab 11.6. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab-docs/merge_requests/362) in GitLab 11.6. > - [Updated](https://gitlab.com/gitlab-org/gitlab-docs/merge_requests/482) in GitLab 12.1. +> - [Per-project](https://gitlab.com/gitlab-org/gitlab-docs/merge_requests/498) navigation added in GitLab 12.2. -The global nav adds to the left sidebar the ability to -navigate and explore the contents of GitLab's documentation. +Global navigation (the left-most pane in our three pane documentation) provides: -The global nav should be maintained consistent through time to allow the -users to locate their most-visited links easily to facilitate navigation. -Therefore, any updates must be carefully considered by the technical writers. +- A high-level grouped view of product features. +- The ability to discover new features by browsing the menu structure. +- A way to allow the reader to focus on product areas. +- The ability to refine landing pages, so they don't have to do all the work of surfacing + every page contained within the documentation. -## Adding new items to the global nav +## Adding new items -To add a new doc to the nav, first and foremost, check with the technical writing team: +All new pages need a new navigation item. Without a navigation, the page becomes "orphaned". That +is: -- If it's applicable -- What's the exact position the doc will be added to the nav +- The navigation shuts when the page is opened, and the reader loses their place. +- The page doesn't belong in a group with other pages. -Once you get their approval and their guidance in regards to the position on the nav, -read trhough this page to understand how it works, and submit a merge request to the -docs site, adding the doc you wish to include in the nav into the -[global nav data file](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/content/_data/global-nav.yaml). +This means the decision to create a new page is a decision to create new navigation item and vice +versa. -Don't forget to ask a technical writer to review your changes before merging. +### Where to add + +Documentation pages can be said to belong in the following groups: + +- GitLab users. This is documentation for day-to-day use of GitLab for users with any level + of permissions, from Reporter to Owner. +- GitLab administrators. This tends to be documentation for self-managed instances that requires + access to the underlying infrastructure hosting GitLab. +- Other documentation. This includes documentation for customers outside their day-to-day use of + GitLab and for contributors. Documentation that doesn't fit in the other groups belongs here. + +With these groups in mind, the following are general rules for where new items should be added. + +- User documentation for: + - Group-level features belongs under **Groups**. + - Project-level features belongs under **Projects**. + - Features outside a group or project level (sometimes called "instance-level") can be placed at + the top-level, but care must be taken not to overwhelm that top-level space. If possible, such + features could be grouped in some way. + - Outside the above, most other miscellaneous user documentation belongs under **User**. +- Administration documentation belongs under **Administrator**. +- Other documentation belongs at the top-level, but care must be taken to not create an enormously + long top-level navigation, which defeats the purpose of it. + +NOTE: **Note:** +Making all documentation and navigation items adhere to these principles is being progressively +rolled out. + +### What to add + +Having decided where to add a navigation element, the next step is deciding what to add. The +mechanics of what is required is [documented below](#data-file) but, in principle: + +- Navigation item text (that which the reader sees) should: + - Be as short as possible. + - Be contextual. It's rare to need to repeat text from a parent item. + - Avoid jargon or terms of art, unless ubiquitous. For example, **CI** is an acceptable + substitution for **Continuous Integration**. +- Navigation links must follow the rules documented in the [data file](#data-file). +- EE badging is subject to the following: + - Required when linking to an EE-only page. + - Not required when linking to a page that is a mix of CE and EE-only content. + - Required when all sub-items are EE-only. In this case, no sub-items are EE badged. + - Not required when sub-items are a mix of CE and EE-only items. In this case, each item is + badged appropriately. ## How it works @@ -55,7 +99,7 @@ To see the improvements planned, check the [global nav epic](https://gitlab.com/groups/gitlab-com/-/epics/21). CAUTION: **Attention!** -**Do not** [add items](#adding-new-items-to-the-global-nav) to the global nav without +**Do not** [add items](#adding-new-items) to the global nav without the consent of one of the technical writers. ## Composition @@ -70,8 +114,13 @@ the data among the nav in containers properly [styled](#css-classes). ### Data file -The [data file](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/content/_data/global-nav.yaml) -is structured in three components: sections, categories, and docs. +The data file describes the structure of the navigation for the applicable project. All data files +are stored at <https://gitlab.com/gitlab-org/gitlab-docs/blob/master/content/_data/> and comprise +three components: + +- Sections +- Categories +- Docs #### Sections diff --git a/doc/development/documentation/site_architecture/index.md b/doc/development/documentation/site_architecture/index.md index 1aef0ed855c..bb598906967 100644 --- a/doc/development/documentation/site_architecture/index.md +++ b/doc/development/documentation/site_architecture/index.md @@ -66,10 +66,12 @@ the GitLab Documentation website. - [Google Analytics](https://marketingplatform.google.com/about/analytics/) - [Google Tag Manager](https://developers.google.com/tag-manager/) -## Global nav +## Global navigation -To understand how the global nav (left sidebar) is built, please -read through the [global navigation](global_nav.md) doc. +Read through the global navigation](global_nav.md) documentation to understand: + +- How the global navigation is built. +- How to add new navigation items. ## Deployment diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md index 941eea2609e..10e8008bad3 100644 --- a/doc/development/gotchas.md +++ b/doc/development/gotchas.md @@ -18,7 +18,7 @@ end Consider the following API spec: ```ruby -require 'rails_helper' +require 'spec_helper' describe API::Labels do it 'creates a first label' do @@ -69,7 +69,7 @@ is ok, but `create(:label, title: 'label1')` is not. Following is the fixed API spec: ```ruby -require 'rails_helper' +require 'spec_helper' describe API::Labels do it 'creates a first label' do diff --git a/doc/development/integrations/jira_connect.md b/doc/development/integrations/jira_connect.md index e1350b02262..9ac87a17232 100644 --- a/doc/development/integrations/jira_connect.md +++ b/doc/development/integrations/jira_connect.md @@ -10,8 +10,10 @@ The following are required to install and test the app: For the app to work, Jira Cloud should be able to connect to the GitLab instance through the internet. - To easily expose your local development environment, you can use tools like [serveo](https://serveo.net) or [ngrok](https://ngrok.com). - These also take care of SSL for you because Jira requires all connections to the app host to be over SSL. + To easily expose your local development environment, you can use tools like + [serveo](https://medium.com/@osanda.deshan/how-to-forward-my-local-port-to-public-using-serveo-4979f352a3bf) + or [ngrok](https://ngrok.com). These also take care of SSL for you because Jira + requires all connections to the app host to be over SSL. > This feature is currently behind the `:jira_connect_app` feature flag diff --git a/doc/development/prometheus_metrics.md b/doc/development/prometheus_metrics.md index 576601372a3..2a34851d21c 100644 --- a/doc/development/prometheus_metrics.md +++ b/doc/development/prometheus_metrics.md @@ -44,3 +44,26 @@ class ImportCommonMetrics < ActiveRecord::Migration[4.2] end end ``` + +## GitLab Prometheus metrics + +GitLab provides [Prometheus metrics](../administration/monitoring/prometheus/gitlab_metrics.md) +to monitor itself. + +### Adding a new metric + +This section describes how to add new metrics for self-monitoring +([example](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/15440)). + +1. Select the [type of metric](https://gitlab.com/gitlab-org/prometheus-client-mmap#metrics): + + - `Gitlab::Metrics.counter` + - `Gitlab::Metrics.gauge` + - `Gitlab::Metrics.histogram` + - `Gitlab::Metrics.summary` + +1. Select the appropriate name for your metric. Refer to the guidelines + for [Prometheus metric names](https://prometheus.io/docs/practices/naming/#metric-names). +1. Update the list of [GitLab Prometheus metrics](../administration/monitoring/prometheus/gitlab_metrics.md). +1. Trigger the relevant page/code that will record the new metric. +1. Check that the new metric appears at `/-/metrics`. diff --git a/doc/development/testing_guide/end_to_end/best_practices.md b/doc/development/testing_guide/end_to_end/best_practices.md index 89500ef9a90..2200069ecfd 100644 --- a/doc/development/testing_guide/end_to_end/best_practices.md +++ b/doc/development/testing_guide/end_to_end/best_practices.md @@ -1,5 +1,7 @@ # Best practices when writing end-to-end tests +## Avoid using a GUI when it's not required + The majority of the end-to-end tests require some state to be built in the application for the tests to happen. A good example is a user being logged in as a pre-condition for testing the feature. @@ -36,3 +38,18 @@ Finally, interacting with the application only by its GUI generates a higher rat - When depending only on the GUI to create the application's state and tests fail due to front-end issues, we can't rely on the test failures rate, and we generate a higher rate of test flakiness. Now that we are aware of all of it, [let's go create some tests](quick_start_guide.md). + +## Prefer to split tests across multiple files + +Our framework includes a couple of parallelization mechanisms that work by executing spec files in parallel. + +However, because tests are parallelized by spec *file* and not by test/example, we can't achieve greater parallelization if a new test is added to an existing file. + +Nonetheless, there could be other reasons to add a new test to an existing file. + +For example, if tests share state that is expensive to set up it might be more efficient to perform that setup once even if it means the tests that use the setup can't be parallelized. + +In summary: + +- **Do**: Split tests across separate files, unless the tests share expensive setup. +- **Don't**: Put new tests in an existing file without considering the impact on parallelization. diff --git a/doc/development/testing_guide/end_to_end/index.md b/doc/development/testing_guide/end_to_end/index.md index 3ae3ce183d9..f6a2f642274 100644 --- a/doc/development/testing_guide/end_to_end/index.md +++ b/doc/development/testing_guide/end_to_end/index.md @@ -118,7 +118,7 @@ Helm chart][helm-chart], itself deployed with custom See [Review Apps][review-apps] for more details about Review Apps. -[helm-chart]: https://gitlab.com/charts/gitlab/ +[helm-chart]: https://gitlab.com/gitlab-org/charts/gitlab/ [cng]: https://gitlab.com/gitlab-org/build/CNG ## How do I run the tests? diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md index 28a60660995..13772cbe015 100644 --- a/doc/development/testing_guide/review_apps.md +++ b/doc/development/testing_guide/review_apps.md @@ -267,7 +267,7 @@ find a way to limit it to only us.** - [Review Apps integration for CE/EE (presentation)](https://docs.google.com/presentation/d/1QPLr6FO4LduROU8pQIPkX1yfGvD13GEJIBOenqoKxR8/edit?usp=sharing) -[charts-1068]: https://gitlab.com/charts/gitlab/issues/1068 +[charts-1068]: https://gitlab.com/gitlab-org/charts/gitlab/issues/1068 [gitlab-pipeline]: https://gitlab.com/gitlab-org/gitlab-ce/pipelines/44362587 [gitlab:assets:compile]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/149511610 [review-build-cng]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/149511623 @@ -276,7 +276,7 @@ find a way to limit it to only us.** [cng]: https://gitlab.com/gitlab-org/build/CNG [cng-mirror-pipeline]: https://gitlab.com/gitlab-org/build/CNG-mirror/pipelines/44364657 [cng-mirror-registry]: https://gitlab.com/gitlab-org/build/CNG-mirror/container_registry -[helm-chart]: https://gitlab.com/charts/gitlab/ +[helm-chart]: https://gitlab.com/gitlab-org/charts/gitlab/ [review-apps-ce]: https://console.cloud.google.com/kubernetes/clusters/details/us-central1-a/review-apps-ce?project=gitlab-review-apps [review-apps-ee]: https://console.cloud.google.com/kubernetes/clusters/details/us-central1-b/review-apps-ee?project=gitlab-review-apps [review-apps.sh]: https://gitlab.com/gitlab-org/gitlab-ee/blob/master/scripts/review_apps/review-apps.sh diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 336000f6cb9..59b775d13bd 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -92,9 +92,9 @@ If you are running GitLab within a Docker container, you can run the backup from docker exec -t <container name> gitlab-backup create ``` -If you are using the [GitLab helm chart](https://gitlab.com/charts/gitlab) on a +If you are using the [GitLab helm chart](https://gitlab.com/gitlab-org/charts/gitlab) on a Kubernetes cluster, you can run the backup task using `backup-utility` script on -the GitLab task runner pod via `kubectl`. Refer to [backing up a GitLab installation](https://gitlab.com/charts/gitlab/blob/master/doc/backup-restore/backup.md#backing-up-a-gitlab-installation) for more details: +the GitLab task runner pod via `kubectl`. Refer to [backing up a GitLab installation](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/backup-restore/backup.md#backing-up-a-gitlab-installation) for more details: ```sh kubectl exec -it <gitlab task-runner pod> backup-utility @@ -764,7 +764,7 @@ docker exec -it <name of container> gitlab-backup restore ``` The GitLab helm chart uses a different process, documented in -[restoring a GitLab helm chart installation](https://gitlab.com/charts/gitlab/blob/master/doc/backup-restore/restore.md). +[restoring a GitLab helm chart installation](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/backup-restore/restore.md). ## Alternative backup strategies diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md index 1e9eb15533a..24a334e7067 100644 --- a/doc/system_hooks/system_hooks.md +++ b/doc/system_hooks/system_hooks.md @@ -13,6 +13,7 @@ Your GitLab instance can perform HTTP POST requests on the following events: - `project_update` - `user_add_to_team` - `user_remove_from_team` +- `user_update_for_team` - `user_create` - `user_destroy` - `user_failed_login` @@ -24,6 +25,7 @@ Your GitLab instance can perform HTTP POST requests on the following events: - `group_rename` - `user_add_to_group` - `user_remove_from_group` +- `user_update_for_group` The triggers for most of these are self-explanatory, but `project_update` and `project_rename` deserve some clarification: `project_update` is fired any time an attribute of a project is changed (name, description, tags, etc.) *unless* the `path` attribute is also changed. In that case, a `project_rename` is triggered instead (so that, for instance, if all you care about is the repo URL, you can just listen for `project_rename`). @@ -173,6 +175,26 @@ Please refer to `group_rename` and `user_rename` for that case. } ``` +**Team Member Updated:** + +```json +{ + "created_at": "2012-07-21T07:30:56Z", + "updated_at": "2012-07-21T07:38:22Z", + "event_name": "user_update_for_team", + "access_level": "Maintainer", + "project_id": 74, + "project_name": "StoreCloud", + "project_path": "storecloud", + "project_path_with_namespace": "jsmith/storecloud", + "user_email": "johnsmith@gmail.com", + "user_name": "John Smith", + "user_username": "johnsmith", + "user_id": 41, + "project_visibility": "visibilitylevel|private" +} +``` + **User created:** ```json @@ -349,6 +371,24 @@ If the user is blocked via LDAP, `state` will be `ldap_blocked`. } ``` +**Group Member Updated:** + +```json +{ + "created_at": "2012-07-21T07:30:56Z", + "updated_at": "2012-07-21T07:38:22Z", + "event_name": "user_update_for_group", + "group_access": "Maintainer", + "group_id": 78, + "group_name": "StoreCloud", + "group_path": "storecloud", + "user_email": "johnsmith@gmail.com", + "user_name": "John Smith", + "user_username": "johnsmith", + "user_id": 41 +} +``` + ## Push events Triggered when you push to the repository, except when pushing tags. diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md index a030f8d96ef..c3f80c6a0fd 100644 --- a/doc/user/application_security/container_scanning/index.md +++ b/doc/user/application_security/container_scanning/index.md @@ -127,7 +127,7 @@ build: ## Security Dashboard The Security Dashboard is a good place to get an overview of all the security -vulnerabilities in your groups and projects. Read more about the +vulnerabilities in your groups, projects and pipelines. Read more about the [Security Dashboard](../security_dashboard/index.md). ## Interacting with the vulnerabilities diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md index fa84f995e58..2d9f522c4f0 100644 --- a/doc/user/application_security/dast/index.md +++ b/doc/user/application_security/dast/index.md @@ -84,6 +84,8 @@ There are two ways to define the URL to be scanned by DAST: - Set the `DAST_WEBSITE` [variable](../../../ci/yaml/README.md#variables). - Add it in an `environment_url.txt` file at the root of your project. +If both values are set, the `DAST_WEBSITE` value will take precedence. + The included template will create a `dast` job in your CI/CD pipeline and scan your project's source code for possible vulnerabilities. @@ -134,7 +136,7 @@ variables: The DAST settings can be changed through environment variables by using the [`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`. -These variables are documented in the [DAST README](https://gitlab.com/gitlab-org/security-products/dast#settings). +These variables are documented in [available variables](#available-variables). For example: @@ -196,7 +198,7 @@ variable value. ## Security Dashboard The Security Dashboard is a good place to get an overview of all the security -vulnerabilities in your groups and projects. Read more about the +vulnerabilities in your groups, projects and pipelines. Read more about the [Security Dashboard](../security_dashboard/index.md). ## Interacting with the vulnerabilities diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md index b40392e12d5..fa2df667031 100644 --- a/doc/user/application_security/dependency_scanning/index.md +++ b/doc/user/application_security/dependency_scanning/index.md @@ -77,7 +77,7 @@ like Gemnasium require sending data to GitLab central servers to analyze them: The Gemnasium client does **NOT** send the exact package versions your project relies on. You can disable the remote checks by [using](#customizing-the-dependency-scanning-settings) -the `DS_DISABLE_REMOTE_CHECKS` environment variable and setting it to `true`. +the `DS_DISABLE_REMOTE_CHECKS` environment variable and setting it to `"true"`. ## Configuration @@ -115,7 +115,7 @@ include: template: Dependency-Scanning.gitlab-ci.yml variables: - DS_DISABLE_REMOTE_CHECKS: true + DS_DISABLE_REMOTE_CHECKS: "true" ``` Because template is [evaluated before](../../../ci/yaml/README.md#include) the pipeline @@ -314,7 +314,7 @@ the report JSON unless stated otherwise. Presence of optional fields depends on ## Security Dashboard The Security Dashboard is a good place to get an overview of all the security -vulnerabilities in your groups and projects. Read more about the +vulnerabilities in your groups, projects and pipelines. Read more about the [Security Dashboard](../security_dashboard/index.md). ## Interacting with the vulnerabilities diff --git a/doc/user/application_security/sast/img/security_report.png b/doc/user/application_security/sast/img/security_report.png Binary files differdeleted file mode 100644 index ba41b707238..00000000000 --- a/doc/user/application_security/sast/img/security_report.png +++ /dev/null diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md index 5e7bc4142fb..fbc130689e0 100644 --- a/doc/user/application_security/sast/index.md +++ b/doc/user/application_security/sast/index.md @@ -333,20 +333,10 @@ CI/CD configuration file to turn it on. Results are available in the SAST report GitLab currently includes [Gitleaks](https://github.com/zricethezav/gitleaks) and [TruffleHog](https://github.com/dxa4481/truffleHog) checks. -## Security report under pipelines - -> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/3776) -in [GitLab Ultimate](https://about.gitlab.com/pricing) 10.6. - -Visit any pipeline page which has a `sast` job and you will be able to see -the security report tab with the listed vulnerabilities (if any). - -![Security Report](img/security_report.png) - ## Security Dashboard The Security Dashboard is a good place to get an overview of all the security -vulnerabilities in your groups and projects. Read more about the +vulnerabilities in your groups, projects and pipelines. Read more about the [Security Dashboard](../security_dashboard/index.md). ## Interacting with the vulnerabilities diff --git a/doc/user/application_security/security_dashboard/img/group_security_dashboard_v12_3.png b/doc/user/application_security/security_dashboard/img/group_security_dashboard_v12_3.png Binary files differindex 61f683c1335..1fe76a9e08f 100644..100755 --- a/doc/user/application_security/security_dashboard/img/group_security_dashboard_v12_3.png +++ b/doc/user/application_security/security_dashboard/img/group_security_dashboard_v12_3.png diff --git a/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v12_3.png b/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v12_3.png Binary files differnew file mode 100755 index 00000000000..09979ba99b3 --- /dev/null +++ b/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v12_3.png diff --git a/doc/user/application_security/security_dashboard/img/project_security_dashboard.png b/doc/user/application_security/security_dashboard/img/project_security_dashboard.png Binary files differdeleted file mode 100644 index baa136fd885..00000000000 --- a/doc/user/application_security/security_dashboard/img/project_security_dashboard.png +++ /dev/null diff --git a/doc/user/application_security/security_dashboard/img/project_security_dashboard_v12_3.png b/doc/user/application_security/security_dashboard/img/project_security_dashboard_v12_3.png Binary files differnew file mode 100755 index 00000000000..51e80bdb50d --- /dev/null +++ b/doc/user/application_security/security_dashboard/img/project_security_dashboard_v12_3.png diff --git a/doc/user/application_security/security_dashboard/index.md b/doc/user/application_security/security_dashboard/index.md index e7cda35eb98..ac539509e22 100644 --- a/doc/user/application_security/security_dashboard/index.md +++ b/doc/user/application_security/security_dashboard/index.md @@ -5,7 +5,7 @@ type: reference, howto # GitLab Security Dashboard **(ULTIMATE)** The Security Dashboard is a good place to get an overview of all the security -vulnerabilities in your groups and projects. +vulnerabilities in your groups, projects and pipelines. You can also drill down into a vulnerability and get extra information, see which project it comes from, the file it's in, and various metadata to help you analyze @@ -26,7 +26,7 @@ The Security Dashboard supports the following reports: ## Requirements -To use the project or group security dashboard: +To use the group, project or pipeline security dashboard: 1. At least one project inside a group must be configured with at least one of the [supported reports](#supported-reports). @@ -34,6 +34,16 @@ To use the project or group security dashboard: 1. [GitLab Runner](https://docs.gitlab.com/runner/) 11.5 or newer must be used. If you're using the shared Runners on GitLab.com, this is already the case. +## Pipeline Security Dashboard + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/13496) in [GitLab Ultimate](https://about.gitlab.com/pricing) 12.3. + +At the pipeline level, the Security Dashboard displays the vulnerabilities present in the branch of the project the pipeline was run against. + +Visit the page for any pipeline which has run any of the [supported reports](#supported-reports). Click the **Security** tab to view the Security Dashboard. + +![Pipeline Security Dashboard](img/pipeline_security_dashboard_v12_3.png) + ## Project Security Dashboard > [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6165) in [GitLab Ultimate](https://about.gitlab.com/pricing) 11.1. @@ -42,12 +52,11 @@ At the project level, the Security Dashboard displays the latest security report for your project. Use it to find and fix vulnerabilities affecting the [default branch](../../project/repository/branches/index.md#default-branch). -![Project Security Dashboard](img/project_security_dashboard.png) +![Project Security Dashboard](img/project_security_dashboard_v12_3.png) ## Group Security Dashboard -> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6709) in -> [GitLab Ultimate](https://about.gitlab.com/pricing) 11.5. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6709) in [GitLab Ultimate](https://about.gitlab.com/pricing) 11.5. The group Security Dashboard gives an overview of the vulnerabilities of all the projects in a group and its subgroups. @@ -62,12 +71,15 @@ Once you're on the dashboard, at the top you should see a series of filters for: - Report type - Project +To the right of the filters, you should see a **Hide dismissed** toggle button. + NOTE: **Note:** The dashboard only shows projects with [security reports](#supported-reports) enabled in a group. ![dashboard with action buttons and metrics](img/group_security_dashboard_v12_3.png) -Selecting one or more filters will filter the results in this page. +Selecting one or more filters will filter the results in this page. Disabling the **Hide dismissed** +toggle button will let you also see vulnerabilities that have been dismissed. The main section is a list of all the vulnerabilities in the group, sorted by severity. In that list, you can see the severity of the vulnerability, its name, its diff --git a/doc/user/group/index.md b/doc/user/group/index.md index 86fb7533e70..c09acd36e31 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -443,6 +443,12 @@ A group owner can check the aggregated storage usage for all the project in a gr ![Group storage usage quota](img/group_storage_usage_quota.png) +The total usage of the storage is updated if any relevant event that +will affect its value is triggered (e.g., a commit push). +For performance reasons, we may delay the update up to 1 hour and 30 minutes. + +If your namespace shows `N/A` as the total storage usage, you can trigger a recalculation by pushing a commit to any project in that namespace. + ## User contribution analysis **(STARTER)** With [GitLab Contribution Analytics](contribution_analytics/index.md), diff --git a/doc/user/instance/clusters/index.md b/doc/user/instance/clusters/index.md index f557dcf4b3c..cb1bfc69826 100644 --- a/doc/user/instance/clusters/index.md +++ b/doc/user/instance/clusters/index.md @@ -19,4 +19,4 @@ GitLab will try match to clusters in the following order: - Instance level To be selected, the cluster must be enabled and -match the [environment selector](../../../ci/environments.md#scoping-environments-with-specs-premium). +match the [environment selector](../../../ci/environments.md#scoping-environments-with-specs). diff --git a/doc/user/permissions.md b/doc/user/permissions.md index c28a5e49ec4..02b5f7437ee 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -37,6 +37,8 @@ usernames. A GitLab administrator can configure the GitLab instance to NOTE: **Note:** In GitLab 11.0, the Master role was renamed to Maintainer. +While Maintainer is the highest project-level role, some actions can only be performed by a personal namespace or group owner. + The following table depicts the various user permission levels in a project. | Action | Guest | Reporter | Developer |Maintainer| Owner | @@ -74,7 +76,7 @@ The following table depicts the various user permission levels in a project. | View project statistics | | ✓ | ✓ | ✓ | ✓ | | View Error Tracking list | | ✓ | ✓ | ✓ | ✓ | | Pull from [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **(PREMIUM)** | | ✓ | ✓ | ✓ | ✓ | -| Publish to [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **(PREMIUM)** | | | ✓ | ✓ | ✓ || +| Publish to [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **(PREMIUM)** | | | ✓ | ✓ | ✓ | | Upload [Design Management](project/issues/design_management.md) files **(PREMIUM)** | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ | | Push to non-protected branches | | | ✓ | ✓ | ✓ | diff --git a/doc/user/project/import/gemnasium.md b/doc/user/project/import/gemnasium.md index cf48189fa6e..3217bbc4772 100644 --- a/doc/user/project/import/gemnasium.md +++ b/doc/user/project/import/gemnasium.md @@ -98,7 +98,7 @@ back to both GitLab and GitHub when completed. 1. The result of the job will be visible directly from the pipeline view: - ![security report](img/gemnasium/report.png) + ![Security Dashboard](../../application_security/security_dashboard/img/pipeline_security_dashboard_v12_3.png) NOTE: **Note:** If you don't commit very often to your project, you may want to use diff --git a/doc/user/project/import/img/gemnasium/report.png b/doc/user/project/import/img/gemnasium/report.png Binary files differdeleted file mode 100644 index 5c4d58662c0..00000000000 --- a/doc/user/project/import/img/gemnasium/report.png +++ /dev/null diff --git a/doc/user/project/operations/feature_flags.md b/doc/user/project/operations/feature_flags.md index 6536a1a0a4b..39ca1bd0c77 100644 --- a/doc/user/project/operations/feature_flags.md +++ b/doc/user/project/operations/feature_flags.md @@ -2,10 +2,6 @@ > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845) in GitLab 11.4. -CAUTION: **Warning:** -This an _alpha_ feature and is subject to change at any time without -prior notice. - Feature flags allow you to ship a project in different flavors by dynamically toggling certain functionality. @@ -67,15 +63,15 @@ For example, you may not want to enable a feature flag on production until your first confirmed that the feature is working correctly on testing environments. To handle these situations, you can enable a feature flag on a particular environment -with [Environment specs](../../../ci/environments.md#scoping-environments-with-specs-premium). +with [Environment specs](../../../ci/environments.md#scoping-environments-with-specs). You can define multiple specs per flag so that you can control your feature flag more granularly. To define specs for each environment: 1. Navigate to your project's **Operations > Feature Flags**. 1. Click on the **New Feature Flag** button or edit an existing flag. -1. Set the status of the default [spec](../../../ci/environments.md#scoping-environments-with-specs-premium) (`*`). Choose a rollout strategy. This status and rollout strategy combination will be used for _all_ environments. -1. If you want to enable/disable the feature on a specific environment, create a new [spec](../../../ci/environments.md#scoping-environments-with-specs-premium) and type the environment name. +1. Set the status of the default [spec](../../../ci/environments.md#scoping-environments-with-specs) (`*`). Choose a rollout strategy. This status and rollout strategy combination will be used for _all_ environments. +1. If you want to enable/disable the feature on a specific environment, create a new [spec](../../../ci/environments.md#scoping-environments-with-specs) and type the environment name. 1. Set the status and rollout strategy of the additional spec. This status and rollout strategy combination takes precedence over the default spec since we always use the most specific match available. 1. Click **Create feature flag** or **Update feature flag**. diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index e373d605098..1f7f85e9750 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -14,8 +14,11 @@ on a separate line in order to be properly detected and executed. Once executed, ## Quick Actions for issues, merge requests and epics -The following quick actions are applicable to descriptions, discussions and threads -in issues and merge requests, as well as epics.**(ULTIMATE)** +The following quick actions are applicable to descriptions, discussions and threads in: + +- Issues +- Merge requests +- Epics **(ULTIMATE)** | Command | Issue | Merge request | Epic | Action | |:--------------------------------------|:------|:--------------|:-----|:------ | diff --git a/lib/api/api.rb b/lib/api/api.rb index 219ed45eff6..aa6a67d817a 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -118,7 +118,7 @@ module API mount ::API::GroupContainerRepositories mount ::API::GroupVariables mount ::API::ImportGithub - mount ::API::Internal + mount ::API::Internal::Base mount ::API::Issues mount ::API::JobArtifacts mount ::API::Jobs diff --git a/lib/api/entities.rb b/lib/api/entities.rb index cfcf6228225..f7cd6d35854 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -978,7 +978,9 @@ module API expose :created_at def todo_target_class(target_type) - ::API::Entities.const_get(target_type) + # false as second argument prevents looking up in module hierarchy + # see also https://gitlab.com/gitlab-org/gitlab-ce/issues/59719 + ::API::Entities.const_get(target_type, false) end end @@ -1229,6 +1231,7 @@ module API expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? } expose :commit, using: Entities::Commit, if: lambda { |_, _| can_download_code? } expose :upcoming_release?, as: :upcoming_release + expose :milestone, using: Entities::Milestone, if: -> (release, _) { release.milestone.present? } expose :assets do expose :assets_count, as: :count do |release, _| @@ -1730,6 +1733,7 @@ API::Entities.prepend_if_ee('EE::API::Entities::Entities') ::API::Entities::Group.prepend_if_ee('EE::API::Entities::Group', with_descendants: true) ::API::Entities::GroupDetail.prepend_if_ee('EE::API::Entities::GroupDetail') ::API::Entities::IssueBasic.prepend_if_ee('EE::API::Entities::IssueBasic', with_descendants: true) +::API::Entities::Issue.prepend_if_ee('EE::API::Entities::Issue') ::API::Entities::List.prepend_if_ee('EE::API::Entities::List') ::API::Entities::MergeRequestBasic.prepend_if_ee('EE::API::Entities::MergeRequestBasic', with_descendants: true) ::API::Entities::Namespace.prepend_if_ee('EE::API::Entities::Namespace') diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 0bcd09d3977..0b086f2e36d 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -75,6 +75,7 @@ module API ).execute projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled] projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled] + projects = projects.visible_to_user_and_access_level(current_user, params[:min_access_level]) if params[:min_access_level] projects = reorder_projects(projects) paginate(projects) end @@ -213,6 +214,7 @@ module API optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature' optional :with_shared, type: Boolean, default: true, desc: 'Include projects shared to this group' optional :include_subgroups, type: Boolean, default: false, desc: 'Includes projects in subgroups of this group' + optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user on projects' use :pagination use :with_custom_attributes diff --git a/lib/api/internal.rb b/lib/api/internal.rb deleted file mode 100644 index 088ea5bd79a..00000000000 --- a/lib/api/internal.rb +++ /dev/null @@ -1,294 +0,0 @@ -# frozen_string_literal: true - -module API - # Internal access API - class Internal < Grape::API - before { authenticate_by_gitlab_shell_token! } - - helpers ::API::Helpers::InternalHelpers - helpers ::Gitlab::Identifier - - UNKNOWN_CHECK_RESULT_ERROR = 'Unknown check result'.freeze - - helpers do - def response_with_status(code: 200, success: true, message: nil, **extra_options) - status code - { status: success, message: message }.merge(extra_options).compact - end - - def lfs_authentication_url(project) - # This is a separate method so that EE can alter its behaviour more - # easily. - project.http_url_to_repo - end - end - - namespace 'internal' do - # Check if git command is allowed for project - # - # Params: - # key_id - ssh key id for Git over SSH - # user_id - user id for Git over HTTP or over SSH in keyless SSH CERT mode - # username - user name for Git over SSH in keyless SSH cert mode - # protocol - Git access protocol being used, e.g. HTTP or SSH - # project - project full_path (not path on disk) - # action - git action (git-upload-pack or git-receive-pack) - # changes - changes as "oldrev newrev ref", see Gitlab::ChangesList - # rubocop: disable CodeReuse/ActiveRecord - post "/allowed" do - # Stores some Git-specific env thread-safely - env = parse_env - Gitlab::Git::HookEnv.set(gl_repository, env) if project - - actor = - if params[:key_id] - Key.find_by(id: params[:key_id]) - elsif params[:user_id] - User.find_by(id: params[:user_id]) - elsif params[:username] - UserFinder.new(params[:username]).find_by_username - end - - protocol = params[:protocol] - - actor.update_last_used_at if actor.is_a?(Key) - user = - if actor.is_a?(Key) - actor.user - else - actor - end - - access_checker_klass = repo_type.access_checker_class - access_checker = access_checker_klass.new(actor, project, - protocol, authentication_abilities: ssh_authentication_abilities, - namespace_path: namespace_path, project_path: project_path, - redirected_path: redirected_path) - - check_result = begin - result = access_checker.check(params[:action], params[:changes]) - @project ||= access_checker.project - result - rescue Gitlab::GitAccess::UnauthorizedError => e - break response_with_status(code: 401, success: false, message: e.message) - rescue Gitlab::GitAccess::TimeoutError => e - break response_with_status(code: 503, success: false, message: e.message) - rescue Gitlab::GitAccess::NotFoundError => e - break response_with_status(code: 404, success: false, message: e.message) - end - - log_user_activity(actor) - - case check_result - when ::Gitlab::GitAccessResult::Success - payload = { - gl_repository: gl_repository, - gl_project_path: gl_project_path, - gl_id: Gitlab::GlId.gl_id(user), - gl_username: user&.username, - git_config_options: [], - gitaly: gitaly_payload(params[:action]), - gl_console_messages: check_result.console_messages - } - - # Custom option for git-receive-pack command - receive_max_input_size = Gitlab::CurrentSettings.receive_max_input_size.to_i - if receive_max_input_size > 0 - payload[:git_config_options] << "receive.maxInputSize=#{receive_max_input_size.megabytes}" - end - - response_with_status(**payload) - when ::Gitlab::GitAccessResult::CustomAction - response_with_status(code: 300, message: check_result.message, payload: check_result.payload) - else - response_with_status(code: 500, success: false, message: UNKNOWN_CHECK_RESULT_ERROR) - end - end - # rubocop: enable CodeReuse/ActiveRecord - - # rubocop: disable CodeReuse/ActiveRecord - post "/lfs_authenticate" do - status 200 - - if params[:key_id] - actor = Key.find(params[:key_id]) - actor.update_last_used_at - elsif params[:user_id] - actor = User.find_by(id: params[:user_id]) - raise ActiveRecord::RecordNotFound.new("No such user id!") unless actor - else - raise ActiveRecord::RecordNotFound.new("No key_id or user_id passed!") - end - - Gitlab::LfsToken - .new(actor) - .authentication_payload(lfs_authentication_url(project)) - end - # rubocop: enable CodeReuse/ActiveRecord - - get "/merge_request_urls" do - merge_request_urls - end - - # - # Get a ssh key using the fingerprint - # - # rubocop: disable CodeReuse/ActiveRecord - get "/authorized_keys" do - fingerprint = params.fetch(:fingerprint) do - Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint - end - key = Key.find_by(fingerprint: fingerprint) - not_found!("Key") if key.nil? - present key, with: Entities::SSHKey - end - # rubocop: enable CodeReuse/ActiveRecord - - # - # Discover user by ssh key, user id or username - # - # rubocop: disable CodeReuse/ActiveRecord - get "/discover" do - if params[:key_id] - key = Key.find(params[:key_id]) - user = key.user - elsif params[:user_id] - user = User.find_by(id: params[:user_id]) - elsif params[:username] - user = UserFinder.new(params[:username]).find_by_username - end - - present user, with: Entities::UserSafe - end - # rubocop: enable CodeReuse/ActiveRecord - - get "/check" do - { - api_version: API.version, - gitlab_version: Gitlab::VERSION, - gitlab_rev: Gitlab.revision, - redis: redis_ping - } - end - - get "/broadcast_messages" do - if messages = BroadcastMessage.current - present messages, with: Entities::BroadcastMessage - else - [] - end - end - - get "/broadcast_message" do - if message = BroadcastMessage.current&.last - present message, with: Entities::BroadcastMessage - else - {} - end - end - - # rubocop: disable CodeReuse/ActiveRecord - post '/two_factor_recovery_codes' do - status 200 - - if params[:key_id] - key = Key.find_by(id: params[:key_id]) - - if key - key.update_last_used_at - else - break { 'success' => false, 'message' => 'Could not find the given key' } - end - - if key.is_a?(DeployKey) - break { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' } - end - - user = key.user - - unless user - break { success: false, message: 'Could not find a user for the given key' } - end - elsif params[:user_id] - user = User.find_by(id: params[:user_id]) - - unless user - break { success: false, message: 'Could not find the given user' } - end - end - - unless user.two_factor_enabled? - break { success: false, message: 'Two-factor authentication is not enabled for this user' } - end - - codes = nil - - ::Users::UpdateService.new(current_user, user: user).execute! do |user| - codes = user.generate_otp_backup_codes! - end - - { success: true, recovery_codes: codes } - end - # rubocop: enable CodeReuse/ActiveRecord - - post '/pre_receive' do - status 200 - - reference_counter_increased = Gitlab::ReferenceCounter.new(params[:gl_repository]).increase - - { reference_counter_increased: reference_counter_increased } - end - - post "/notify_post_receive" do - status 200 - - # TODO: Re-enable when Gitaly is processing the post-receive notification - # return unless Gitlab::GitalyClient.enabled? - # - # begin - # repository = wiki? ? project.wiki.repository : project.repository - # Gitlab::GitalyClient::NotificationService.new(repository.raw_repository).post_receive - # rescue GRPC::Unavailable => e - # render_api_error!(e, 500) - # end - end - - post '/post_receive' do - status 200 - - response = Gitlab::InternalPostReceive::Response.new - user = identify(params[:identifier]) - project = Gitlab::GlRepository.parse(params[:gl_repository]).first - push_options = Gitlab::PushOptions.new(params[:push_options]) - - response.reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease - - PostReceive.perform_async(params[:gl_repository], params[:identifier], - params[:changes], push_options.as_json) - - mr_options = push_options.get(:merge_request) - if mr_options.present? - message = process_mr_push_options(mr_options, project, user, params[:changes]) - response.add_alert_message(message) - end - - broadcast_message = BroadcastMessage.current&.last&.message - response.add_alert_message(broadcast_message) - - response.add_merge_request_urls(merge_request_urls) - - # A user is not guaranteed to be returned; an orphaned write deploy - # key could be used - if user - redirect_message = Gitlab::Checks::ProjectMoved.fetch_message(user.id, project.id) - project_created_message = Gitlab::Checks::ProjectCreated.fetch_message(user.id, project.id) - - response.add_basic_message(redirect_message) - response.add_basic_message(project_created_message) - end - - present response, with: Entities::InternalPostReceive::Response - end - end - end -end diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb new file mode 100644 index 00000000000..622032b8355 --- /dev/null +++ b/lib/api/internal/base.rb @@ -0,0 +1,296 @@ +# frozen_string_literal: true + +module API + # Internal access API + module Internal + class Base < Grape::API + before { authenticate_by_gitlab_shell_token! } + + helpers ::API::Helpers::InternalHelpers + helpers ::Gitlab::Identifier + + UNKNOWN_CHECK_RESULT_ERROR = 'Unknown check result'.freeze + + helpers do + def response_with_status(code: 200, success: true, message: nil, **extra_options) + status code + { status: success, message: message }.merge(extra_options).compact + end + + def lfs_authentication_url(project) + # This is a separate method so that EE can alter its behaviour more + # easily. + project.http_url_to_repo + end + end + + namespace 'internal' do + # Check if git command is allowed for project + # + # Params: + # key_id - ssh key id for Git over SSH + # user_id - user id for Git over HTTP or over SSH in keyless SSH CERT mode + # username - user name for Git over SSH in keyless SSH cert mode + # protocol - Git access protocol being used, e.g. HTTP or SSH + # project - project full_path (not path on disk) + # action - git action (git-upload-pack or git-receive-pack) + # changes - changes as "oldrev newrev ref", see Gitlab::ChangesList + # rubocop: disable CodeReuse/ActiveRecord + post "/allowed" do + # Stores some Git-specific env thread-safely + env = parse_env + Gitlab::Git::HookEnv.set(gl_repository, env) if project + + actor = + if params[:key_id] + Key.find_by(id: params[:key_id]) + elsif params[:user_id] + User.find_by(id: params[:user_id]) + elsif params[:username] + UserFinder.new(params[:username]).find_by_username + end + + protocol = params[:protocol] + + actor.update_last_used_at if actor.is_a?(Key) + user = + if actor.is_a?(Key) + actor.user + else + actor + end + + access_checker_klass = repo_type.access_checker_class + access_checker = access_checker_klass.new(actor, project, + protocol, authentication_abilities: ssh_authentication_abilities, + namespace_path: namespace_path, project_path: project_path, + redirected_path: redirected_path) + + check_result = begin + result = access_checker.check(params[:action], params[:changes]) + @project ||= access_checker.project + result + rescue Gitlab::GitAccess::UnauthorizedError => e + break response_with_status(code: 401, success: false, message: e.message) + rescue Gitlab::GitAccess::TimeoutError => e + break response_with_status(code: 503, success: false, message: e.message) + rescue Gitlab::GitAccess::NotFoundError => e + break response_with_status(code: 404, success: false, message: e.message) + end + + log_user_activity(actor) + + case check_result + when ::Gitlab::GitAccessResult::Success + payload = { + gl_repository: gl_repository, + gl_project_path: gl_project_path, + gl_id: Gitlab::GlId.gl_id(user), + gl_username: user&.username, + git_config_options: [], + gitaly: gitaly_payload(params[:action]), + gl_console_messages: check_result.console_messages + } + + # Custom option for git-receive-pack command + receive_max_input_size = Gitlab::CurrentSettings.receive_max_input_size.to_i + if receive_max_input_size > 0 + payload[:git_config_options] << "receive.maxInputSize=#{receive_max_input_size.megabytes}" + end + + response_with_status(**payload) + when ::Gitlab::GitAccessResult::CustomAction + response_with_status(code: 300, message: check_result.message, payload: check_result.payload) + else + response_with_status(code: 500, success: false, message: UNKNOWN_CHECK_RESULT_ERROR) + end + end + # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable CodeReuse/ActiveRecord + post "/lfs_authenticate" do + status 200 + + if params[:key_id] + actor = Key.find(params[:key_id]) + actor.update_last_used_at + elsif params[:user_id] + actor = User.find_by(id: params[:user_id]) + raise ActiveRecord::RecordNotFound.new("No such user id!") unless actor + else + raise ActiveRecord::RecordNotFound.new("No key_id or user_id passed!") + end + + Gitlab::LfsToken + .new(actor) + .authentication_payload(lfs_authentication_url(project)) + end + # rubocop: enable CodeReuse/ActiveRecord + + get "/merge_request_urls" do + merge_request_urls + end + + # + # Get a ssh key using the fingerprint + # + # rubocop: disable CodeReuse/ActiveRecord + get "/authorized_keys" do + fingerprint = params.fetch(:fingerprint) do + Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint + end + key = Key.find_by(fingerprint: fingerprint) + not_found!("Key") if key.nil? + present key, with: Entities::SSHKey + end + # rubocop: enable CodeReuse/ActiveRecord + + # + # Discover user by ssh key, user id or username + # + # rubocop: disable CodeReuse/ActiveRecord + get "/discover" do + if params[:key_id] + key = Key.find(params[:key_id]) + user = key.user + elsif params[:user_id] + user = User.find_by(id: params[:user_id]) + elsif params[:username] + user = UserFinder.new(params[:username]).find_by_username + end + + present user, with: Entities::UserSafe + end + # rubocop: enable CodeReuse/ActiveRecord + + get "/check" do + { + api_version: API.version, + gitlab_version: Gitlab::VERSION, + gitlab_rev: Gitlab.revision, + redis: redis_ping + } + end + + get "/broadcast_messages" do + if messages = BroadcastMessage.current + present messages, with: Entities::BroadcastMessage + else + [] + end + end + + get "/broadcast_message" do + if message = BroadcastMessage.current&.last + present message, with: Entities::BroadcastMessage + else + {} + end + end + + # rubocop: disable CodeReuse/ActiveRecord + post '/two_factor_recovery_codes' do + status 200 + + if params[:key_id] + key = Key.find_by(id: params[:key_id]) + + if key + key.update_last_used_at + else + break { 'success' => false, 'message' => 'Could not find the given key' } + end + + if key.is_a?(DeployKey) + break { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' } + end + + user = key.user + + unless user + break { success: false, message: 'Could not find a user for the given key' } + end + elsif params[:user_id] + user = User.find_by(id: params[:user_id]) + + unless user + break { success: false, message: 'Could not find the given user' } + end + end + + unless user.two_factor_enabled? + break { success: false, message: 'Two-factor authentication is not enabled for this user' } + end + + codes = nil + + ::Users::UpdateService.new(current_user, user: user).execute! do |user| + codes = user.generate_otp_backup_codes! + end + + { success: true, recovery_codes: codes } + end + # rubocop: enable CodeReuse/ActiveRecord + + post '/pre_receive' do + status 200 + + reference_counter_increased = Gitlab::ReferenceCounter.new(params[:gl_repository]).increase + + { reference_counter_increased: reference_counter_increased } + end + + post "/notify_post_receive" do + status 200 + + # TODO: Re-enable when Gitaly is processing the post-receive notification + # return unless Gitlab::GitalyClient.enabled? + # + # begin + # repository = wiki? ? project.wiki.repository : project.repository + # Gitlab::GitalyClient::NotificationService.new(repository.raw_repository).post_receive + # rescue GRPC::Unavailable => e + # render_api_error!(e, 500) + # end + end + + post '/post_receive' do + status 200 + + response = Gitlab::InternalPostReceive::Response.new + user = identify(params[:identifier]) + project = Gitlab::GlRepository.parse(params[:gl_repository]).first + push_options = Gitlab::PushOptions.new(params[:push_options]) + + response.reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease + + PostReceive.perform_async(params[:gl_repository], params[:identifier], + params[:changes], push_options.as_json) + + mr_options = push_options.get(:merge_request) + if mr_options.present? + message = process_mr_push_options(mr_options, project, user, params[:changes]) + response.add_alert_message(message) + end + + broadcast_message = BroadcastMessage.current&.last&.message + response.add_alert_message(broadcast_message) + + response.add_merge_request_urls(merge_request_urls) + + # A user is not guaranteed to be returned; an orphaned write deploy + # key could be used + if user + redirect_message = Gitlab::Checks::ProjectMoved.fetch_message(user.id, project.id) + project_created_message = Gitlab::Checks::ProjectCreated.fetch_message(user.id, project.id) + + response.add_basic_message(redirect_message) + response.add_basic_message(project_created_message) + end + + present response, with: Entities::InternalPostReceive::Response + end + end + end + end +end diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb index 5d1b40e3bff..def36dc8529 100644 --- a/lib/api/release/links.rb +++ b/lib/api/release/links.rb @@ -5,7 +5,7 @@ module API class Links < Grape::API include PaginationParams - RELEASE_ENDPOINT_REQUIREMETS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS + RELEASE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS .merge(tag_name: API::NO_SLASH_URL_PART_REGEX) before { authorize! :read_release, user_project } @@ -17,7 +17,7 @@ module API params do requires :tag_name, type: String, desc: 'The name of the tag', as: :tag end - resource 'releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do + resource 'releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do resource :assets do desc 'Get a list of links of a release' do detail 'This feature was introduced in GitLab 11.7.' diff --git a/lib/api/releases.rb b/lib/api/releases.rb index 7a3d804c30c..5a31581c4da 100644 --- a/lib/api/releases.rb +++ b/lib/api/releases.rb @@ -4,7 +4,7 @@ module API class Releases < Grape::API include PaginationParams - RELEASE_ENDPOINT_REQUIREMETS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS + RELEASE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS .merge(tag_name: API::NO_SLASH_URL_PART_REGEX) before { authorize_read_releases! } @@ -33,7 +33,7 @@ module API params do requires :tag_name, type: String, desc: 'The name of the tag', as: :tag end - get ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do + get ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do authorize_download_code! present release, with: Entities::Release, current_user: current_user @@ -54,6 +54,7 @@ module API requires :url, type: String end end + optional :milestone, type: String, desc: 'The title of the related milestone' optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready. Defaults to the current time.' end post ':id/releases' do @@ -79,8 +80,9 @@ module API 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.' + optional :milestone, type: String, desc: 'The title of the related milestone' end - put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do + put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do authorize_update_release! result = ::Releases::UpdateService @@ -101,7 +103,7 @@ module API params do requires :tag_name, type: String, desc: 'The name of the tag', as: :tag end - delete ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do + delete ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do authorize_destroy_release! result = ::Releases::DestroyService diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 6769bd95c2b..bdc46abeb9f 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -265,7 +265,8 @@ module Gitlab :read_project, :build_download_code, :build_read_container_image, - :build_create_container_image + :build_create_container_image, + :build_destroy_container_image ] end diff --git a/lib/gitlab/auth/unique_ips_limiter.rb b/lib/gitlab/auth/unique_ips_limiter.rb index 31dd61ae6cf..97e78ecf094 100644 --- a/lib/gitlab/auth/unique_ips_limiter.rb +++ b/lib/gitlab/auth/unique_ips_limiter.rb @@ -3,7 +3,7 @@ module Gitlab module Auth class UniqueIpsLimiter - USER_UNIQUE_IPS_PREFIX = 'user_unique_ips'.freeze + USER_UNIQUE_IPS_PREFIX = 'user_unique_ips' class << self def limit_user_id!(user_id) diff --git a/lib/gitlab/auth/user_auth_finders.rb b/lib/gitlab/auth/user_auth_finders.rb index bba7e2cbb3c..97755117edc 100644 --- a/lib/gitlab/auth/user_auth_finders.rb +++ b/lib/gitlab/auth/user_auth_finders.rb @@ -20,7 +20,7 @@ module Gitlab module UserAuthFinders include Gitlab::Utils::StrongMemoize - PRIVATE_TOKEN_HEADER = 'HTTP_PRIVATE_TOKEN'.freeze + PRIVATE_TOKEN_HEADER = 'HTTP_PRIVATE_TOKEN' PRIVATE_TOKEN_PARAM = :private_token # Check the Rails session for valid authentication details diff --git a/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check.rb b/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check.rb index e948cedaad5..de0c357ab1c 100644 --- a/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check.rb +++ b/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check.rb @@ -7,7 +7,7 @@ module Gitlab include Gitlab::Utils::StrongMemoize RESCHEDULE_DELAY = 3.hours - WORKER = 'PopulateMergeRequestAssigneesTable'.freeze + WORKER = 'PopulateMergeRequestAssigneesTable' DeadJobsError = Class.new(StandardError) def perform diff --git a/lib/gitlab/background_migration/prepare_untracked_uploads.rb b/lib/gitlab/background_migration/prepare_untracked_uploads.rb index 2ac51dd7b55..3d943205783 100644 --- a/lib/gitlab/background_migration/prepare_untracked_uploads.rb +++ b/lib/gitlab/background_migration/prepare_untracked_uploads.rb @@ -10,15 +10,15 @@ module Gitlab include ::Gitlab::Utils::StrongMemoize FIND_BATCH_SIZE = 500 - RELATIVE_UPLOAD_DIR = "uploads".freeze + RELATIVE_UPLOAD_DIR = "uploads" ABSOLUTE_UPLOAD_DIR = File.join( Gitlab.config.uploads.storage_path, RELATIVE_UPLOAD_DIR ) - FOLLOW_UP_MIGRATION = 'PopulateUntrackedUploads'.freeze + FOLLOW_UP_MIGRATION = 'PopulateUntrackedUploads' START_WITH_ROOT_REGEX = %r{\A#{Gitlab.config.uploads.storage_path}/}.freeze - EXCLUDED_HASHED_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/@hashed/*".freeze - EXCLUDED_TMP_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/tmp/*".freeze + EXCLUDED_HASHED_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/@hashed/*" + EXCLUDED_TMP_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/tmp/*" # This class is used to iterate over batches of # `untracked_files_for_uploads` rows. diff --git a/lib/gitlab/bitbucket_server_import/importer.rb b/lib/gitlab/bitbucket_server_import/importer.rb index ff2694abd5e..93c6fdcf69c 100644 --- a/lib/gitlab/bitbucket_server_import/importer.rb +++ b/lib/gitlab/bitbucket_server_import/importer.rb @@ -7,7 +7,7 @@ module Gitlab attr_reader :project, :project_key, :repository_slug, :client, :errors, :users attr_accessor :logger - REMOTE_NAME = 'bitbucket_server'.freeze + REMOTE_NAME = 'bitbucket_server' BATCH_SIZE = 100 TempBranch = Struct.new(:name, :sha) diff --git a/lib/gitlab/checks/lfs_check.rb b/lib/gitlab/checks/lfs_check.rb index 67a65d61441..7b013567a03 100644 --- a/lib/gitlab/checks/lfs_check.rb +++ b/lib/gitlab/checks/lfs_check.rb @@ -3,8 +3,8 @@ module Gitlab module Checks class LfsCheck < BaseChecker - LOG_MESSAGE = "Scanning repository for blobs stored in LFS and verifying their files have been uploaded to GitLab...".freeze - ERROR_MESSAGE = 'LFS objects are missing. Ensure LFS is properly set up or try a manual "git lfs push --all".'.freeze + LOG_MESSAGE = 'Scanning repository for blobs stored in LFS and verifying their files have been uploaded to GitLab...' + ERROR_MESSAGE = 'LFS objects are missing. Ensure LFS is properly set up or try a manual "git lfs push --all".' def validate! return unless Feature.enabled?(:lfs_check, default_enabled: true) diff --git a/lib/gitlab/checks/project_created.rb b/lib/gitlab/checks/project_created.rb index 0058a402a62..362562068e9 100644 --- a/lib/gitlab/checks/project_created.rb +++ b/lib/gitlab/checks/project_created.rb @@ -3,7 +3,7 @@ module Gitlab module Checks class ProjectCreated < PostPushMessage - PROJECT_CREATED = "project_created".freeze + PROJECT_CREATED = "project_created" def message <<~MESSAGE diff --git a/lib/gitlab/checks/project_moved.rb b/lib/gitlab/checks/project_moved.rb index cb3b7acaaad..6f04fddc6c4 100644 --- a/lib/gitlab/checks/project_moved.rb +++ b/lib/gitlab/checks/project_moved.rb @@ -3,7 +3,7 @@ module Gitlab module Checks class ProjectMoved < PostPushMessage - REDIRECT_NAMESPACE = "redirect_namespace".freeze + REDIRECT_NAMESPACE = "redirect_namespace" def initialize(project, user, protocol, redirected_path) @redirected_path = redirected_path diff --git a/lib/gitlab/ci/build/port.rb b/lib/gitlab/ci/build/port.rb index 6c4656ffea2..017b31b4f77 100644 --- a/lib/gitlab/ci/build/port.rb +++ b/lib/gitlab/ci/build/port.rb @@ -4,8 +4,8 @@ module Gitlab module Ci module Build class Port - DEFAULT_PORT_NAME = 'default_port'.freeze - DEFAULT_PORT_PROTOCOL = 'http'.freeze + DEFAULT_PORT_NAME = 'default_port' + DEFAULT_PORT_PROTOCOL = 'http' attr_reader :number, :protocol, :name diff --git a/lib/gitlab/ci/build/step.rb b/lib/gitlab/ci/build/step.rb index 7fcabc035ac..48111ae5717 100644 --- a/lib/gitlab/ci/build/step.rb +++ b/lib/gitlab/ci/build/step.rb @@ -4,9 +4,9 @@ module Gitlab module Ci module Build class Step - WHEN_ON_FAILURE = 'on_failure'.freeze - WHEN_ON_SUCCESS = 'on_success'.freeze - WHEN_ALWAYS = 'always'.freeze + WHEN_ON_FAILURE = 'on_failure' + WHEN_ON_SUCCESS = 'on_success' + WHEN_ALWAYS = 'always' attr_reader :name attr_accessor :script, :timeout, :when, :allow_failure diff --git a/lib/gitlab/ci/config/entry/cache.rb b/lib/gitlab/ci/config/entry/cache.rb index 7b94af24c09..ef07c319ce4 100644 --- a/lib/gitlab/ci/config/entry/cache.rb +++ b/lib/gitlab/ci/config/entry/cache.rb @@ -12,7 +12,7 @@ module Gitlab include ::Gitlab::Config::Entry::Attributable ALLOWED_KEYS = %i[key untracked paths policy].freeze - DEFAULT_POLICY = 'pull-push'.freeze + DEFAULT_POLICY = 'pull-push' validations do validates :config, allowed_keys: ALLOWED_KEYS diff --git a/lib/gitlab/ci/config/external/file/template.rb b/lib/gitlab/ci/config/external/file/template.rb index 54f4cf74c4d..db56f6a9b00 100644 --- a/lib/gitlab/ci/config/external/file/template.rb +++ b/lib/gitlab/ci/config/external/file/template.rb @@ -8,7 +8,7 @@ module Gitlab class Template < Base attr_reader :location, :project - SUFFIX = '.gitlab-ci.yml'.freeze + SUFFIX = '.gitlab-ci.yml' def initialize(params, context) @location = params[:template] diff --git a/lib/gitlab/ci/cron_parser.rb b/lib/gitlab/ci/cron_parser.rb index 94f4a4e36c9..1d7e7ea0f9a 100644 --- a/lib/gitlab/ci/cron_parser.rb +++ b/lib/gitlab/ci/cron_parser.rb @@ -3,8 +3,8 @@ module Gitlab module Ci class CronParser - VALID_SYNTAX_SAMPLE_TIME_ZONE = 'UTC'.freeze - VALID_SYNTAX_SAMPLE_CRON = '* * * * *'.freeze + VALID_SYNTAX_SAMPLE_TIME_ZONE = 'UTC' + VALID_SYNTAX_SAMPLE_CRON = '* * * * *' def initialize(cron, cron_timezone = 'UTC') @cron = cron diff --git a/lib/gitlab/ci/pipeline/chain/helpers.rb b/lib/gitlab/ci/pipeline/chain/helpers.rb index 6bb3a75291b..8ccb1066575 100644 --- a/lib/gitlab/ci/pipeline/chain/helpers.rb +++ b/lib/gitlab/ci/pipeline/chain/helpers.rb @@ -5,7 +5,12 @@ module Gitlab module Pipeline module Chain module Helpers - def error(message) + def error(message, config_error: false) + if config_error && command.save_incompleted + pipeline.yaml_errors = message + pipeline.drop!(:config_error) + end + pipeline.errors.add(:base, message) end end diff --git a/lib/gitlab/ci/pipeline/chain/populate.rb b/lib/gitlab/ci/pipeline/chain/populate.rb index 65029f5ce7f..13eca5a9d28 100644 --- a/lib/gitlab/ci/pipeline/chain/populate.rb +++ b/lib/gitlab/ci/pipeline/chain/populate.rb @@ -26,7 +26,7 @@ module Gitlab # Gather all runtime build/stage errors # if seeds_errors = pipeline.stage_seeds.flat_map(&:errors).compact.presence - return error(seeds_errors.join("\n")) + return error(seeds_errors.join("\n"), config_error: true) end ## diff --git a/lib/gitlab/ci/reports/test_case.rb b/lib/gitlab/ci/reports/test_case.rb index 292e273a03a..fdeaad698b9 100644 --- a/lib/gitlab/ci/reports/test_case.rb +++ b/lib/gitlab/ci/reports/test_case.rb @@ -4,10 +4,10 @@ module Gitlab module Ci module Reports class TestCase - STATUS_SUCCESS = 'success'.freeze - STATUS_FAILED = 'failed'.freeze - STATUS_SKIPPED = 'skipped'.freeze - STATUS_ERROR = 'error'.freeze + STATUS_SUCCESS = 'success' + STATUS_FAILED = 'failed' + STATUS_SKIPPED = 'skipped' + STATUS_ERROR = 'error' STATUS_TYPES = [STATUS_SUCCESS, STATUS_FAILED, STATUS_SKIPPED, STATUS_ERROR].freeze attr_reader :name, :classname, :execution_time, :status, :file, :system_output, :stack_trace, :key diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml index 5c1c0c142e5..f704266b73d 100644 --- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @@ -54,7 +54,7 @@ variables: ROLLOUT_RESOURCE_TYPE: deployment - DOCKER_TLS_CERTDIR: "" # https://gitlab.com/gitlab-org/gitlab-runner/issues/4501 + DOCKER_TLS_CERTDIR: "" # https://gitlab.com/gitlab-org/gitlab-runner/issues/4501 stages: - build @@ -73,16 +73,16 @@ stages: - cleanup include: - - template: Jobs/Build.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml - - template: Jobs/Test.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Test.gitlab-ci.yml - - template: Jobs/Code-Quality.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml - - template: Jobs/Deploy.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml - - template: Jobs/Browser-Performance-Testing.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml - - template: Security/DAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml - - template: Security/Container-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml - - template: Security/Dependency-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml - - template: Security/License-Management.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml - - template: Security/SAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml + - template: Jobs/Build.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml + - template: Jobs/Test.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Test.gitlab-ci.yml + - template: Jobs/Code-Quality.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml + - template: Jobs/Deploy.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml + - template: Jobs/Browser-Performance-Testing.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml + - template: Security/DAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml + - template: Security/Container-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml + - template: Security/Dependency-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml + - template: Security/License-Management.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml + - template: Security/SAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml # Override DAST job to exclude master branch dast: diff --git a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml index 3adc6a72874..3cdb7b5420c 100644 --- a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml @@ -25,8 +25,9 @@ code_quality: expire_in: 1 week dependencies: [] only: - - branches - - tags + refs: + - branches + - tags except: variables: - $CODE_QUALITY_DISABLED diff --git a/lib/gitlab/cleanup/project_upload_file_finder.rb b/lib/gitlab/cleanup/project_upload_file_finder.rb index 5aace564c2d..3d35d474f5d 100644 --- a/lib/gitlab/cleanup/project_upload_file_finder.rb +++ b/lib/gitlab/cleanup/project_upload_file_finder.rb @@ -5,9 +5,9 @@ module Gitlab class ProjectUploadFileFinder FIND_BATCH_SIZE = 500 ABSOLUTE_UPLOAD_DIR = FileUploader.root.freeze - EXCLUDED_SYSTEM_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/-/*".freeze - EXCLUDED_HASHED_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/@hashed/*".freeze - EXCLUDED_TMP_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/tmp/*".freeze + EXCLUDED_SYSTEM_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/-/*" + EXCLUDED_HASHED_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/@hashed/*" + EXCLUDED_TMP_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/tmp/*" # Paths are relative to the upload directory def each_file_batch(batch_size: FIND_BATCH_SIZE, &block) diff --git a/lib/gitlab/correlation_id.rb b/lib/gitlab/correlation_id.rb deleted file mode 100644 index 0f9bde4390e..00000000000 --- a/lib/gitlab/correlation_id.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CorrelationId - LOG_KEY = 'correlation_id'.freeze - - class << self - def use_id(correlation_id, &blk) - # always generate a id if null is passed - correlation_id ||= new_id - - ids.push(correlation_id || new_id) - - begin - yield(current_id) - ensure - ids.pop - end - end - - def current_id - ids.last - end - - def current_or_new_id - current_id || new_id - end - - private - - def ids - Thread.current[:correlation_id] ||= [] - end - - def new_id - SecureRandom.uuid - end - end - end -end diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb index 5424298723e..d30d5a38670 100644 --- a/lib/gitlab/danger/helper.rb +++ b/lib/gitlab/danger/helper.rb @@ -110,7 +110,8 @@ module Gitlab karma\.config\.js | webpack\.config\.js | package\.json | - yarn\.lock + yarn\.lock | + \.gitlab/ci/frontend\.gitlab-ci\.yml )\z}x => :frontend, %r{\A(ee/)?db/(?!fixtures)[^/]+} => :database, diff --git a/lib/gitlab/database/sha_attribute.rb b/lib/gitlab/database/sha_attribute.rb index ddbabc9098e..776e80701f1 100644 --- a/lib/gitlab/database/sha_attribute.rb +++ b/lib/gitlab/database/sha_attribute.rb @@ -12,7 +12,7 @@ module Gitlab # using them as if they were stored as string values. This gives you the # ease of use of string values, but without the storage overhead. class ShaAttribute < BINARY_TYPE - PACK_FORMAT = 'H*'.freeze + PACK_FORMAT = 'H*' # Casts binary data to a SHA1 in hexadecimal. def deserialize(value) diff --git a/lib/gitlab/downtime_check/message.rb b/lib/gitlab/downtime_check/message.rb index ec38bd769a3..5debb754943 100644 --- a/lib/gitlab/downtime_check/message.rb +++ b/lib/gitlab/downtime_check/message.rb @@ -5,8 +5,8 @@ module Gitlab class Message attr_reader :path, :offline - OFFLINE = "\e[31moffline\e[0m".freeze - ONLINE = "\e[32monline\e[0m".freeze + OFFLINE = "\e[31moffline\e[0m" + ONLINE = "\e[32monline\e[0m" # path - The file path of the migration. # offline - When set to `true` the migration will require downtime. diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb index 86e532766b1..20c31e06905 100644 --- a/lib/gitlab/ee_compat_check.rb +++ b/lib/gitlab/ee_compat_check.rb @@ -1,11 +1,12 @@ +# coding: utf-8 # frozen_string_literal: true # rubocop: disable Rails/Output module Gitlab # Checks if a set of migrations requires downtime or not. class EeCompatCheck - CANONICAL_CE_PROJECT_URL = 'https://gitlab.com/gitlab-org/gitlab-ce'.freeze - CANONICAL_EE_REPO_URL = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze + CANONICAL_CE_PROJECT_URL = 'https://gitlab.com/gitlab-org/gitlab-ce' + CANONICAL_EE_REPO_URL = 'https://gitlab.com/gitlab-org/gitlab-ee.git' CHECK_DIR = Rails.root.join('ee_compat_check') IGNORED_FILES_REGEX = /VERSION|CHANGELOG\.md|doc\/.+/i.freeze PLEASE_READ_THIS_BANNER = %Q{ diff --git a/lib/gitlab/etag_caching/store.rb b/lib/gitlab/etag_caching/store.rb index 2395e7be026..1d2f0d7bbf4 100644 --- a/lib/gitlab/etag_caching/store.rb +++ b/lib/gitlab/etag_caching/store.rb @@ -4,7 +4,7 @@ module Gitlab module EtagCaching class Store EXPIRY_TIME = 20.minutes - SHARED_STATE_NAMESPACE = 'etag:'.freeze + SHARED_STATE_NAMESPACE = 'etag:' def get(key) Gitlab::Redis::SharedState.with { |redis| redis.get(redis_shared_state_key(key)) } diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb index df9f33baec2..8d13c74dca2 100644 --- a/lib/gitlab/git.rb +++ b/lib/gitlab/git.rb @@ -7,11 +7,11 @@ module Gitlab # The ID of empty tree. # See http://stackoverflow.com/a/40884093/1856239 and # https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012 - EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze + EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904' BLANK_SHA = ('0' * 40).freeze COMMIT_ID = /\A[0-9a-f]{40}\z/.freeze - TAG_REF_PREFIX = "refs/tags/".freeze - BRANCH_REF_PREFIX = "refs/heads/".freeze + TAG_REF_PREFIX = "refs/tags/" + BRANCH_REF_PREFIX = "refs/heads/" BaseError = Class.new(StandardError) CommandError = Class.new(BaseError) diff --git a/lib/gitlab/git/diff_collection.rb b/lib/gitlab/git/diff_collection.rb index 5c70cb6c66c..cb9154cb1e8 100644 --- a/lib/gitlab/git/diff_collection.rb +++ b/lib/gitlab/git/diff_collection.rb @@ -81,6 +81,12 @@ module Gitlab end end + def line_count + populate! + + @line_count + end + def decorate! collection = each_with_index do |element, i| @array[i] = yield(element) diff --git a/lib/gitlab/git/lfs_pointer_file.rb b/lib/gitlab/git/lfs_pointer_file.rb index b7019a221ac..efd84e30ad9 100644 --- a/lib/gitlab/git/lfs_pointer_file.rb +++ b/lib/gitlab/git/lfs_pointer_file.rb @@ -3,8 +3,8 @@ module Gitlab module Git class LfsPointerFile - VERSION = "https://git-lfs.github.com/spec/v1".freeze - VERSION_LINE = "version #{VERSION}".freeze + VERSION = "https://git-lfs.github.com/spec/v1" + VERSION_LINE = "version #{VERSION}" def initialize(data) @data = data diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 27032602828..4ea618f063b 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -15,9 +15,9 @@ module Gitlab SEARCH_CONTEXT_LINES = 3 REV_LIST_COMMIT_LIMIT = 2_000 - GITALY_INTERNAL_URL = 'ssh://gitaly/internal.git'.freeze + GITALY_INTERNAL_URL = 'ssh://gitaly/internal.git' GITLAB_PROJECTS_TIMEOUT = Gitlab.config.gitlab_shell.git_timeout - EMPTY_REPOSITORY_CHECKSUM = '0000000000000000000000000000000000000000'.freeze + EMPTY_REPOSITORY_CHECKSUM = '0000000000000000000000000000000000000000' NoRepository = Class.new(StandardError) InvalidRepository = Class.new(StandardError) diff --git a/lib/gitlab/git/util.rb b/lib/gitlab/git/util.rb index 03c2c1367b0..cf8571bf917 100644 --- a/lib/gitlab/git/util.rb +++ b/lib/gitlab/git/util.rb @@ -5,7 +5,7 @@ module Gitlab module Git module Util - LINE_SEP = "\n".freeze + LINE_SEP = "\n" def self.count_lines(string) case string[-1] diff --git a/lib/gitlab/github_import/issuable_finder.rb b/lib/gitlab/github_import/issuable_finder.rb index 211915f1d87..c81603a1aa9 100644 --- a/lib/gitlab/github_import/issuable_finder.rb +++ b/lib/gitlab/github_import/issuable_finder.rb @@ -10,7 +10,7 @@ module Gitlab attr_reader :project, :object # The base cache key to use for storing/retrieving issuable IDs. - CACHE_KEY = 'github-import/issuable-finder/%{project}/%{type}/%{iid}'.freeze + CACHE_KEY = 'github-import/issuable-finder/%{project}/%{type}/%{iid}' # project - An instance of `Project`. # object - The object to look up or set a database ID for. diff --git a/lib/gitlab/github_import/label_finder.rb b/lib/gitlab/github_import/label_finder.rb index d2479a8f565..cad39e48e43 100644 --- a/lib/gitlab/github_import/label_finder.rb +++ b/lib/gitlab/github_import/label_finder.rb @@ -6,7 +6,7 @@ module Gitlab attr_reader :project # The base cache key to use for storing/retrieving label IDs. - CACHE_KEY = 'github-import/label-finder/%{project}/%{name}'.freeze + CACHE_KEY = 'github-import/label-finder/%{project}/%{name}' # project - An instance of `Project`. def initialize(project) diff --git a/lib/gitlab/github_import/milestone_finder.rb b/lib/gitlab/github_import/milestone_finder.rb index 5625730e796..a157a1e1ff5 100644 --- a/lib/gitlab/github_import/milestone_finder.rb +++ b/lib/gitlab/github_import/milestone_finder.rb @@ -6,7 +6,7 @@ module Gitlab attr_reader :project # The base cache key to use for storing/retrieving milestone IDs. - CACHE_KEY = 'github-import/milestone-finder/%{project}/%{iid}'.freeze + CACHE_KEY = 'github-import/milestone-finder/%{project}/%{iid}' # project - An instance of `Project` def initialize(project) diff --git a/lib/gitlab/github_import/page_counter.rb b/lib/gitlab/github_import/page_counter.rb index c3db2d0b469..a3e7b3c1afc 100644 --- a/lib/gitlab/github_import/page_counter.rb +++ b/lib/gitlab/github_import/page_counter.rb @@ -9,7 +9,7 @@ module Gitlab attr_reader :cache_key # The base cache key to use for storing the last page number. - CACHE_KEY = 'github-importer/page-counter/%{project}/%{collection}'.freeze + CACHE_KEY = 'github-importer/page-counter/%{project}/%{collection}' def initialize(project, collection) @cache_key = CACHE_KEY % { project: project.id, collection: collection } diff --git a/lib/gitlab/github_import/parallel_scheduling.rb b/lib/gitlab/github_import/parallel_scheduling.rb index d4d1357f5a3..849a66d47ed 100644 --- a/lib/gitlab/github_import/parallel_scheduling.rb +++ b/lib/gitlab/github_import/parallel_scheduling.rb @@ -7,7 +7,7 @@ module Gitlab # The base cache key to use for tracking already imported objects. ALREADY_IMPORTED_CACHE_KEY = - 'github-importer/already-imported/%{project}/%{collection}'.freeze + 'github-importer/already-imported/%{project}/%{collection}' # project - An instance of `Project`. # client - An instance of `Gitlab::GithubImport::Client`. diff --git a/lib/gitlab/github_import/user_finder.rb b/lib/gitlab/github_import/user_finder.rb index 30283f147ef..51a532437bd 100644 --- a/lib/gitlab/github_import/user_finder.rb +++ b/lib/gitlab/github_import/user_finder.rb @@ -16,17 +16,17 @@ module Gitlab # The base cache key to use for caching user IDs for a given GitHub user # ID. - ID_CACHE_KEY = 'github-import/user-finder/user-id/%s'.freeze + ID_CACHE_KEY = 'github-import/user-finder/user-id/%s' # The base cache key to use for caching user IDs for a given GitHub email # address. ID_FOR_EMAIL_CACHE_KEY = - 'github-import/user-finder/id-for-email/%s'.freeze + 'github-import/user-finder/id-for-email/%s' # The base cache key to use for caching the Email addresses of GitHub # usernames. EMAIL_FOR_USERNAME_CACHE_KEY = - 'github-import/user-finder/email-for-username/%s'.freeze + 'github-import/user-finder/email-for-username/%s' # project - An instance of `Project` # client - An instance of `Gitlab::GithubImport::Client` diff --git a/lib/gitlab/graphql/authorize/authorize_field_service.rb b/lib/gitlab/graphql/authorize/authorize_field_service.rb index 3b5dde2fde5..c7f430490d6 100644 --- a/lib/gitlab/graphql/authorize/authorize_field_service.rb +++ b/lib/gitlab/graphql/authorize/authorize_field_service.rb @@ -54,14 +54,14 @@ module Gitlab # The field is a built-in/scalar type, or a list of scalars # authorize using the parent's object parent_typed_object.object - elsif resolved_type.respond_to?(:object) - # The field is a type representing a single object, we'll authorize - # against the object directly - resolved_type.object elsif @field.connection? || resolved_type.is_a?(Array) # The field is a connection or a list of non-built-in types, we'll # authorize each element when rendering nil + elsif resolved_type.respond_to?(:object) + # The field is a type representing a single object, we'll authorize + # against the object directly + resolved_type.object else # Resolved type is a single object that might not be loaded yet by # the batchloader, we'll authorize that @@ -74,9 +74,9 @@ module Gitlab # Authorizing fields representing scalars, or a simple field with an object resolved_type if allowed_access?(current_user, authorizing_object) elsif @field.connection? - # A connection with pagination, modify the visible nodes in on the + # A connection with pagination, modify the visible nodes on the # connection type in place - resolved_type.edge_nodes.to_a.keep_if { |node| allowed_access?(current_user, node) } + resolved_type.object.edge_nodes.to_a.keep_if { |node| allowed_access?(current_user, node) } resolved_type elsif resolved_type.is_a? Array # A simple list of rendered types each object being an object to authorize diff --git a/lib/gitlab/graphql/authorize/authorize_resource.rb b/lib/gitlab/graphql/authorize/authorize_resource.rb index ef5caaf5b0e..6844367454f 100644 --- a/lib/gitlab/graphql/authorize/authorize_resource.rb +++ b/lib/gitlab/graphql/authorize/authorize_resource.rb @@ -29,19 +29,25 @@ module Gitlab def authorized_find!(*args) object = find_object(*args) + object = object.sync if object.respond_to?(:sync) + authorize!(object) object end def authorize!(object) - unless authorized?(object) + unless authorized_resource?(object) raise Gitlab::Graphql::Errors::ResourceNotAvailable, "The resource that you are attempting to access does not exist or you don't have permission to perform this action" end end - def authorized?(object) + # this was named `#authorized?`, however it conflicts with the native + # graphql gem version + # TODO consider adopting the gem's built in authorization system + # https://gitlab.com/gitlab-org/gitlab-ee/issues/13984 + def authorized_resource?(object) # Sanity check. We don't want to accidentally allow a developer to authorize # without first adding permissions to authorize against if self.class.required_permissions.empty? diff --git a/lib/gitlab/graphql/loaders/batch_lfs_oid_loader.rb b/lib/gitlab/graphql/loaders/batch_lfs_oid_loader.rb index 8f34e58a771..67511c124e4 100644 --- a/lib/gitlab/graphql/loaders/batch_lfs_oid_loader.rb +++ b/lib/gitlab/graphql/loaders/batch_lfs_oid_loader.rb @@ -9,7 +9,7 @@ module Gitlab end def find - BatchLoader.for(blob_id).batch(key: repository) do |blob_ids, loader, batch_args| + BatchLoader::GraphQL.for(blob_id).batch(key: repository) do |blob_ids, loader, batch_args| Gitlab::Git::Blob.batch_lfs_pointers(batch_args[:key], blob_ids).each do |loaded_blob| loader.call(loaded_blob.id, loaded_blob.lfs_oid) end diff --git a/lib/gitlab/graphql/loaders/batch_model_loader.rb b/lib/gitlab/graphql/loaders/batch_model_loader.rb index 50d3293fcbb..164fe74148c 100644 --- a/lib/gitlab/graphql/loaders/batch_model_loader.rb +++ b/lib/gitlab/graphql/loaders/batch_model_loader.rb @@ -12,7 +12,7 @@ module Gitlab # rubocop: disable CodeReuse/ActiveRecord def find - BatchLoader.for({ model: model_class, id: model_id.to_i }).batch do |loader_info, loader| + BatchLoader::GraphQL.for({ model: model_class, id: model_id.to_i }).batch do |loader_info, loader| per_model = loader_info.group_by { |info| info[:model] } per_model.each do |model, info| ids = info.map { |i| i[:id] } diff --git a/lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb b/lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb index 5e151f4dbd7..449f4160a6c 100644 --- a/lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb +++ b/lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb @@ -11,7 +11,7 @@ module Gitlab end def find - BatchLoader.for(project_id).batch do |project_ids, loader| + BatchLoader::GraphQL.for(project_id).batch do |project_ids, loader| ProjectStatistics.for_project_ids(project_ids).each do |statistics| loader.call(statistics.project_id, statistics) end diff --git a/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader.rb b/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader.rb index a0312366d66..366aa74d435 100644 --- a/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader.rb +++ b/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader.rb @@ -11,7 +11,7 @@ module Gitlab end def find - BatchLoader.for(namespace_id).batch do |namespace_ids, loader| + BatchLoader::GraphQL.for(namespace_id).batch do |namespace_ids, loader| Namespace::RootStorageStatistics.for_namespace_ids(namespace_ids).each do |statistics| loader.call(statistics.namespace_id, statistics) end diff --git a/lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb b/lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb index 81c5cabf451..70344392138 100644 --- a/lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb +++ b/lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb @@ -11,7 +11,7 @@ module Gitlab end def find_last - BatchLoader.for(sha).batch(key: project) do |shas, loader, args| + BatchLoader::GraphQL.for(sha).batch(key: project) do |shas, loader, args| pipelines = args[:key].ci_pipelines.latest_for_shas(shas) pipelines.each do |pipeline| diff --git a/lib/gitlab/graphql/representation/submodule_tree_entry.rb b/lib/gitlab/graphql/representation/submodule_tree_entry.rb index 65716dff75d..8d17cb9eecc 100644 --- a/lib/gitlab/graphql/representation/submodule_tree_entry.rb +++ b/lib/gitlab/graphql/representation/submodule_tree_entry.rb @@ -4,6 +4,8 @@ module Gitlab module Graphql module Representation class SubmoduleTreeEntry < SimpleDelegator + include GlobalID::Identification + class << self def decorate(submodules, tree) repository = tree.repository diff --git a/lib/gitlab/graphql/representation/tree_entry.rb b/lib/gitlab/graphql/representation/tree_entry.rb index 7ea83db5876..7e347081a9b 100644 --- a/lib/gitlab/graphql/representation/tree_entry.rb +++ b/lib/gitlab/graphql/representation/tree_entry.rb @@ -4,6 +4,8 @@ module Gitlab module Graphql module Representation class TreeEntry < SimpleDelegator + include GlobalID::Identification + class << self def decorate(entries, repository) return if entries.nil? diff --git a/lib/gitlab/health_checks/gitaly_check.rb b/lib/gitlab/health_checks/gitaly_check.rb index 898733fea5d..e560f87bf98 100644 --- a/lib/gitlab/health_checks/gitaly_check.rb +++ b/lib/gitlab/health_checks/gitaly_check.rb @@ -5,7 +5,7 @@ module Gitlab class GitalyCheck extend BaseAbstractCheck - METRIC_PREFIX = 'gitaly_health_check'.freeze + METRIC_PREFIX = 'gitaly_health_check' class << self def readiness diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index bb46bd657e8..d08848a65a8 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -7,7 +7,7 @@ module Gitlab # For every version update the version history in these docs must be kept up to date: # - development/import_export.md # - user/project/settings/import_export.md - VERSION = '0.2.4'.freeze + VERSION = '0.2.4' FILENAME_LIMIT = 50 def export_path(relative_path:) diff --git a/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb b/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb index d39b6fe5955..c5fb39b7b52 100644 --- a/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb +++ b/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb @@ -10,7 +10,7 @@ module Gitlab StrategyError = Class.new(StandardError) - AFTER_EXPORT_LOCK_FILE_NAME = '.after_export_action'.freeze + AFTER_EXPORT_LOCK_FILE_NAME = '.after_export_action' private diff --git a/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb b/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb index acb7f225b17..aaa70f0b36d 100644 --- a/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb +++ b/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb @@ -4,9 +4,9 @@ module Gitlab module ImportExport module AfterExportStrategies class WebUploadStrategy < BaseAfterExportStrategy - PUT_METHOD = 'PUT'.freeze - POST_METHOD = 'POST'.freeze - INVALID_HTTP_METHOD = 'invalid. Only PUT and POST methods allowed.'.freeze + PUT_METHOD = 'PUT' + POST_METHOD = 'POST' + INVALID_HTTP_METHOD = 'invalid. Only PUT and POST methods allowed.' validates :url, addressable_url: true diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb index 8b346f6d7d2..eece4edf895 100644 --- a/lib/gitlab/incoming_email.rb +++ b/lib/gitlab/incoming_email.rb @@ -2,9 +2,9 @@ module Gitlab module IncomingEmail - UNSUBSCRIBE_SUFFIX = '-unsubscribe'.freeze - UNSUBSCRIBE_SUFFIX_LEGACY = '+unsubscribe'.freeze - WILDCARD_PLACEHOLDER = '%{key}'.freeze + UNSUBSCRIBE_SUFFIX = '-unsubscribe' + UNSUBSCRIBE_SUFFIX_LEGACY = '+unsubscribe' + WILDCARD_PLACEHOLDER = '%{key}' class << self def enabled? diff --git a/lib/gitlab/job_waiter.rb b/lib/gitlab/job_waiter.rb index e97e961771c..90dbe4d005d 100644 --- a/lib/gitlab/job_waiter.rb +++ b/lib/gitlab/job_waiter.rb @@ -17,7 +17,7 @@ module Gitlab # push to that array when done. Once the waiter has popped `count` items, it # knows all the jobs are done. class JobWaiter - KEY_PREFIX = "gitlab:job_waiter".freeze + KEY_PREFIX = "gitlab:job_waiter" def self.notify(key, jid) Gitlab::Redis::SharedState.with { |redis| redis.lpush(key, jid) } diff --git a/lib/gitlab/jwt_authenticatable.rb b/lib/gitlab/jwt_authenticatable.rb new file mode 100644 index 00000000000..1270a148e8d --- /dev/null +++ b/lib/gitlab/jwt_authenticatable.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Gitlab + module JwtAuthenticatable + # Supposedly the effective key size for HMAC-SHA256 is 256 bits, i.e. 32 + # bytes https://tools.ietf.org/html/rfc4868#section-2.6 + SECRET_LENGTH = 32 + + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + include Gitlab::Utils::StrongMemoize + + def decode_jwt_for_issuer(issuer, encoded_message) + JWT.decode( + encoded_message, + secret, + true, + { iss: issuer, verify_iss: true, algorithm: 'HS256' } + ) + end + + def secret + strong_memoize(:secret) do + Base64.strict_decode64(File.read(secret_path).chomp).tap do |bytes| + raise "#{secret_path} does not contain #{SECRET_LENGTH} bytes" if bytes.length != SECRET_LENGTH + end + end + end + + def write_secret + bytes = SecureRandom.random_bytes(SECRET_LENGTH) + File.open(secret_path, 'w:BINARY', 0600) do |f| + f.chmod(0600) # If the file already existed, the '0600' passed to 'open' above was a no-op. + f.write(Base64.strict_encode64(bytes)) + end + end + end + end +end diff --git a/lib/gitlab/kubernetes/helm.rb b/lib/gitlab/kubernetes/helm.rb index 6e4286589c1..16ed0cb0f8e 100644 --- a/lib/gitlab/kubernetes/helm.rb +++ b/lib/gitlab/kubernetes/helm.rb @@ -3,12 +3,12 @@ module Gitlab module Kubernetes module Helm - HELM_VERSION = '2.14.3'.freeze - KUBECTL_VERSION = '1.11.10'.freeze - NAMESPACE = 'gitlab-managed-apps'.freeze - SERVICE_ACCOUNT = 'tiller'.freeze - CLUSTER_ROLE_BINDING = 'tiller-admin'.freeze - CLUSTER_ROLE = 'cluster-admin'.freeze + HELM_VERSION = '2.14.3' + KUBECTL_VERSION = '1.11.10' + NAMESPACE = 'gitlab-managed-apps' + SERVICE_ACCOUNT = 'tiller' + CLUSTER_ROLE_BINDING = 'tiller-admin' + CLUSTER_ROLE = 'cluster-admin' end end end diff --git a/lib/gitlab/kubernetes/pod.rb b/lib/gitlab/kubernetes/pod.rb index 81317e532b2..d247662dc3b 100644 --- a/lib/gitlab/kubernetes/pod.rb +++ b/lib/gitlab/kubernetes/pod.rb @@ -3,11 +3,11 @@ module Gitlab module Kubernetes module Pod - PENDING = 'Pending'.freeze - RUNNING = 'Running'.freeze - SUCCEEDED = 'Succeeded'.freeze - FAILED = 'Failed'.freeze - UNKNOWN = 'Unknown'.freeze + PENDING = 'Pending' + RUNNING = 'Running' + SUCCEEDED = 'Succeeded' + FAILED = 'Failed' + UNKNOWN = 'Unknown' PHASES = [PENDING, RUNNING, SUCCEEDED, FAILED, UNKNOWN].freeze end end diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb index 128a5dd8936..2ec8c268d09 100644 --- a/lib/gitlab/logger.rb +++ b/lib/gitlab/logger.rb @@ -40,7 +40,7 @@ module Gitlab end def self.cache_key - 'logger:'.freeze + self.full_log_path.to_s + 'logger:' + self.full_log_path.to_s end end end diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb index 3bfa3da35e0..c38769f39a9 100644 --- a/lib/gitlab/metrics/samplers/ruby_sampler.rb +++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb @@ -6,7 +6,7 @@ module Gitlab module Metrics module Samplers class RubySampler < BaseSampler - GC_REPORT_BUCKETS = [0.001, 0.002, 0.005, 0.01, 0.05, 0.1, 0.5].freeze + GC_REPORT_BUCKETS = [0.005, 0.01, 0.02, 0.04, 0.07, 0.1, 0.5].freeze def initialize(interval) GC::Profiler.clear diff --git a/lib/gitlab/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb index c068f8017fd..2ed5878286a 100644 --- a/lib/gitlab/metrics/subscribers/action_view.rb +++ b/lib/gitlab/metrics/subscribers/action_view.rb @@ -15,7 +15,7 @@ module Gitlab attach_to :action_view - SERIES = 'views'.freeze + SERIES = 'views' def render_template(event) track(event) if current_transaction diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb index d7986685c65..9584285be7e 100644 --- a/lib/gitlab/metrics/transaction.rb +++ b/lib/gitlab/metrics/transaction.rb @@ -14,7 +14,7 @@ module Gitlab THREAD_KEY = :_gitlab_metrics_transaction # The series to store events (e.g. Git pushes) in. - EVENT_SERIES = 'events'.freeze + EVENT_SERIES = 'events' attr_reader :tags, :values, :method, :metrics diff --git a/lib/gitlab/metrics/web_transaction.rb b/lib/gitlab/metrics/web_transaction.rb index b2a43d46fb2..fa17548723e 100644 --- a/lib/gitlab/metrics/web_transaction.rb +++ b/lib/gitlab/metrics/web_transaction.rb @@ -3,8 +3,8 @@ module Gitlab module Metrics class WebTransaction < Transaction - CONTROLLER_KEY = 'action_controller.instance'.freeze - ENDPOINT_KEY = 'api.endpoint'.freeze + CONTROLLER_KEY = 'action_controller.instance' + ENDPOINT_KEY = 'api.endpoint' ALLOWED_SUFFIXES = Set.new(%w[json js atom rss xml zip]) def initialize(env) diff --git a/lib/gitlab/middleware/multipart.rb b/lib/gitlab/middleware/multipart.rb index 433151b80e7..86b28e4e20a 100644 --- a/lib/gitlab/middleware/multipart.rb +++ b/lib/gitlab/middleware/multipart.rb @@ -28,7 +28,7 @@ module Gitlab module Middleware class Multipart - RACK_ENV_KEY = 'HTTP_GITLAB_WORKHORSE_MULTIPART_FIELDS'.freeze + RACK_ENV_KEY = 'HTTP_GITLAB_WORKHORSE_MULTIPART_FIELDS' class Handler def initialize(env, message) diff --git a/lib/gitlab/middleware/read_only/controller.rb b/lib/gitlab/middleware/read_only/controller.rb index 53e55269c6e..802ff18fc58 100644 --- a/lib/gitlab/middleware/read_only/controller.rb +++ b/lib/gitlab/middleware/read_only/controller.rb @@ -5,9 +5,9 @@ module Gitlab class ReadOnly class Controller DISALLOWED_METHODS = %w(POST PATCH PUT DELETE).freeze - APPLICATION_JSON = 'application/json'.freeze + APPLICATION_JSON = 'application/json' APPLICATION_JSON_TYPES = %W{#{APPLICATION_JSON} application/vnd.git-lfs+json}.freeze - ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance'.freeze + ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance' WHITELISTED_GIT_ROUTES = { 'projects/git_http' => %w{git_upload_pack git_receive_pack} diff --git a/lib/gitlab/optimistic_locking.rb b/lib/gitlab/optimistic_locking.rb index 0c0f46d3b77..d51d718c826 100644 --- a/lib/gitlab/optimistic_locking.rb +++ b/lib/gitlab/optimistic_locking.rb @@ -4,7 +4,8 @@ module Gitlab module OptimisticLocking module_function - def retry_lock(subject, retries = 100, &block) + def retry_lock(subject, retries = nil, &block) + retries ||= 100 # TODO(Observability): We should be recording details of the number of retries and the duration of the total execution here ActiveRecord::Base.transaction do yield(subject) diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb index d9c28ff1181..ee2ef91c65c 100644 --- a/lib/gitlab/path_regex.rb +++ b/lib/gitlab/path_regex.rb @@ -125,9 +125,9 @@ module Gitlab # allow non-regex validations, etc), `NAMESPACE_FORMAT_REGEX_JS` serves as a Javascript-compatible version of # `NAMESPACE_FORMAT_REGEX`, with the negative lookbehind assertion removed. This means that the client-side validation # will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation. - PATH_START_CHAR = '[a-zA-Z0-9_\.]'.freeze - PATH_REGEX_STR = PATH_START_CHAR + '[a-zA-Z0-9_\-\.]*'.freeze - NAMESPACE_FORMAT_REGEX_JS = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze + PATH_START_CHAR = '[a-zA-Z0-9_\.]' + PATH_REGEX_STR = PATH_START_CHAR + '[a-zA-Z0-9_\-\.]*' + NAMESPACE_FORMAT_REGEX_JS = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]' NO_SUFFIX_REGEX = /(?<!\.git|\.atom)/.freeze NAMESPACE_FORMAT_REGEX = /(?:#{NAMESPACE_FORMAT_REGEX_JS})#{NO_SUFFIX_REGEX}/.freeze diff --git a/lib/gitlab/performance_bar.rb b/lib/gitlab/performance_bar.rb index 68af290d069..4445c876e7a 100644 --- a/lib/gitlab/performance_bar.rb +++ b/lib/gitlab/performance_bar.rb @@ -2,7 +2,7 @@ module Gitlab module PerformanceBar - ALLOWED_USER_IDS_KEY = 'performance_bar_allowed_user_ids:v2'.freeze + ALLOWED_USER_IDS_KEY = 'performance_bar_allowed_user_ids:v2' EXPIRY_TIME_L1_CACHE = 1.minute EXPIRY_TIME_L2_CACHE = 5.minutes diff --git a/lib/gitlab/polling_interval.rb b/lib/gitlab/polling_interval.rb index 0f69990df63..fd66787b029 100644 --- a/lib/gitlab/polling_interval.rb +++ b/lib/gitlab/polling_interval.rb @@ -2,7 +2,7 @@ module Gitlab class PollingInterval - HEADER_NAME = 'Poll-Interval'.freeze + HEADER_NAME = 'Poll-Interval' def self.set_header(response, interval:) if polling_enabled? diff --git a/lib/gitlab/private_commit_email.rb b/lib/gitlab/private_commit_email.rb index 536fc9dae3a..886c2c32d36 100644 --- a/lib/gitlab/private_commit_email.rb +++ b/lib/gitlab/private_commit_email.rb @@ -2,7 +2,7 @@ module Gitlab module PrivateCommitEmail - TOKEN = "_private".freeze + TOKEN = "_private" class << self def regex diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb index 425c30d67fe..3f26b84be20 100644 --- a/lib/gitlab/profiler.rb +++ b/lib/gitlab/profiler.rb @@ -3,7 +3,7 @@ module Gitlab module Profiler - FILTERED_STRING = '[FILTERED]'.freeze + FILTERED_STRING = '[FILTERED]' IGNORE_BACKTRACES = %w[ lib/gitlab/i18n.rb diff --git a/lib/gitlab/prometheus/additional_metrics_parser.rb b/lib/gitlab/prometheus/additional_metrics_parser.rb index bd4ca578840..ee3d98f3602 100644 --- a/lib/gitlab/prometheus/additional_metrics_parser.rb +++ b/lib/gitlab/prometheus/additional_metrics_parser.rb @@ -3,7 +3,7 @@ module Gitlab module Prometheus module AdditionalMetricsParser - CONFIG_ROOT = 'config/prometheus'.freeze + CONFIG_ROOT = 'config/prometheus' MUTEX = Mutex.new extend self diff --git a/lib/gitlab/query_limiting/middleware.rb b/lib/gitlab/query_limiting/middleware.rb index 949ae79a047..f6ffbfe2645 100644 --- a/lib/gitlab/query_limiting/middleware.rb +++ b/lib/gitlab/query_limiting/middleware.rb @@ -5,8 +5,8 @@ module Gitlab # Middleware for reporting (or raising) when a request performs more than a # certain amount of database queries. class Middleware - CONTROLLER_KEY = 'action_controller.instance'.freeze - ENDPOINT_KEY = 'api.endpoint'.freeze + CONTROLLER_KEY = 'action_controller.instance' + ENDPOINT_KEY = 'api.endpoint' def initialize(app) @app = app diff --git a/lib/gitlab/quick_actions/issuable_actions.rb b/lib/gitlab/quick_actions/issuable_actions.rb index e5d99ebee35..5cf24823ef5 100644 --- a/lib/gitlab/quick_actions/issuable_actions.rb +++ b/lib/gitlab/quick_actions/issuable_actions.rb @@ -1,3 +1,4 @@ +# coding: utf-8 # frozen_string_literal: true module Gitlab @@ -6,8 +7,8 @@ module Gitlab extend ActiveSupport::Concern include Gitlab::QuickActions::Dsl - SHRUG = '¯\\_(ツ)_/¯'.freeze - TABLEFLIP = '(╯°□°)╯︵ ┻━┻'.freeze + SHRUG = '¯\\_(ツ)_/¯' + TABLEFLIP = '(╯°□°)╯︵ ┻━┻' included do # Issue, MergeRequest, Epic: quick actions definitions diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb index da28fbf5be0..869627ac585 100644 --- a/lib/gitlab/quick_actions/issue_actions.rb +++ b/lib/gitlab/quick_actions/issue_actions.rb @@ -25,7 +25,11 @@ module Gitlab Chronic.parse(due_date_param).try(:to_date) end command :due do |due_date| - @updates[:due_date] = due_date if due_date + if due_date + @updates[:due_date] = due_date + else + @execution_message[:due] = _('Failed to set due date because the date format is invalid.') + end end desc _('Remove due date') diff --git a/lib/gitlab/redis/cache.rb b/lib/gitlab/redis/cache.rb index 6e8403ad878..6e31c506438 100644 --- a/lib/gitlab/redis/cache.rb +++ b/lib/gitlab/redis/cache.rb @@ -6,9 +6,9 @@ require_relative 'wrapper' unless defined?(::Rails) && ::Rails.root.present? module Gitlab module Redis class Cache < ::Gitlab::Redis::Wrapper - CACHE_NAMESPACE = 'cache:gitlab'.freeze - DEFAULT_REDIS_CACHE_URL = 'redis://localhost:6380'.freeze - REDIS_CACHE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_CACHE_CONFIG_FILE'.freeze + CACHE_NAMESPACE = 'cache:gitlab' + DEFAULT_REDIS_CACHE_URL = 'redis://localhost:6380' + REDIS_CACHE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_CACHE_CONFIG_FILE' class << self def default_url diff --git a/lib/gitlab/redis/queues.rb b/lib/gitlab/redis/queues.rb index 8b42c269dd0..0375e4a221a 100644 --- a/lib/gitlab/redis/queues.rb +++ b/lib/gitlab/redis/queues.rb @@ -6,10 +6,10 @@ require_relative 'wrapper' unless defined?(::Gitlab::Redis::Wrapper) module Gitlab module Redis class Queues < ::Gitlab::Redis::Wrapper - SIDEKIQ_NAMESPACE = 'resque:gitlab'.freeze - MAILROOM_NAMESPACE = 'mail_room:gitlab'.freeze - DEFAULT_REDIS_QUEUES_URL = 'redis://localhost:6381'.freeze - REDIS_QUEUES_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_QUEUES_CONFIG_FILE'.freeze + SIDEKIQ_NAMESPACE = 'resque:gitlab' + MAILROOM_NAMESPACE = 'mail_room:gitlab' + DEFAULT_REDIS_QUEUES_URL = 'redis://localhost:6381' + REDIS_QUEUES_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_QUEUES_CONFIG_FILE' class << self def default_url diff --git a/lib/gitlab/redis/shared_state.rb b/lib/gitlab/redis/shared_state.rb index 270a19e780c..35356083f26 100644 --- a/lib/gitlab/redis/shared_state.rb +++ b/lib/gitlab/redis/shared_state.rb @@ -6,12 +6,12 @@ require_relative 'wrapper' unless defined?(::Gitlab::Redis::Wrapper) module Gitlab module Redis class SharedState < ::Gitlab::Redis::Wrapper - SESSION_NAMESPACE = 'session:gitlab'.freeze - USER_SESSIONS_NAMESPACE = 'session:user:gitlab'.freeze - USER_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:user:gitlab'.freeze - IP_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:ip:gitlab'.freeze - DEFAULT_REDIS_SHARED_STATE_URL = 'redis://localhost:6382'.freeze - REDIS_SHARED_STATE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_SHARED_STATE_CONFIG_FILE'.freeze + SESSION_NAMESPACE = 'session:gitlab' + USER_SESSIONS_NAMESPACE = 'session:user:gitlab' + USER_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:user:gitlab' + IP_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:ip:gitlab' + DEFAULT_REDIS_SHARED_STATE_URL = 'redis://localhost:6382' + REDIS_SHARED_STATE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_SHARED_STATE_CONFIG_FILE' class << self def default_url diff --git a/lib/gitlab/redis/wrapper.rb b/lib/gitlab/redis/wrapper.rb index 07a1e20b076..fa1615a5953 100644 --- a/lib/gitlab/redis/wrapper.rb +++ b/lib/gitlab/redis/wrapper.rb @@ -8,8 +8,8 @@ require 'active_support/core_ext/module/delegation' module Gitlab module Redis class Wrapper - DEFAULT_REDIS_URL = 'redis://localhost:6379'.freeze - REDIS_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_CONFIG_FILE'.freeze + DEFAULT_REDIS_URL = 'redis://localhost:6379' + REDIS_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_CONFIG_FILE' class << self delegate :params, :url, to: :new diff --git a/lib/gitlab/request_profiler.rb b/lib/gitlab/request_profiler.rb index 033e451dbee..dd1482da40d 100644 --- a/lib/gitlab/request_profiler.rb +++ b/lib/gitlab/request_profiler.rb @@ -4,7 +4,7 @@ require 'fileutils' module Gitlab module RequestProfiler - PROFILES_DIR = "#{Gitlab.config.shared.path}/tmp/requests_profiles".freeze + PROFILES_DIR = "#{Gitlab.config.shared.path}/tmp/requests_profiles" def all Dir["#{PROFILES_DIR}/*.{html,txt}"].map do |path| diff --git a/lib/gitlab/shard_health_cache.rb b/lib/gitlab/shard_health_cache.rb index 6612347438e..eeb0cc75ef9 100644 --- a/lib/gitlab/shard_health_cache.rb +++ b/lib/gitlab/shard_health_cache.rb @@ -2,7 +2,7 @@ module Gitlab class ShardHealthCache - HEALTHY_SHARDS_KEY = 'gitlab-healthy-shards'.freeze + HEALTHY_SHARDS_KEY = 'gitlab-healthy-shards' HEALTHY_SHARDS_TIMEOUT = 300 # Clears the Redis set storing the list of healthy shards diff --git a/lib/gitlab/sidekiq_monitor.rb b/lib/gitlab/sidekiq_monitor.rb index 9842f1f53f7..a58b33534bf 100644 --- a/lib/gitlab/sidekiq_monitor.rb +++ b/lib/gitlab/sidekiq_monitor.rb @@ -4,7 +4,7 @@ module Gitlab class SidekiqMonitor < Daemon include ::Gitlab::Utils::StrongMemoize - NOTIFICATION_CHANNEL = 'sidekiq:cancel:notifications'.freeze + NOTIFICATION_CHANNEL = 'sidekiq:cancel:notifications' CANCEL_DEADLINE = 24.hours.seconds RECONNECT_TIME = 3.seconds diff --git a/lib/gitlab/sidekiq_status.rb b/lib/gitlab/sidekiq_status.rb index 0f890a12134..0dafccb3d34 100644 --- a/lib/gitlab/sidekiq_status.rb +++ b/lib/gitlab/sidekiq_status.rb @@ -18,7 +18,7 @@ module Gitlab # expire after a certain period of time to prevent storing too many keys in # Redis. module SidekiqStatus - STATUS_KEY = 'gitlab-sidekiq-status:%s'.freeze + STATUS_KEY = 'gitlab-sidekiq-status:%s' # The default time (in seconds) after which a status key is expired # automatically. The default of 30 minutes should be more than sufficient diff --git a/lib/gitlab/tracing.rb b/lib/gitlab/tracing.rb index 29517591c51..7732d7c9d9c 100644 --- a/lib/gitlab/tracing.rb +++ b/lib/gitlab/tracing.rb @@ -30,7 +30,7 @@ module Gitlab # Avoid using `format` since it can throw TypeErrors # which we want to avoid on unsanitised env var input tracing_url_template.to_s - .gsub(/\{\{\s*correlation_id\s*\}\}/, Gitlab::CorrelationId.current_id.to_s) + .gsub(/\{\{\s*correlation_id\s*\}\}/, Labkit::Correlation::CorrelationId.current_id.to_s) .gsub(/\{\{\s*service\s*\}\}/, Gitlab.process_name) end end diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb index 9c35d200dcb..fab504aa603 100644 --- a/lib/gitlab/url_blocker.rb +++ b/lib/gitlab/url_blocker.rb @@ -49,7 +49,7 @@ module Gitlab hostname = uri.hostname port = get_port(uri) - address_info = get_address_info(hostname, port) + address_info = get_address_info(hostname, port, dns_rebind_protection) return [uri, nil] unless address_info ip_address = ip_address(address_info) @@ -110,11 +110,15 @@ module Gitlab validate_unicode_restriction(uri) if ascii_only end - def get_address_info(hostname, port) + def get_address_info(hostname, port, dns_rebind_protection) Addrinfo.getaddrinfo(hostname, port, nil, :STREAM).map do |addr| addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr end rescue SocketError + # If the dns rebinding protection is not enabled, we allow + # urls that can't be resolved at this point. + return unless dns_rebind_protection + # In the test suite we use a lot of mocked urls that are either invalid or # don't exist. In order to avoid modifying a ton of tests and factories # we allow invalid urls unless the environment variable RSPEC_ALLOW_INVALID_URLS diff --git a/lib/gitlab/url_helpers.rb b/lib/gitlab/url_helpers.rb index 883585c196f..2a7919883c5 100644 --- a/lib/gitlab/url_helpers.rb +++ b/lib/gitlab/url_helpers.rb @@ -2,7 +2,7 @@ module Gitlab class UrlHelpers - WSS_PROTOCOL = "wss".freeze + WSS_PROTOCOL = "wss" def self.as_wss(url) return unless url.present? diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 29087d26007..db67e4fd479 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -7,17 +7,15 @@ require 'uri' module Gitlab class Workhorse - SEND_DATA_HEADER = 'Gitlab-Workhorse-Send-Data'.freeze - VERSION_FILE = 'GITLAB_WORKHORSE_VERSION'.freeze - INTERNAL_API_CONTENT_TYPE = 'application/vnd.gitlab-workhorse+json'.freeze - INTERNAL_API_REQUEST_HEADER = 'Gitlab-Workhorse-Api-Request'.freeze - NOTIFICATION_CHANNEL = 'workhorse:notifications'.freeze + SEND_DATA_HEADER = 'Gitlab-Workhorse-Send-Data' + VERSION_FILE = 'GITLAB_WORKHORSE_VERSION' + INTERNAL_API_CONTENT_TYPE = 'application/vnd.gitlab-workhorse+json' + INTERNAL_API_REQUEST_HEADER = 'Gitlab-Workhorse-Api-Request' + NOTIFICATION_CHANNEL = 'workhorse:notifications' ALLOWED_GIT_HTTP_ACTIONS = %w[git_receive_pack git_upload_pack info_refs].freeze - DETECT_HEADER = 'Gitlab-Workhorse-Detect-Content-Type'.freeze + DETECT_HEADER = 'Gitlab-Workhorse-Detect-Content-Type' - # Supposedly the effective key size for HMAC-SHA256 is 256 bits, i.e. 32 - # bytes https://tools.ietf.org/html/rfc4868#section-2.6 - SECRET_LENGTH = 32 + include JwtAuthenticatable class << self def git_http_ok(repository, repo_type, user, action, show_all_refs: false) @@ -187,34 +185,12 @@ module Gitlab path.readable? ? path.read.chomp : 'unknown' end - def secret - @secret ||= begin - bytes = Base64.strict_decode64(File.read(secret_path).chomp) - raise "#{secret_path} does not contain #{SECRET_LENGTH} bytes" if bytes.length != SECRET_LENGTH - - bytes - end - end - - def write_secret - bytes = SecureRandom.random_bytes(SECRET_LENGTH) - File.open(secret_path, 'w:BINARY', 0600) do |f| - f.chmod(0600) # If the file already existed, the '0600' passed to 'open' above was a no-op. - f.write(Base64.strict_encode64(bytes)) - end - end - def verify_api_request!(request_headers) decode_jwt(request_headers[INTERNAL_API_REQUEST_HEADER]) end def decode_jwt(encoded_message) - JWT.decode( - encoded_message, - secret, - true, - { iss: 'gitlab-workhorse', verify_iss: true, algorithm: 'HS256' } - ) + decode_jwt_for_issuer('gitlab-workhorse', encoded_message) end def secret_path diff --git a/lib/peek/views/active_record.rb b/lib/peek/views/active_record.rb index bbc9f11e90f..1bb3ddb964a 100644 --- a/lib/peek/views/active_record.rb +++ b/lib/peek/views/active_record.rb @@ -5,15 +5,15 @@ module Peek class ActiveRecord < DetailedView DEFAULT_THRESHOLDS = { calls: 100, - duration: 3, - individual_call: 1 + duration: 3000, + individual_call: 1000 }.freeze THRESHOLDS = { production: { calls: 100, - duration: 15, - individual_call: 5 + duration: 15000, + individual_call: 5000 } }.freeze diff --git a/lib/peek/views/gitaly.rb b/lib/peek/views/gitaly.rb index f669feae254..7dc00b16cc0 100644 --- a/lib/peek/views/gitaly.rb +++ b/lib/peek/views/gitaly.rb @@ -5,15 +5,15 @@ module Peek class Gitaly < DetailedView DEFAULT_THRESHOLDS = { calls: 30, - duration: 1, - individual_call: 0.5 + duration: 1000, + individual_call: 500 }.freeze THRESHOLDS = { production: { calls: 30, - duration: 1, - individual_call: 0.5 + duration: 1000, + individual_call: 500 } }.freeze @@ -24,7 +24,7 @@ module Peek private def duration - ::Gitlab::GitalyClient.query_time + ::Gitlab::GitalyClient.query_time * 1000 end def calls diff --git a/lib/system_check/incoming_email/foreman_configured_check.rb b/lib/system_check/incoming_email/foreman_configured_check.rb deleted file mode 100644 index 944913087da..00000000000 --- a/lib/system_check/incoming_email/foreman_configured_check.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -module SystemCheck - module IncomingEmail - class ForemanConfiguredCheck < SystemCheck::BaseCheck - set_name 'Foreman configured correctly?' - - def check? - path = Rails.root.join('Procfile') - - File.exist?(path) && File.read(path) =~ /^mail_room:/ - end - - def show_error - try_fixing_it( - 'Enable mail_room in your Procfile.' - ) - for_more_information( - 'doc/administration/reply_by_email.md' - ) - fix_and_rerun - end - end - end -end diff --git a/lib/system_check/incoming_email_check.rb b/lib/system_check/incoming_email_check.rb index 155b6547595..e0e1147711c 100644 --- a/lib/system_check/incoming_email_check.rb +++ b/lib/system_check/incoming_email_check.rb @@ -14,8 +14,6 @@ module SystemCheck if Rails.env.production? checks << SystemCheck::IncomingEmail::InitdConfiguredCheck checks << SystemCheck::IncomingEmail::MailRoomRunningCheck - else - checks << SystemCheck::IncomingEmail::ForemanConfiguredCheck end SystemCheck.run('Reply by email', checks) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a1836646b1a..e15000b5184 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1960,7 +1960,10 @@ msgstr "" msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found." msgstr "" -msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly." +msgid "CICD|You must add a %{base_domain_link_start}base domain%{link_end} to your %{kubernetes_cluster_link_start}Kubernetes cluster%{link_end} in order for your deployment strategy to work." +msgstr "" + +msgid "CICD|You must add a %{kubernetes_cluster_link_start}Kubernetes cluster integration%{link_end} to this project with a domain in order for your deployment strategy to work correctly." msgstr "" msgid "CICD|group enabled" @@ -4981,6 +4984,9 @@ msgstr "" msgid "Failed to save preferences." msgstr "" +msgid "Failed to set due date because the date format is invalid." +msgstr "" + msgid "Failed to update branch!" msgstr "" @@ -5334,9 +5340,6 @@ msgstr "" msgid "GitLab.com import" msgstr "" -msgid "GitLab’s issue tracker" -msgstr "" - msgid "Gitaly" msgstr "" @@ -6586,9 +6589,6 @@ msgstr "" msgid "Learn more" msgstr "" -msgid "Learn more about %{issue_boards_url}, to keep track of issues in multiple lists, using labels, assignees, and milestones. If you’re missing something from issue boards, please create an issue on %{gitlab_issues_url}." -msgstr "" - msgid "Learn more about Auto DevOps" msgstr "" @@ -8188,6 +8188,9 @@ msgstr "" msgid "Pipeline|Coverage" msgstr "" +msgid "Pipeline|Detached merge request pipeline" +msgstr "" + msgid "Pipeline|Duration" msgstr "" @@ -8197,6 +8200,12 @@ msgstr "" msgid "Pipeline|Key" msgstr "" +msgid "Pipeline|Merge train pipeline" +msgstr "" + +msgid "Pipeline|Merged result pipeline" +msgstr "" + msgid "Pipeline|Pipeline" msgstr "" @@ -10876,6 +10885,9 @@ msgstr "" msgid "Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging." msgstr "" +msgid "Star labels to start sorting by priority" +msgstr "" + msgid "Star toggle failed. Try again later." msgstr "" @@ -11553,9 +11565,6 @@ msgstr "" msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "" -msgid "The tabs below will be removed in a future version" -msgstr "" - msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running." msgstr "" @@ -13814,9 +13823,6 @@ msgstr "" msgid "issue" msgstr "" -msgid "issue boards" -msgstr "" - msgid "it is stored externally" msgstr "" diff --git a/package.json b/package.json index 4244dc2d52b..23e611ae6cc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "scripts": { - "check-dependencies": "yarn check --integrity", + "check-dependencies": "scripts/frontend/check_dependencies.sh", "clean": "rm -rf public/assets tmp/cache/*-loader", "dev-server": "NODE_OPTIONS=\"--max-old-space-size=3584\" nodemon -w 'config/webpack.config.js' --exec 'webpack-dev-server --config config/webpack.config.js'", "eslint": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue .", @@ -16,6 +16,7 @@ "karma": "BABEL_ENV=${BABEL_ENV:=karma} karma start --single-run true config/karma.config.js", "karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js", "karma-start": "BABEL_ENV=karma karma start config/karma.config.js", + "markdownlint": "markdownlint -c .markdownlint.json 'doc/**/*.md'", "postinstall": "node ./scripts/frontend/postinstall.js", "prettier-staged": "node ./scripts/frontend/prettier.js check", "prettier-staged-save": "node ./scripts/frontend/prettier.js save", @@ -37,7 +38,7 @@ "@babel/plugin-syntax-import-meta": "^7.2.0", "@babel/preset-env": "^7.5.5", "@gitlab/svgs": "^1.71.0", - "@gitlab/ui": "5.20.2", + "@gitlab/ui": "5.21.0", "@gitlab/visual-review-tools": "1.0.1", "apollo-cache-inmemory": "^1.5.1", "apollo-client": "^2.5.1", @@ -185,6 +186,7 @@ "karma-mocha-reporter": "^2.2.5", "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^4.0.2", + "markdownlint-cli": "0.18.0", "md5": "^2.2.1", "node-sass": "^4.12.0", "nodemon": "^1.18.9", diff --git a/scripts/frontend/check_dependencies.sh b/scripts/frontend/check_dependencies.sh new file mode 100755 index 00000000000..64e7cf9da52 --- /dev/null +++ b/scripts/frontend/check_dependencies.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +if ! yarn check --integrity 2>&1 > /dev/null +then + echo + echo " $(tput setaf 1)yarn check --integrity$(tput sgr0) failed!" + echo " Your dependencies probably don't match the yarn.lock file." + echo " Please run $(tput setaf 2)yarn install$(tput sgr0) and try again." + echo + exit 1 +fi diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index 4935c1342a3..a9549171b54 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -36,7 +36,7 @@ function previous_deploy_failed() { return $status } -function delete() { +function delete_release() { if [ -z "$CI_ENVIRONMENT_SLUG" ]; then echoerr "No release given, aborting the delete!" return @@ -164,7 +164,7 @@ function create_application_secret() { function download_chart() { echoinfo "Downloading the GitLab chart..." true - curl -o gitlab.tar.bz2 "https://gitlab.com/charts/gitlab/-/archive/${GITLAB_HELM_CHART_REF}/gitlab-${GITLAB_HELM_CHART_REF}.tar.bz2" + curl --location -o gitlab.tar.bz2 "https://gitlab.com/gitlab-org/charts/gitlab/-/archive/${GITLAB_HELM_CHART_REF}/gitlab-${GITLAB_HELM_CHART_REF}.tar.bz2" tar -xjf gitlab.tar.bz2 cd "gitlab-${GITLAB_HELM_CHART_REF}" @@ -193,7 +193,8 @@ function deploy() { HELM_CMD=$(cat << EOF helm upgrade --install \ - --atomic \ + --force \ + --wait \ --timeout 900 \ --set releaseOverride="$CI_ENVIRONMENT_SLUG" \ --set global.appConfig.enableUsagePing=false \ diff --git a/spec/config/smime_signature_settings_spec.rb b/spec/config/smime_signature_settings_spec.rb index 4f0c227d866..38f96e9b330 100644 --- a/spec/config/smime_signature_settings_spec.rb +++ b/spec/config/smime_signature_settings_spec.rb @@ -1,4 +1,4 @@ -require 'fast_spec_helper' +require 'spec_helper' describe SmimeSignatureSettings do describe '.parse' do diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 45aebd1554c..17964c78e8d 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Projects::BlobController do include ProjectForksHelper diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb index 89857a9d21b..fafcd6927cd 100644 --- a/spec/controllers/sent_notifications_controller_spec.rb +++ b/spec/controllers/sent_notifications_controller_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe SentNotificationsController do let(:user) { create(:user) } diff --git a/spec/factories/milestone_releases.rb b/spec/factories/milestone_releases.rb new file mode 100644 index 00000000000..08e109480ab --- /dev/null +++ b/spec/factories/milestone_releases.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :milestone_release do + milestone + release + + before(:create, :build) do |mr| + project = create(:project) + mr.milestone.project = project + mr.release.project = project + end + end +end diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb index ca8874a24f9..4c3c0904a06 100644 --- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb +++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Admin disables Git access protocol', :js do include StubENV diff --git a/spec/features/admin/admin_disables_two_factor_spec.rb b/spec/features/admin/admin_disables_two_factor_spec.rb index 6e6a4964541..7227141168e 100644 --- a/spec/features/admin/admin_disables_two_factor_spec.rb +++ b/spec/features/admin/admin_disables_two_factor_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Admin disables 2FA for a user' do it 'successfully', :js do diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb index 8c1ec183286..3e8197588ed 100644 --- a/spec/features/admin/admin_uses_repository_checks_spec.rb +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Admin uses repository checks' do include StubENV diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb index 6e477a93293..0412dc2b69c 100644 --- a/spec/features/boards/add_issues_modal_spec.rb +++ b/spec/features/boards/add_issues_modal_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Issue Boards add issue modal', :js do let(:project) { create(:project, :public) } diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 902ecdcd3e8..57dc9de62fb 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Issue Boards', :js do include DragTo diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb index c8ea202169c..62abd914fcb 100644 --- a/spec/features/boards/issue_ordering_spec.rb +++ b/spec/features/boards/issue_ordering_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Issue Boards', :js do include DragTo diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb index 5f3a2c409ed..6074c559701 100644 --- a/spec/features/boards/keyboard_shortcut_spec.rb +++ b/spec/features/boards/keyboard_shortcut_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Issue Boards shortcut', :js do context 'issues are enabled' do diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb index 93de2750466..70bc067f79d 100644 --- a/spec/features/boards/modal_filter_spec.rb +++ b/spec/features/boards/modal_filter_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Issue Boards add issue modal filtering', :js do let(:project) { create(:project, :public) } diff --git a/spec/features/boards/multiple_boards_spec.rb b/spec/features/boards/multiple_boards_spec.rb index 9a2b7a80498..2389707be9c 100644 --- a/spec/features/boards/multiple_boards_spec.rb +++ b/spec/features/boards/multiple_boards_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Multiple Issue Boards', :js do set(:user) { create(:user) } diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index abbec0ea810..730887370dd 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Issue Boards new issue', :js do let(:project) { create(:project, :public) } @@ -127,4 +127,44 @@ describe 'Issue Boards new issue', :js do end end end + + context 'group boards' do + set(:group) { create(:group, :public) } + set(:project) { create(:project, namespace: group) } + set(:group_board) { create(:board, group: group) } + set(:list) { create(:list, board: group_board, position: 0) } + + context 'for unauthorized users' do + before do + sign_in(user) + visit group_board_path(group, group_board) + wait_for_requests + end + + it 'displays new issue button in open list' do + expect(first('.board')).to have_selector('.issue-count-badge-add-button', count: 1) + end + + it 'does not display new issue button in label list' do + page.within('.board.is-draggable') do + expect(page).not_to have_selector('.issue-count-badge-add-button') + end + end + end + + context 'for authorized users' do + it 'display new issue button in label list' do + project = create(:project, namespace: group) + project.add_reporter(user) + + sign_in(user) + visit group_board_path(group, group_board) + wait_for_requests + + page.within('.board.is-draggable') do + expect(page).to have_selector('.issue-count-badge-add-button') + end + end + end + end end diff --git a/spec/features/boards/reload_boards_on_browser_back_spec.rb b/spec/features/boards/reload_boards_on_browser_back_spec.rb index 752c8c1052d..6528b8f58bb 100644 --- a/spec/features/boards/reload_boards_on_browser_back_spec.rb +++ b/spec/features/boards/reload_boards_on_browser_back_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Ensure Boards do not show stale data on browser back', :js do let(:project) {create(:project, :public)} diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index 7ee3a839293..8497eaf102f 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Issue Boards', :js do include BoardHelpers diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb index 264e5e85505..4384a1a9379 100644 --- a/spec/features/boards/sub_group_project_spec.rb +++ b/spec/features/boards/sub_group_project_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Sub-group project issue boards', :js do let(:group) { create(:group) } diff --git a/spec/features/commits/user_uses_quick_actions_spec.rb b/spec/features/commits/user_uses_quick_actions_spec.rb index 4b7e7465df1..70ea920b7ec 100644 --- a/spec/features/commits/user_uses_quick_actions_spec.rb +++ b/spec/features/commits/user_uses_quick_actions_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Commit > User uses quick actions', :js do include Spec::Support::Helpers::Features::NotesHelpers diff --git a/spec/features/dashboard/todos/target_state_spec.rb b/spec/features/dashboard/todos/target_state_spec.rb index 931fc16a5eb..0ea1f43e34b 100644 --- a/spec/features/dashboard/todos/target_state_spec.rb +++ b/spec/features/dashboard/todos/target_state_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Dashboard > Todo target states' do let(:user) { create(:user) } diff --git a/spec/features/groups/board_sidebar_spec.rb b/spec/features/groups/board_sidebar_spec.rb index 9f597efa7b7..ed874141ef4 100644 --- a/spec/features/groups/board_sidebar_spec.rb +++ b/spec/features/groups/board_sidebar_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Group Issue Boards', :js do include BoardHelpers diff --git a/spec/features/groups/board_spec.rb b/spec/features/groups/board_spec.rb index ca33dbb7a33..f95e2e91cd7 100644 --- a/spec/features/groups/board_spec.rb +++ b/spec/features/groups/board_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Group Boards' do let(:group) { create(:group) } diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb index 7f0155b63e0..17738905e8d 100644 --- a/spec/features/groups/milestone_spec.rb +++ b/spec/features/groups/milestone_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Group milestones' do let(:group) { create(:group) } diff --git a/spec/features/groups/user_browse_projects_group_page_spec.rb b/spec/features/groups/user_browse_projects_group_page_spec.rb index 075bc1128ca..68221e3fefe 100644 --- a/spec/features/groups/user_browse_projects_group_page_spec.rb +++ b/spec/features/groups/user_browse_projects_group_page_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'User browse group projects page' do let(:user) { create :user } diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb index bcd2b90d3bb..88a7aa51326 100644 --- a/spec/features/help_pages_spec.rb +++ b/spec/features/help_pages_spec.rb @@ -58,7 +58,7 @@ describe 'Help Pages' do before do stub_application_setting(version_check_enabled: true) - allow(Rails.env).to receive(:production?).and_return(true) + stub_rails_env('production') allow(VersionCheck).to receive(:url).and_return('/version-check-url') sign_in(create(:user)) diff --git a/spec/features/instance_statistics/cohorts_spec.rb b/spec/features/instance_statistics/cohorts_spec.rb index 61294ec9af2..3940e8fa389 100644 --- a/spec/features/instance_statistics/cohorts_spec.rb +++ b/spec/features/instance_statistics/cohorts_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Cohorts page' do before do diff --git a/spec/features/instance_statistics/instance_statistics_spec.rb b/spec/features/instance_statistics/instance_statistics_spec.rb index 40d0f1db207..0fb78c6eef8 100644 --- a/spec/features/instance_statistics/instance_statistics_spec.rb +++ b/spec/features/instance_statistics/instance_statistics_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Cohorts page', :js do before do diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb index dc68e633e27..30c516459c5 100644 --- a/spec/features/issuables/issuable_list_spec.rb +++ b/spec/features/issuables/issuable_list_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'issuable list' do let(:project) { create(:project) } diff --git a/spec/features/issuables/markdown_references/internal_references_spec.rb b/spec/features/issuables/markdown_references/internal_references_spec.rb index e84629dc17f..f3b534bca49 100644 --- a/spec/features/issuables/markdown_references/internal_references_spec.rb +++ b/spec/features/issuables/markdown_references/internal_references_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "rails_helper" +require 'spec_helper' describe "Internal references", :js do include Spec::Support::Helpers::Features::NotesHelpers diff --git a/spec/features/issuables/markdown_references/jira_spec.rb b/spec/features/issuables/markdown_references/jira_spec.rb index aecdeffe4a4..8085918f533 100644 --- a/spec/features/issuables/markdown_references/jira_spec.rb +++ b/spec/features/issuables/markdown_references/jira_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "rails_helper" +require 'spec_helper' describe "Jira", :js do let(:user) { create(:user) } diff --git a/spec/features/issuables/user_sees_sidebar_spec.rb b/spec/features/issuables/user_sees_sidebar_spec.rb index d1e10c70414..52040eb8cbb 100644 --- a/spec/features/issuables/user_sees_sidebar_spec.rb +++ b/spec/features/issuables/user_sees_sidebar_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Issue Sidebar on Mobile' do include MobileHelpers diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb index e8b828bd264..57d04f0bf40 100644 --- a/spec/features/issues/bulk_assignment_labels_spec.rb +++ b/spec/features/issues/bulk_assignment_labels_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Issues > Labels bulk assignment' do let(:user) { create(:user) } diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb index f2e482faf5f..7d3df711555 100644 --- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Resolving all open threads in a merge request from an issue', :js do let(:user) { create(:user) } diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb index 08c98358544..8f4083533b3 100644 --- a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Resolve an open thread in a merge request by creating an issue', :js do let(:user) { create(:user) } diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb index 2ca551cec77..e1177bedd2d 100644 --- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Dropdown assignee', :js do include FilteredSearchHelpers diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb index b2905fc1dde..bd22eb1056b 100644 --- a/spec/features/issues/filtered_search/dropdown_author_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Dropdown author', :js do include FilteredSearchHelpers diff --git a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb index 45344aeb8f5..7ec3d215fb1 100644 --- a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Dropdown emoji', :js do include FilteredSearchHelpers diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb index 34313553b34..1c56902a27d 100644 --- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Dropdown hint', :js do include FilteredSearchHelpers diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index 8429fe44c43..9a782950bf6 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Dropdown milestone', :js do include FilteredSearchHelpers diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb index 0fe03160e7a..e97314e02e6 100644 --- a/spec/features/issues/filtered_search/search_bar_spec.rb +++ b/spec/features/issues/filtered_search/search_bar_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Search bar', :js do include FilteredSearchHelpers diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb index b20add1ea33..f8035ef4b85 100644 --- a/spec/features/issues/filtered_search/visual_tokens_spec.rb +++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Visual tokens', :js do include FilteredSearchHelpers diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index e840ac79916..8d7b6be5ea2 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'New/edit issue', :js do include ActionView::Helpers::JavaScriptHelper diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 566441dc6f6..cc834df367b 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'GFM autocomplete', :js do let(:issue_xss_title) { 'This will execute alert<img src=x onerror=alert(2)<img src=x onerror=alert(1)>' } diff --git a/spec/features/issues/group_label_sidebar_spec.rb b/spec/features/issues/group_label_sidebar_spec.rb index 746c55b6a26..fe6d95e1039 100644 --- a/spec/features/issues/group_label_sidebar_spec.rb +++ b/spec/features/issues/group_label_sidebar_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Group label on issue' do it 'renders link to the project issues page' do diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb index 8f2e5b237ea..a1b53718577 100644 --- a/spec/features/issues/issue_detail_spec.rb +++ b/spec/features/issues/issue_detail_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Issue Detail', :js do let(:user) { create(:user) } diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index b88d1bbeae5..9baba80bf06 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Issue Sidebar' do include MobileHelpers diff --git a/spec/features/issues/keyboard_shortcut_spec.rb b/spec/features/issues/keyboard_shortcut_spec.rb index e1325e63e6b..c5d53cd1cd0 100644 --- a/spec/features/issues/keyboard_shortcut_spec.rb +++ b/spec/features/issues/keyboard_shortcut_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Issues shortcut', :js do context 'New Issue shortcut' do diff --git a/spec/features/issues/markdown_toolbar_spec.rb b/spec/features/issues/markdown_toolbar_spec.rb index b80b47a4c99..d174fdcb25e 100644 --- a/spec/features/issues/markdown_toolbar_spec.rb +++ b/spec/features/issues/markdown_toolbar_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Issue markdown toolbar', :js do let(:project) { create(:project, :public) } diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index 9e0be7d8935..1122504248f 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'issue move to another project' do let(:user) { create(:user) } diff --git a/spec/features/issues/resource_label_events_spec.rb b/spec/features/issues/resource_label_events_spec.rb index 3d380c183ec..b367bbe2c99 100644 --- a/spec/features/issues/resource_label_events_spec.rb +++ b/spec/features/issues/resource_label_events_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'List issue resource label events', :js do let(:user) { create(:user) } diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb index 85c72c42f45..0d009f47fff 100644 --- a/spec/features/issues/spam_issues_spec.rb +++ b/spec/features/issues/spam_issues_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'New issue', :js do include StubENV diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb index 9282c31751a..4bb96ad069c 100644 --- a/spec/features/issues/todo_spec.rb +++ b/spec/features/issues/todo_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Manually create a todo item from issue', :js do let!(:project) { create(:project) } diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb index 0e27d0231d0..45a0b1932a2 100644 --- a/spec/features/issues/update_issues_spec.rb +++ b/spec/features/issues/update_issues_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Multiple issue updating from issues#index', :js do let!(:project) { create(:project) } diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb index 878a73718d7..be31c45b373 100644 --- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb +++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'User creates branch and merge request on issue page', :js do let(:membership_level) { :developer } diff --git a/spec/features/issues/user_creates_confidential_merge_request_spec.rb b/spec/features/issues/user_creates_confidential_merge_request_spec.rb index 4a32b7e2b73..24089bdeb81 100644 --- a/spec/features/issues/user_creates_confidential_merge_request_spec.rb +++ b/spec/features/issues/user_creates_confidential_merge_request_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'User creates confidential merge request on issue page', :js do include ProjectForksHelper diff --git a/spec/features/issues/user_sees_breadcrumb_links_spec.rb b/spec/features/issues/user_sees_breadcrumb_links_spec.rb index db8d3c5dd53..f31d730c337 100644 --- a/spec/features/issues/user_sees_breadcrumb_links_spec.rb +++ b/spec/features/issues/user_sees_breadcrumb_links_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'New issue breadcrumb' do let(:project) { create(:project) } diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb index 8fec92031a9..26979e943d0 100644 --- a/spec/features/issues/user_uses_quick_actions_spec.rb +++ b/spec/features/issues/user_uses_quick_actions_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' # These are written as feature specs because they cover more specific test scenarios # than the ones described on spec/services/notes/create_service_spec.rb for quick actions, diff --git a/spec/features/merge_request/user_assigns_themselves_spec.rb b/spec/features/merge_request/user_assigns_themselves_spec.rb index d7918a9e9d7..549d6e50337 100644 --- a/spec/features/merge_request/user_assigns_themselves_spec.rb +++ b/spec/features/merge_request/user_assigns_themselves_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User assigns themselves' do let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_request/user_awards_emoji_spec.rb b/spec/features/merge_request/user_awards_emoji_spec.rb index 5e9b232760b..8aa90107251 100644 --- a/spec/features/merge_request/user_awards_emoji_spec.rb +++ b/spec/features/merge_request/user_awards_emoji_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User awards emoji', :js do let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_request/user_creates_mr_spec.rb b/spec/features/merge_request/user_creates_mr_spec.rb index 267097bd466..665bc352c0f 100644 --- a/spec/features/merge_request/user_creates_mr_spec.rb +++ b/spec/features/merge_request/user_creates_mr_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User creates MR' do include ProjectForksHelper diff --git a/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb index 3213efcb60b..895cbb8f02b 100644 --- a/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb +++ b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request < User customizes merge commit message', :js do let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_request/user_locks_discussion_spec.rb b/spec/features/merge_request/user_locks_discussion_spec.rb index 365e9b60d90..0eaaf32dc31 100644 --- a/spec/features/merge_request/user_locks_discussion_spec.rb +++ b/spec/features/merge_request/user_locks_discussion_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User locks discussion', :js do let(:user) { create(:user) } diff --git a/spec/features/merge_request/user_merges_immediately_spec.rb b/spec/features/merge_request/user_merges_immediately_spec.rb index 2f01971c2e9..1188d3b2ceb 100644 --- a/spec/features/merge_request/user_merges_immediately_spec.rb +++ b/spec/features/merge_request/user_merges_immediately_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge requests > User merges immediately', :js do let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb index 3f9c27bbf9d..4afbf30ece4 100644 --- a/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb +++ b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User merges only if pipeline succeeds', :js do let(:merge_request) { create(:merge_request_with_diffs) } diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb index 93938ac221a..ffc12ffdbaf 100644 --- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User merges when pipeline succeeds', :js do let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_request/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb index abae6ffbd71..8b16760606c 100644 --- a/spec/features/merge_request/user_posts_diff_notes_spec.rb +++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User posts diff notes', :js do include MergeRequestDiffHelpers diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb index 1fad9eac1bd..435b3cd2555 100644 --- a/spec/features/merge_request/user_posts_notes_spec.rb +++ b/spec/features/merge_request/user_posts_notes_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User posts notes', :js do include NoteInteractionHelpers diff --git a/spec/features/merge_request/user_resolves_conflicts_spec.rb b/spec/features/merge_request/user_resolves_conflicts_spec.rb index fb089a9ffcb..dd1ec17ae51 100644 --- a/spec/features/merge_request/user_resolves_conflicts_spec.rb +++ b/spec/features/merge_request/user_resolves_conflicts_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User resolves conflicts', :js do let(:project) { create(:project, :repository) } diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb index b8e4852a748..8b41ef86791 100644 --- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb +++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User resolves diff notes and threads', :js do let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb index 23b37c218e1..48c3ed7178d 100644 --- a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb +++ b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User scrolls to note on load', :js do let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb index fae81758086..baef831c40e 100644 --- a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb +++ b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees avatars on diff notes', :js do include NoteInteractionHelpers diff --git a/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb b/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb index e1f0ddc4c6a..592ad3aae9b 100644 --- a/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb +++ b/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'New merge request breadcrumb' do let(:project) { create(:project, :repository) } diff --git a/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb index 93ae408a173..f54161fbaec 100644 --- a/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb +++ b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees check out branch modal', :js do let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb b/spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb index 8a2614c53af..1d62f7f0702 100644 --- a/spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb +++ b/spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User cherry-picks', :js do let(:group) { create(:group) } diff --git a/spec/features/merge_request/user_sees_closing_issues_message_spec.rb b/spec/features/merge_request/user_sees_closing_issues_message_spec.rb index 52163571175..f77ea82649c 100644 --- a/spec/features/merge_request/user_sees_closing_issues_message_spec.rb +++ b/spec/features/merge_request/user_sees_closing_issues_message_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees closing issues message', :js do let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb b/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb index f46921af1cd..224261dec00 100644 --- a/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb +++ b/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees deleted target branch', :js do let(:merge_request) { create(:merge_request) } diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb index 14fbfc2fd3f..87fb3f5b3e7 100644 --- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb +++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees deployment widget', :js do describe 'when merge request has associated environments' do diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb index 32429bac5f9..8eeed7b0843 100644 --- a/spec/features/merge_request/user_sees_diff_spec.rb +++ b/spec/features/merge_request/user_sees_diff_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees diff', :js do include ProjectForksHelper diff --git a/spec/features/merge_request/user_sees_discussions_spec.rb b/spec/features/merge_request/user_sees_discussions_spec.rb index a11baf1dca3..48f5814322a 100644 --- a/spec/features/merge_request/user_sees_discussions_spec.rb +++ b/spec/features/merge_request/user_sees_discussions_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees threads', :js do let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_request/user_sees_empty_state_spec.rb b/spec/features/merge_request/user_sees_empty_state_spec.rb index f1ad5c2dffc..88eba976d62 100644 --- a/spec/features/merge_request/user_sees_empty_state_spec.rb +++ b/spec/features/merge_request/user_sees_empty_state_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees empty state' do let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb b/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb index 53cafb60dfb..4cc129e5d5f 100644 --- a/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb +++ b/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees merge button depending on unresolved threads', :js do let(:project) { create(:project, :repository) } diff --git a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb index 28f88718ec1..dd5662d83f2 100644 --- a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb +++ b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees pipelines triggered by merge request', :js do include ProjectForksHelper diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb index b5d0240e9de..3f2a676462b 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees merge widget', :js do include ProjectForksHelper diff --git a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb index eb89d616b14..4d801c6ff11 100644 --- a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb +++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request < User sees mini pipeline graph', :js do let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb b/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb index 80ee35f2a91..b4fb844b943 100644 --- a/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb +++ b/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees MR from deleted forked project', :js do include ProjectForksHelper diff --git a/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb index d9dc32d0594..db0d632cdf2 100644 --- a/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb +++ b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' # This test serves as a regression test for a bug that caused an error # message to be shown by JavaScript when the source branch was deleted. diff --git a/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb b/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb index 195592531f2..0391794649c 100644 --- a/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb +++ b/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees notes from forked project', :js do include ProjectForksHelper diff --git a/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb b/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb index 68c9e0b123d..3e15a9c136b 100644 --- a/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb +++ b/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees pipelines from forked project', :js do include ProjectForksHelper diff --git a/spec/features/merge_request/user_sees_pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb index e057f59e00c..f04317a59ee 100644 --- a/spec/features/merge_request/user_sees_pipelines_spec.rb +++ b/spec/features/merge_request/user_sees_pipelines_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees pipelines', :js do describe 'pipeline tab' do diff --git a/spec/features/merge_request/user_sees_system_notes_spec.rb b/spec/features/merge_request/user_sees_system_notes_spec.rb index 26cdd5ba21c..0482458d5ac 100644 --- a/spec/features/merge_request/user_sees_system_notes_spec.rb +++ b/spec/features/merge_request/user_sees_system_notes_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees system notes', :js do let(:public_project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb index dae5845adec..62abcff7bda 100644 --- a/spec/features/merge_request/user_sees_versions_spec.rb +++ b/spec/features/merge_request/user_sees_versions_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees versions', :js do let(:merge_request) do diff --git a/spec/features/merge_request/user_sees_wip_help_message_spec.rb b/spec/features/merge_request/user_sees_wip_help_message_spec.rb index 46209237faf..1179303171c 100644 --- a/spec/features/merge_request/user_sees_wip_help_message_spec.rb +++ b/spec/features/merge_request/user_sees_wip_help_message_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees WIP help message' do let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb index a1f027cbb1d..6262f1ce055 100644 --- a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb +++ b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User selects branches for new MR', :js do let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb index 61b9904dde5..4db067a4e41 100644 --- a/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb +++ b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User toggles whitespace changes', :js do let(:merge_request) { create(:merge_request) } diff --git a/spec/features/merge_request/user_uses_quick_actions_spec.rb b/spec/features/merge_request/user_uses_quick_actions_spec.rb index 3948c0c5c89..318f8812263 100644 --- a/spec/features/merge_request/user_uses_quick_actions_spec.rb +++ b/spec/features/merge_request/user_uses_quick_actions_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' # These are written as feature specs because they cover more specific test scenarios # than the ones described on spec/services/notes/create_service_spec.rb for quick actions, diff --git a/spec/features/merge_requests/filters_generic_behavior_spec.rb b/spec/features/merge_requests/filters_generic_behavior_spec.rb index 404e591cf4b..58aad1b7e91 100644 --- a/spec/features/merge_requests/filters_generic_behavior_spec.rb +++ b/spec/features/merge_requests/filters_generic_behavior_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge Requests > Filters generic behavior', :js do include FilteredSearchHelpers diff --git a/spec/features/merge_requests/user_filters_by_assignees_spec.rb b/spec/features/merge_requests/user_filters_by_assignees_spec.rb index b896b6392f1..00bd8455ae1 100644 --- a/spec/features/merge_requests/user_filters_by_assignees_spec.rb +++ b/spec/features/merge_requests/user_filters_by_assignees_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge Requests > User filters by assignees', :js do include FilteredSearchHelpers diff --git a/spec/features/merge_requests/user_filters_by_labels_spec.rb b/spec/features/merge_requests/user_filters_by_labels_spec.rb index 9fb149ca58d..fd2b4b23f96 100644 --- a/spec/features/merge_requests/user_filters_by_labels_spec.rb +++ b/spec/features/merge_requests/user_filters_by_labels_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge Requests > User filters by labels', :js do include FilteredSearchHelpers diff --git a/spec/features/merge_requests/user_filters_by_milestones_spec.rb b/spec/features/merge_requests/user_filters_by_milestones_spec.rb index 2fb3c86ea94..e0ee69d7a5b 100644 --- a/spec/features/merge_requests/user_filters_by_milestones_spec.rb +++ b/spec/features/merge_requests/user_filters_by_milestones_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge Requests > User filters by milestones', :js do include FilteredSearchHelpers diff --git a/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb index 3dd0f93ddfa..bc6e2ac5132 100644 --- a/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb +++ b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge requests > User filters by multiple criteria', :js do include FilteredSearchHelpers diff --git a/spec/features/merge_requests/user_filters_by_target_branch_spec.rb b/spec/features/merge_requests/user_filters_by_target_branch_spec.rb index b5969f656b0..0d03c5eae31 100644 --- a/spec/features/merge_requests/user_filters_by_target_branch_spec.rb +++ b/spec/features/merge_requests/user_filters_by_target_branch_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge Requests > User filters by target branch', :js do include FilteredSearchHelpers diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb index 69d4650d1ac..f145bdd044b 100644 --- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb +++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge requests > User lists merge requests' do include MergeRequestHelpers diff --git a/spec/features/merge_requests/user_mass_updates_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb index 48c92a87c63..4d3461bf1ae 100644 --- a/spec/features/merge_requests/user_mass_updates_spec.rb +++ b/spec/features/merge_requests/user_mass_updates_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge requests > User mass updates', :js do let(:project) { create(:project, :repository) } diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb index 6d5acc894b1..bfff33f3956 100644 --- a/spec/features/milestone_spec.rb +++ b/spec/features/milestone_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Milestone' do let(:group) { create(:group, :public) } diff --git a/spec/features/milestones/user_creates_milestone_spec.rb b/spec/features/milestones/user_creates_milestone_spec.rb index 73f4f187501..5c93ddcf6f8 100644 --- a/spec/features/milestones/user_creates_milestone_spec.rb +++ b/spec/features/milestones/user_creates_milestone_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "rails_helper" +require 'spec_helper' describe "User creates milestone", :js do set(:user) { create(:user) } diff --git a/spec/features/milestones/user_deletes_milestone_spec.rb b/spec/features/milestones/user_deletes_milestone_spec.rb index 5666c1dd507..7c1d88f7798 100644 --- a/spec/features/milestones/user_deletes_milestone_spec.rb +++ b/spec/features/milestones/user_deletes_milestone_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "rails_helper" +require 'spec_helper' describe "User deletes milestone", :js do let(:user) { create(:user) } diff --git a/spec/features/milestones/user_edits_milestone_spec.rb b/spec/features/milestones/user_edits_milestone_spec.rb index f8e96eac3ea..b41b8f3282f 100644 --- a/spec/features/milestones/user_edits_milestone_spec.rb +++ b/spec/features/milestones/user_edits_milestone_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "rails_helper" +require 'spec_helper' describe "User edits milestone", :js do set(:user) { create(:user) } diff --git a/spec/features/milestones/user_promotes_milestone_spec.rb b/spec/features/milestones/user_promotes_milestone_spec.rb index acceb5cad62..7678b6cbfa5 100644 --- a/spec/features/milestones/user_promotes_milestone_spec.rb +++ b/spec/features/milestones/user_promotes_milestone_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'User promotes milestone' do set(:group) { create(:group) } diff --git a/spec/features/milestones/user_sees_breadcrumb_links_spec.rb b/spec/features/milestones/user_sees_breadcrumb_links_spec.rb index f58242759cc..92445735328 100644 --- a/spec/features/milestones/user_sees_breadcrumb_links_spec.rb +++ b/spec/features/milestones/user_sees_breadcrumb_links_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'New project milestone breadcrumb' do let(:project) { create(:project) } diff --git a/spec/features/milestones/user_views_milestone_spec.rb b/spec/features/milestones/user_views_milestone_spec.rb index aa0cdf66b75..71abb195ad1 100644 --- a/spec/features/milestones/user_views_milestone_spec.rb +++ b/spec/features/milestones/user_views_milestone_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "rails_helper" +require 'spec_helper' describe "User views milestone" do set(:user) { create(:user) } diff --git a/spec/features/milestones/user_views_milestones_spec.rb b/spec/features/milestones/user_views_milestones_spec.rb index 6868791f584..0b51ca12997 100644 --- a/spec/features/milestones/user_views_milestones_spec.rb +++ b/spec/features/milestones/user_views_milestones_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "rails_helper" +require 'spec_helper' describe "User views milestones" do set(:user) { create(:user) } diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb index 741e41adbf1..c6db15065f2 100644 --- a/spec/features/profiles/account_spec.rb +++ b/spec/features/profiles/account_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Profile > Account', :js do let(:user) { create(:user, username: 'foo') } diff --git a/spec/features/profiles/active_sessions_spec.rb b/spec/features/profiles/active_sessions_spec.rb index 709cca7d178..a5c2d15f598 100644 --- a/spec/features/profiles/active_sessions_spec.rb +++ b/spec/features/profiles/active_sessions_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do let(:user) do diff --git a/spec/features/profiles/chat_names_spec.rb b/spec/features/profiles/chat_names_spec.rb index 0219dacbc38..4c447faf77e 100644 --- a/spec/features/profiles/chat_names_spec.rb +++ b/spec/features/profiles/chat_names_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Profile > Chat' do let(:user) { create(:user) } diff --git a/spec/features/profiles/emails_spec.rb b/spec/features/profiles/emails_spec.rb index 40e2988730b..4d2cd0f8b56 100644 --- a/spec/features/profiles/emails_spec.rb +++ b/spec/features/profiles/emails_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Profile > Emails' do let(:user) { create(:user) } diff --git a/spec/features/profiles/gpg_keys_spec.rb b/spec/features/profiles/gpg_keys_spec.rb index 4237f037c27..07e87f36c65 100644 --- a/spec/features/profiles/gpg_keys_spec.rb +++ b/spec/features/profiles/gpg_keys_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Profile > GPG Keys' do let(:user) { create(:user, email: GpgHelpers::User2.emails.first) } diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb index c1b142c4e12..3fc0fd76d2e 100644 --- a/spec/features/profiles/keys_spec.rb +++ b/spec/features/profiles/keys_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Profile > SSH Keys' do let(:user) { create(:user) } diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb index 1199a3bd226..3d9fcfe0f62 100644 --- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb +++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Mini Pipeline Graph in Commit View', :js do let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb b/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb index 35c84204910..68a924e4fad 100644 --- a/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb +++ b/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'New project label breadcrumb' do let(:project) { create(:project) } diff --git a/spec/features/projects/settings/lfs_settings_spec.rb b/spec/features/projects/settings/lfs_settings_spec.rb index 56606df5a78..5fa3b9bba55 100644 --- a/spec/features/projects/settings/lfs_settings_spec.rb +++ b/spec/features/projects/settings/lfs_settings_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Projects > Settings > LFS settings' do let(:project) { create(:project) } diff --git a/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb b/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb index 9c77a08718e..3e9bfed1e47 100644 --- a/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb +++ b/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Repository Settings > User sees revoke deploy token modal', :js do let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb index 6f176c260a2..70dc6c966ba 100644 --- a/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb +++ b/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Projects > Show > Developer views empty project instructions' do let(:project) { create(:project, :empty_repo) } diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb index 430883fdf29..891b780a100 100644 --- a/spec/features/projects/snippets/create_snippet_spec.rb +++ b/spec/features/projects/snippets/create_snippet_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Projects > Snippets > Create Snippet', :js do include DropzoneHelper diff --git a/spec/features/projects/tags/user_edits_tags_spec.rb b/spec/features/projects/tags/user_edits_tags_spec.rb index ebb2844d17f..63f97eeb4e0 100644 --- a/spec/features/projects/tags/user_edits_tags_spec.rb +++ b/spec/features/projects/tags/user_edits_tags_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Project > Tags', :js do include DropzoneHelper diff --git a/spec/features/read_only_spec.rb b/spec/features/read_only_spec.rb index 3af4b51b9b1..619d34ebed4 100644 --- a/spec/features/read_only_spec.rb +++ b/spec/features/read_only_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'read-only message' do set(:user) { create(:user) } diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb index f4f3872aa09..d6575ec9de1 100644 --- a/spec/features/security/group/internal_access_spec.rb +++ b/spec/features/security/group/internal_access_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Internal Group access' do include AccessMatchers diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb index 9cef8ef777c..2dc863a6e73 100644 --- a/spec/features/security/group/private_access_spec.rb +++ b/spec/features/security/group/private_access_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Private Group access' do include AccessMatchers diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb index bbe74f0dab0..4066a19fce2 100644 --- a/spec/features/security/group/public_access_spec.rb +++ b/spec/features/security/group/public_access_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Public Group access' do include AccessMatchers diff --git a/spec/features/snippets/explore_spec.rb b/spec/features/snippets/explore_spec.rb index b48a5691e96..57e91fea709 100644 --- a/spec/features/snippets/explore_spec.rb +++ b/spec/features/snippets/explore_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Explore Snippets' do let!(:public_snippet) { create(:personal_snippet, :public) } diff --git a/spec/features/snippets/internal_snippet_spec.rb b/spec/features/snippets/internal_snippet_spec.rb index 8454a347382..4ef3b0e5e7a 100644 --- a/spec/features/snippets/internal_snippet_spec.rb +++ b/spec/features/snippets/internal_snippet_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Internal Snippets', :js do let(:internal_snippet) { create(:personal_snippet, :internal) } diff --git a/spec/features/snippets/public_snippets_spec.rb b/spec/features/snippets/public_snippets_spec.rb index e32a9292e22..a0db00cfc67 100644 --- a/spec/features/snippets/public_snippets_spec.rb +++ b/spec/features/snippets/public_snippets_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Public Snippets', :js do it 'Unauthenticated user should see public snippets' do diff --git a/spec/features/snippets/search_snippets_spec.rb b/spec/features/snippets/search_snippets_spec.rb index bbdf544bd0c..dce790e5708 100644 --- a/spec/features/snippets/search_snippets_spec.rb +++ b/spec/features/snippets/search_snippets_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Search Snippets' do it 'User searches for snippets by title' do diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb index a4a5407d1f7..52ec5eddd5c 100644 --- a/spec/features/snippets/user_creates_snippet_spec.rb +++ b/spec/features/snippets/user_creates_snippet_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'User creates snippet', :js do include DropzoneHelper diff --git a/spec/features/snippets/user_deletes_snippet_spec.rb b/spec/features/snippets/user_deletes_snippet_spec.rb index 9773aca849a..217419a220a 100644 --- a/spec/features/snippets/user_deletes_snippet_spec.rb +++ b/spec/features/snippets/user_deletes_snippet_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'User deletes snippet' do let(:user) { create(:user) } diff --git a/spec/features/snippets/user_edits_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb index 5ff12c37aff..51d9baf44bc 100644 --- a/spec/features/snippets/user_edits_snippet_spec.rb +++ b/spec/features/snippets/user_edits_snippet_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'User edits snippet', :js do include DropzoneHelper diff --git a/spec/features/snippets/user_snippets_spec.rb b/spec/features/snippets/user_snippets_spec.rb index 4e9215db945..c6daa19d5e6 100644 --- a/spec/features/snippets/user_snippets_spec.rb +++ b/spec/features/snippets/user_snippets_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'User Snippets' do let(:author) { create(:user) } diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb index 40d864e0002..d9d9d7e4b04 100644 --- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'User uploads avatar to group' do it 'they see the new avatar' do diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb index 4c694365e3b..fc31d7aa3d1 100644 --- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'User uploads avatar to profile' do let!(:user) { create(:user) } diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb index 6bf8e8ea74f..30b5cf267ae 100644 --- a/spec/features/uploads/user_uploads_file_to_note_spec.rb +++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'User uploads file to note' do include DropzoneHelper diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb index b2036108d42..8b3f193f418 100644 --- a/spec/features/user_can_display_performance_bar_spec.rb +++ b/spec/features/user_can_display_performance_bar_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'User can display performance bar', :js do shared_examples 'performance bar cannot be displayed' do @@ -37,7 +37,7 @@ describe 'User can display performance bar', :js do shared_examples 'performance bar is enabled by default in development' do before do - allow(Rails.env).to receive(:development?).and_return(true) + stub_rails_env('development') end it 'shows the performance bar by default' do diff --git a/spec/features/user_sees_revert_modal_spec.rb b/spec/features/user_sees_revert_modal_spec.rb index 35828b5f086..24b4f8dd4aa 100644 --- a/spec/features/user_sees_revert_modal_spec.rb +++ b/spec/features/user_sees_revert_modal_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User sees revert modal', :js do let(:project) { create(:project, :public, :repository) } diff --git a/spec/fixtures/api/schemas/pipeline.json b/spec/fixtures/api/schemas/pipeline.json index b6e30c40f13..d9ffad8fbab 100644 --- a/spec/fixtures/api/schemas/pipeline.json +++ b/spec/fixtures/api/schemas/pipeline.json @@ -97,6 +97,10 @@ "id": "/properties/details/properties/finished_at", "type": "string" }, + "name": { + "id": "/properties/details/properties/name", + "type": "string" + }, "manual_actions": { "id": "/properties/details/properties/manual_actions", "items": {}, @@ -323,6 +327,10 @@ "id": "/properties/web_url", "type": "string" }, + "merge_request_event_type": { + "id": "/properties/merge_request_event_type", + "type": "string" + }, "user": { "id": "/properties/user", "properties": { diff --git a/spec/fixtures/api/schemas/public_api/v4/release.json b/spec/fixtures/api/schemas/public_api/v4/release.json index ec3fa59cdb1..078b1c0b982 100644 --- a/spec/fixtures/api/schemas/public_api/v4/release.json +++ b/spec/fixtures/api/schemas/public_api/v4/release.json @@ -15,6 +15,7 @@ "author": { "oneOf": [{ "type": "null" }, { "$ref": "user/basic.json" }] }, + "milestone": { "type": "string" }, "assets": { "required": ["count", "links", "sources"], "properties": { diff --git a/spec/frontend/branches/divergence_graph_spec.js b/spec/frontend/branches/divergence_graph_spec.js index 8283bc966e4..adf39a2216a 100644 --- a/spec/frontend/branches/divergence_graph_spec.js +++ b/spec/frontend/branches/divergence_graph_spec.js @@ -25,13 +25,25 @@ describe('Divergence graph', () => { mock.restore(); }); - it('calls axos get with list of branch names', () => + it('calls axios get with list of branch names', () => init('/-/diverging_counts').then(() => { expect(axios.get).toHaveBeenCalledWith('/-/diverging_counts', { params: { names: ['master', 'test/hello-world'] }, }); })); + describe('no branches listed', () => { + beforeEach(() => { + document.body.innerHTML = `<div></div>`; + }); + + it('avoids requesting diverging commit counts', () => { + expect(axios.get).not.toHaveBeenCalledWith('/-/diverging_counts'); + + init('/-/diverging_counts'); + }); + }); + it('creates Vue components', () => init('/-/diverging_counts').then(() => { expect(document.querySelector('[data-name="master"]').innerHTML).not.toEqual(''); diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js index c168bce7a4e..98077498998 100644 --- a/spec/frontend/clusters/stores/clusters_store_spec.js +++ b/spec/frontend/clusters/stores/clusters_store_spec.js @@ -51,6 +51,9 @@ describe('Clusters Store', () => { expect(store.state).toEqual({ helpPath: null, ingressHelpPath: null, + environmentsHelpPath: null, + clustersHelpPath: null, + deployBoardsHelpPath: null, status: mockResponseData.status, statusReason: mockResponseData.status_reason, rbac: false, @@ -148,6 +151,7 @@ describe('Clusters Store', () => { uninstallFailed: false, }, }, + environments: [], }); }); diff --git a/spec/frontend/jobs/store/utils_spec.js b/spec/frontend/jobs/store/utils_spec.js new file mode 100644 index 00000000000..9e81558f8c2 --- /dev/null +++ b/spec/frontend/jobs/store/utils_spec.js @@ -0,0 +1,60 @@ +import linesParser from '~/jobs/store/utils'; + +describe('linesParser', () => { + const mockData = [ + { + offset: 1001, + content: [{ text: ' on docker-auto-scale-com 8a6210b8' }], + }, + { + offset: 1002, + content: [ + { + text: + 'Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33', + }, + ], + sections: ['prepare-executor'], + section_header: true, + }, + { + offset: 1003, + content: [{ text: 'Starting service postgres:9.6.14 ...' }], + sections: ['prepare-executor'], + }, + { + offset: 1004, + content: [{ text: 'Pulling docker image postgres:9.6.14 ...', style: 'term-fg-l-green' }], + sections: ['prepare-executor'], + }, + ]; + + let result; + + beforeEach(() => { + result = linesParser(mockData); + }); + + describe('regular line', () => { + it('adds a lineNumber property with correct index', () => { + expect(result[0].lineNumber).toEqual(0); + expect(result[1].line.lineNumber).toEqual(1); + }); + }); + + describe('collpasible section', () => { + it('adds a `isClosed` property', () => { + expect(result[1].isClosed).toEqual(true); + }); + + it('adds a `isHeader` property', () => { + expect(result[1].isHeader).toEqual(true); + }); + + it('creates a lines array property with the content of the collpasible section', () => { + expect(result[1].lines.length).toEqual(2); + expect(result[1].lines[0].content).toEqual(mockData[2].content); + expect(result[1].lines[1].content).toEqual(mockData[3].content); + }); + }); +}); diff --git a/spec/frontend/mocks/ce/lib/utils/axios_utils.js b/spec/frontend/mocks/ce/lib/utils/axios_utils.js index b4065626b09..a3783b91f95 100644 --- a/spec/frontend/mocks/ce/lib/utils/axios_utils.js +++ b/spec/frontend/mocks/ce/lib/utils/axios_utils.js @@ -6,9 +6,10 @@ axios.isMock = true; axios.defaults.adapter = config => { const message = `Unexpected unmocked request: ${JSON.stringify(config, null, 2)}\n` + - 'Consider using the `axios-mock-adapter` in tests.'; + 'Consider using the `axios-mock-adapter` module in tests.'; const error = new Error(message); error.config = config; + global.fail(error); throw error; }; diff --git a/spec/frontend/mocks/mocks_helper_spec.js b/spec/frontend/mocks/mocks_helper_spec.js index b8bb02c2f43..82e88b712c0 100644 --- a/spec/frontend/mocks/mocks_helper_spec.js +++ b/spec/frontend/mocks/mocks_helper_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable global-require, promise/catch-or-return */ +/* eslint-disable global-require */ import path from 'path'; @@ -126,9 +126,8 @@ describe('mocks_helper.js', () => { it('survives jest.isolateModules()', done => { jest.isolateModules(() => { const axios2 = require('~/lib/utils/axios_utils').default; - expect(axios2.get('http://gitlab.com')) - .rejects.toThrow('Unexpected unmocked request') - .then(done); + expect(axios2.isMock).toBe(true); + done(); }); }); diff --git a/spec/frontend/mocks/node/jquery.js b/spec/frontend/mocks/node/jquery.js index 34a25772f67..5c82f65406e 100644 --- a/spec/frontend/mocks/node/jquery.js +++ b/spec/frontend/mocks/node/jquery.js @@ -4,9 +4,11 @@ const $ = jest.requireActual('jquery'); // Fail tests for unmocked requests $.ajax = () => { - throw new Error( + const err = new Error( 'Unexpected unmocked jQuery.ajax() call! Make sure to mock jQuery.ajax() in tests.', ); + global.fail(err); + throw err; }; // jquery is not an ES6 module diff --git a/spec/frontend/mocks_spec.js b/spec/frontend/mocks_spec.js index 2d2324120fd..a4a1fdea396 100644 --- a/spec/frontend/mocks_spec.js +++ b/spec/frontend/mocks_spec.js @@ -3,11 +3,22 @@ import axios from '~/lib/utils/axios_utils'; describe('Mock auto-injection', () => { describe('mocks', () => { - it('~/lib/utils/axios_utils', () => - expect(axios.get('http://gitlab.com')).rejects.toThrow('Unexpected unmocked request')); + let failMock; + beforeEach(() => { + failMock = jest.spyOn(global, 'fail').mockImplementation(); + }); + + it('~/lib/utils/axios_utils', done => { + expect(axios.get('http://gitlab.com')).rejects.toThrow('Unexpected unmocked request'); + setImmediate(() => { + expect(failMock).toHaveBeenCalledTimes(1); + done(); + }); + }); it('jQuery.ajax()', () => { expect($.ajax).toThrow('Unexpected unmocked'); + expect(failMock).toHaveBeenCalledTimes(1); }); }); }); diff --git a/spec/graphql/features/authorization_spec.rb b/spec/graphql/features/authorization_spec.rb index c427893f9cc..9a60ff3b78c 100644 --- a/spec/graphql/features/authorization_spec.rb +++ b/spec/graphql/features/authorization_spec.rb @@ -8,10 +8,10 @@ describe 'Gitlab::Graphql::Authorization' do let(:permission_single) { :foo } let(:permission_collection) { [:foo, :bar] } let(:test_object) { double(name: 'My name') } - let(:query_string) { '{ object() { name } }' } + let(:query_string) { '{ item() { name } }' } let(:result) { execute_query(query_type)['data'] } - subject { result['object'] } + subject { result['item'] } shared_examples 'authorization with a single permission' do it 'returns the protected field when user has permission' do @@ -54,7 +54,7 @@ describe 'Gitlab::Graphql::Authorization' do describe 'with a single permission' do let(:query_type) do query_factory do |query| - query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_single + query.field :item, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_single end end @@ -65,7 +65,7 @@ describe 'Gitlab::Graphql::Authorization' do let(:query_type) do permissions = permission_collection query_factory do |qt| - qt.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object } do + qt.field :item, type, null: true, resolve: ->(obj, args, ctx) { test_object } do authorize permissions end end @@ -78,7 +78,7 @@ describe 'Gitlab::Graphql::Authorization' do describe 'Field authorizations when field is a built in type' do let(:query_type) do query_factory do |query| - query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object } + query.field :item, type, null: true, resolve: ->(obj, args, ctx) { test_object } end end @@ -131,7 +131,7 @@ describe 'Gitlab::Graphql::Authorization' do describe 'Type authorizations' do let(:query_type) do query_factory do |query| - query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object } + query.field :item, type, null: true, resolve: ->(obj, args, ctx) { test_object } end end @@ -168,7 +168,7 @@ describe 'Gitlab::Graphql::Authorization' do let(:query_type) do query_factory do |query| - query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_2 + query.field :item, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_2 end end @@ -176,7 +176,7 @@ describe 'Gitlab::Graphql::Authorization' do end describe 'type authorizations when applied to a relay connection' do - let(:query_string) { '{ object() { edges { node { name } } } }' } + let(:query_string) { '{ item() { edges { node { name } } } }' } let(:second_test_object) { double(name: 'Second thing') } let(:type) do @@ -187,11 +187,11 @@ describe 'Gitlab::Graphql::Authorization' do let(:query_type) do query_factory do |query| - query.field :object, type.connection_type, null: true, resolve: ->(obj, args, ctx) { [test_object, second_test_object] } + query.field :item, type.connection_type, null: true, resolve: ->(obj, args, ctx) { [test_object, second_test_object] } end end - subject { result.dig('object', 'edges') } + subject { result.dig('item', 'edges') } it 'returns only the elements visible to the user' do permit(permission_single) @@ -207,13 +207,13 @@ describe 'Gitlab::Graphql::Authorization' do describe 'limiting connections with multiple objects' do let(:query_type) do query_factory do |query| - query.field :object, type.connection_type, null: true, resolve: ->(obj, args, ctx) do + query.field :item, type.connection_type, null: true, resolve: ->(obj, args, ctx) do [test_object, second_test_object] end end end - let(:query_string) { '{ object(first: 1) { edges { node { name } } } }' } + let(:query_string) { '{ item(first: 1) { edges { node { name } } } }' } it 'only checks permissions for the first object' do expect(Ability).to receive(:allowed?).with(user, permission_single, test_object) { true } @@ -233,11 +233,11 @@ describe 'Gitlab::Graphql::Authorization' do let(:query_type) do query_factory do |query| - query.field :object, [type], null: true, resolve: ->(obj, args, ctx) { [test_object] } + query.field :item, [type], null: true, resolve: ->(obj, args, ctx) { [test_object] } end end - subject { result['object'].first } + subject { result['item'].first } include_examples 'authorization with a single permission' end diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb index dec6b23d72a..0a27bbecfef 100644 --- a/spec/graphql/gitlab_schema_spec.rb +++ b/spec/graphql/gitlab_schema_spec.rb @@ -129,7 +129,7 @@ describe GitlabSchema do result = described_class.object_from_id(user.to_global_id.to_s) - expect(result.__sync).to eq(user) + expect(result.sync).to eq(user) end it 'batchloads the queries' do @@ -138,7 +138,7 @@ describe GitlabSchema do expect do [described_class.object_from_id(user1.to_global_id), - described_class.object_from_id(user2.to_global_id)].map(&:__sync) + described_class.object_from_id(user2.to_global_id)].map(&:sync) end.not_to exceed_query_limit(1) end end @@ -149,7 +149,7 @@ describe GitlabSchema do result = described_class.object_from_id(note.to_global_id) - expect(result.__sync).to eq(note) + expect(result.sync).to eq(note) end it 'batchloads the queries' do @@ -158,7 +158,7 @@ describe GitlabSchema do expect do [described_class.object_from_id(note1.to_global_id), - described_class.object_from_id(note2.to_global_id)].map(&:__sync) + described_class.object_from_id(note2.to_global_id)].map(&:sync) end.not_to exceed_query_limit(1) end end diff --git a/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb b/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb index 19f5a8907a2..aa0f5c55902 100644 --- a/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb +++ b/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb @@ -14,6 +14,6 @@ describe Mutations::ResolvesProject do project = create(:project) expect(Resolvers::ProjectResolver).to receive(:new).with(object: nil, context: context).and_call_original - expect(mutation.resolve_project(full_path: project.full_path)).to eq(project) + expect(mutation.resolve_project(full_path: project.full_path).sync).to eq(project) end end diff --git a/spec/graphql/resolvers/group_resolver_spec.rb b/spec/graphql/resolvers/group_resolver_spec.rb index 5eb9cd06d15..7dec9ac1aa5 100644 --- a/spec/graphql/resolvers/group_resolver_spec.rb +++ b/spec/graphql/resolvers/group_resolver_spec.rb @@ -12,7 +12,7 @@ describe Resolvers::GroupResolver do it 'batch-resolves groups by full path' do paths = [group1.full_path, group2.full_path] - result = batch(max_queries: 1) do + result = batch_sync(max_queries: 1) do paths.map { |path| resolve_group(path) } end @@ -20,7 +20,7 @@ describe Resolvers::GroupResolver do end it 'resolves an unknown full_path to nil' do - result = batch { resolve_group('unknown/project') } + result = batch_sync { resolve_group('unknown/project') } expect(result).to be_nil end diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index 798fe00de97..d122c081069 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -110,7 +110,7 @@ describe Resolvers::IssuesResolver do context "when passing a non existent, batch loaded project" do let(:project) do - BatchLoader.for("non-existent-path").batch do |_fake_paths, loader, _| + BatchLoader::GraphQL.for("non-existent-path").batch do |_fake_paths, loader, _| loader.call("non-existent-path", nil) end end diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb index ab3c426b2cd..97b8e5ed41c 100644 --- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb +++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb @@ -17,7 +17,7 @@ describe Resolvers::MergeRequestsResolver do describe '#resolve' do it 'batch-resolves by target project full path and individual IID' do - result = batch(max_queries: 2) do + result = batch_sync(max_queries: 2) do resolve_mr(project, iid: iid_1) + resolve_mr(project, iid: iid_2) end @@ -25,7 +25,7 @@ describe Resolvers::MergeRequestsResolver do end it 'batch-resolves by target project full path and IIDS' do - result = batch(max_queries: 2) do + result = batch_sync(max_queries: 2) do resolve_mr(project, iids: [iid_1, iid_2]) end @@ -33,7 +33,7 @@ describe Resolvers::MergeRequestsResolver do end it 'can batch-resolve merge requests from different projects' do - result = batch(max_queries: 3) do + result = batch_sync(max_queries: 3) do resolve_mr(project, iid: iid_1) + resolve_mr(project, iid: iid_2) + resolve_mr(other_project, iid: other_iid) @@ -43,13 +43,13 @@ describe Resolvers::MergeRequestsResolver do end it 'resolves an unknown iid to be empty' do - result = batch { resolve_mr(project, iid: -1) } + result = batch_sync { resolve_mr(project, iid: -1) } - expect(result).to be_empty + expect(result.compact).to be_empty end it 'resolves empty iids to be empty' do - result = batch { resolve_mr(project, iids: []) } + result = batch_sync { resolve_mr(project, iids: []) } expect(result).to be_empty end diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb index 47591445fc0..639cc69650b 100644 --- a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb +++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb @@ -46,7 +46,7 @@ describe Resolvers::NamespaceProjectsResolver do context "when passing a non existent, batch loaded namespace" do let(:namespace) do - BatchLoader.for("non-existent-path").batch do |_fake_paths, loader, _| + BatchLoader::GraphQL.for("non-existent-path").batch do |_fake_paths, loader, _| loader.call("non-existent-path", nil) end end diff --git a/spec/graphql/resolvers/project_resolver_spec.rb b/spec/graphql/resolvers/project_resolver_spec.rb index 4fdbb3aa43e..d0fc2957909 100644 --- a/spec/graphql/resolvers/project_resolver_spec.rb +++ b/spec/graphql/resolvers/project_resolver_spec.rb @@ -12,7 +12,7 @@ describe Resolvers::ProjectResolver do it 'batch-resolves projects by full path' do paths = [project1.full_path, project2.full_path] - result = batch(max_queries: 1) do + result = batch_sync(max_queries: 1) do paths.map { |path| resolve_project(path) } end @@ -20,7 +20,7 @@ describe Resolvers::ProjectResolver do end it 'resolves an unknown full_path to nil' do - result = batch { resolve_project('unknown/project') } + result = batch_sync { resolve_project('unknown/project') } expect(result).to be_nil end diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb index 6fbb6147d84..9330e75af11 100644 --- a/spec/helpers/avatars_helper_spec.rb +++ b/spec/helpers/avatars_helper_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe AvatarsHelper do include UploadHelpers diff --git a/spec/helpers/boards_helper_spec.rb b/spec/helpers/boards_helper_spec.rb index f014537eb54..ad088398ce9 100644 --- a/spec/helpers/boards_helper_spec.rb +++ b/spec/helpers/boards_helper_spec.rb @@ -40,7 +40,7 @@ describe BoardsHelper do assign(:project, project) allow(helper).to receive(:current_user) { user } - allow(helper).to receive(:can?).with(user, :admin_list, project).and_return(true) + allow(helper).to receive(:can?).with(user, :create_non_backlog_issues, board).and_return(true) end it 'returns a board_lists_path as lists_endpoint' do diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb index 824b3ab4fc1..9e53bc05a48 100644 --- a/spec/helpers/commits_helper_spec.rb +++ b/spec/helpers/commits_helper_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe CommitsHelper do describe 'commit_author_link' do diff --git a/spec/helpers/form_helper_spec.rb b/spec/helpers/form_helper_spec.rb index 18cf0031d5f..a70d8333f30 100644 --- a/spec/helpers/form_helper_spec.rb +++ b/spec/helpers/form_helper_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe FormHelper do describe 'form_errors' do diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb index f92b94a9583..950f951e22e 100644 --- a/spec/helpers/icons_helper_spec.rb +++ b/spec/helpers/icons_helper_spec.rb @@ -60,20 +60,19 @@ describe IconsHelper do non_existing = 'non_existing_icon_sprite' it 'raises in development mode' do - allow(Rails.env).to receive(:development?).and_return(true) + stub_rails_env('development') expect { sprite_icon(non_existing) }.to raise_error(ArgumentError, /is not a known icon/) end it 'raises in test mode' do - allow(Rails.env).to receive(:test?).and_return(true) + stub_rails_env('test') expect { sprite_icon(non_existing) }.to raise_error(ArgumentError, /is not a known icon/) end it 'does not raise in production mode' do - allow(Rails.env).to receive(:test?).and_return(false) - allow(Rails.env).to receive(:development?).and_return(false) + stub_rails_env('production') expect { sprite_icon(non_existing) }.not_to raise_error end diff --git a/spec/helpers/import_helper_spec.rb b/spec/helpers/import_helper_spec.rb index 6e8c13db9fe..8a1b1e859b1 100644 --- a/spec/helpers/import_helper_spec.rb +++ b/spec/helpers/import_helper_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe ImportHelper do describe '#sanitize_project_name' do diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb index bf50763d06f..3b08fc511a3 100644 --- a/spec/helpers/page_layout_helper_spec.rb +++ b/spec/helpers/page_layout_helper_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe PageLayoutHelper do describe 'page_description' do diff --git a/spec/helpers/profiles_helper_spec.rb b/spec/helpers/profiles_helper_spec.rb index 8e336469c27..da2dc229c35 100644 --- a/spec/helpers/profiles_helper_spec.rb +++ b/spec/helpers/profiles_helper_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe ProfilesHelper do describe '#commit_email_select_options' do diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb index a6623bc7941..9165e91ddcd 100644 --- a/spec/helpers/users_helper_spec.rb +++ b/spec/helpers/users_helper_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe UsersHelper do include TermsHelper diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb index e384e2bf9a0..edc0d64d031 100644 --- a/spec/helpers/version_check_helper_spec.rb +++ b/spec/helpers/version_check_helper_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe VersionCheckHelper do describe '#version_status_badge' do it 'returns nil if not dev environment and not enabled' do - allow(Rails.env).to receive(:production?) { false } + stub_rails_env('development') allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { false } expect(helper.version_status_badge).to be(nil) @@ -11,7 +11,7 @@ describe VersionCheckHelper do context 'when production and enabled' do before do - allow(Rails.env).to receive(:production?) { true } + stub_rails_env('production') allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { true } allow(VersionCheck).to receive(:url) { 'https://version.host.com/check.svg?gitlab_info=xxx' } end diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index bb7abe52eae..05e6ea1394d 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -1,6 +1,6 @@ /* global ListIssue */ -import Vue from 'vue'; +import axios from '~/lib/utils/axios_utils'; import '~/boards/models/label'; import '~/boards/models/assignee'; import '~/boards/models/issue'; @@ -175,7 +175,7 @@ describe('Issue model', () => { describe('update', () => { it('passes assignee ids when there are assignees', done => { - spyOn(Vue.http, 'patch').and.callFake((url, data) => { + spyOn(axios, 'patch').and.callFake((url, data) => { expect(data.issue.assignee_ids).toEqual([1]); done(); return Promise.resolve(); @@ -185,7 +185,7 @@ describe('Issue model', () => { }); it('passes assignee ids of [0] when there are no assignees', done => { - spyOn(Vue.http, 'patch').and.callFake((url, data) => { + spyOn(axios, 'patch').and.callFake((url, data) => { expect(data.issue.assignee_ids).toEqual([0]); done(); return Promise.resolve(); diff --git a/spec/javascripts/flash_spec.js b/spec/javascripts/flash_spec.js index aecab331ead..bd8608b6bac 100644 --- a/spec/javascripts/flash_spec.js +++ b/spec/javascripts/flash_spec.js @@ -25,14 +25,6 @@ describe('Flash', () => { '<script>alert("a");</script>', ); }); - - it('adds container classes when inside content wrapper', () => { - el.innerHTML = createFlashEl('testing', 'alert', true); - - expect(el.querySelector('.flash-text').classList.contains('container-fluid')).toBeTruthy(); - - expect(el.querySelector('.flash-text').classList.contains('container-limited')).toBeTruthy(); - }); }); describe('hideFlash', () => { @@ -171,9 +163,7 @@ describe('Flash', () => { it('adds container classes when inside content-wrapper', () => { flash('test'); - expect(document.querySelector('.flash-text').className).toBe( - 'flash-text container-fluid container-limited limit-container-width', - ); + expect(document.querySelector('.flash-text').className).toBe('flash-text'); }); it('does not add container when outside of content-wrapper', () => { diff --git a/spec/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js index 31873311e16..23b2564d3f9 100644 --- a/spec/javascripts/groups/components/app_spec.js +++ b/spec/javascripts/groups/components/app_spec.js @@ -1,3 +1,4 @@ +import '~/flash'; import $ from 'jquery'; import Vue from 'vue'; @@ -333,7 +334,7 @@ describe('AppComponent', () => { it('hides modal confirmation leave group and remove group item from tree', done => { const notice = `You left the "${childGroupItem.fullName}" group.`; - spyOn(vm.service, 'leaveGroup').and.returnValue(returnServicePromise({ notice })); + spyOn(vm.service, 'leaveGroup').and.returnValue(Promise.resolve({ data: { notice } })); spyOn(vm.store, 'removeGroup').and.callThrough(); spyOn(window, 'Flash'); spyOn($, 'scrollTo'); diff --git a/spec/javascripts/groups/service/groups_service_spec.js b/spec/javascripts/groups/service/groups_service_spec.js index 339e5131615..45db962a1ef 100644 --- a/spec/javascripts/groups/service/groups_service_spec.js +++ b/spec/javascripts/groups/service/groups_service_spec.js @@ -1,11 +1,8 @@ -import Vue from 'vue'; -import VueResource from 'vue-resource'; +import axios from '~/lib/utils/axios_utils'; import GroupsService from '~/groups/service/groups_service'; import { mockEndpoint, mockParentGroupItem } from '../mock_data'; -Vue.use(VueResource); - describe('GroupsService', () => { let service; @@ -15,8 +12,8 @@ describe('GroupsService', () => { describe('getGroups', () => { it('should return promise for `GET` request on provided endpoint', () => { - spyOn(service.groups, 'get').and.stub(); - const queryParams = { + spyOn(axios, 'get').and.stub(); + const params = { page: 2, filter: 'git', sort: 'created_asc', @@ -25,21 +22,21 @@ describe('GroupsService', () => { service.getGroups(55, 2, 'git', 'created_asc', true); - expect(service.groups.get).toHaveBeenCalledWith({ parent_id: 55 }); + expect(axios.get).toHaveBeenCalledWith(mockEndpoint, { params: { parent_id: 55 } }); service.getGroups(null, 2, 'git', 'created_asc', true); - expect(service.groups.get).toHaveBeenCalledWith(queryParams); + expect(axios.get).toHaveBeenCalledWith(mockEndpoint, { params }); }); }); describe('leaveGroup', () => { it('should return promise for `DELETE` request on provided endpoint', () => { - spyOn(Vue.http, 'delete').and.stub(); + spyOn(axios, 'delete').and.stub(); service.leaveGroup(mockParentGroupItem.leavePath); - expect(Vue.http.delete).toHaveBeenCalledWith(mockParentGroupItem.leavePath); + expect(axios.delete).toHaveBeenCalledWith(mockParentGroupItem.leavePath); }); }); }); diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js index d3c1cf831bb..57ab1aa73f7 100644 --- a/spec/javascripts/jobs/components/job_app_spec.js +++ b/spec/javascripts/jobs/components/job_app_spec.js @@ -25,6 +25,7 @@ describe('Job App ', () => { terminalPath: 'jobs/123/terminal', pagePath: `${gl.TEST_HOST}jobs/123`, projectPath: 'user-name/project-name', + subscriptionsMoreMinutesUrl: 'https://customers.gitlab.com/buy_pipeline_minutes', logState: 'eyJvZmZzZXQiOjE3NDUxLCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowfQ%3D%3D', }; diff --git a/spec/javascripts/jobs/components/log/line_header_spec.js b/spec/javascripts/jobs/components/log/line_header_spec.js new file mode 100644 index 00000000000..4efd412a6cd --- /dev/null +++ b/spec/javascripts/jobs/components/log/line_header_spec.js @@ -0,0 +1,84 @@ +import { mount } from '@vue/test-utils'; +import LineHeader from '~/jobs/components/log/line_header.vue'; +import LineNumber from '~/jobs/components/log/line_number.vue'; + +describe('Job Log Header Line', () => { + let wrapper; + + const data = { + line: { + content: [ + { + text: 'Running with gitlab-runner 12.1.0 (de7731dd)', + style: 'term-fg-l-green', + }, + ], + lineNumber: 0, + }, + isClosed: true, + path: '/jashkenas/underscore/-/jobs/335', + }; + + const createComponent = (props = {}) => { + wrapper = mount(LineHeader, { + sync: false, + propsData: { + ...props, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('line', () => { + beforeEach(() => { + createComponent(data); + }); + + it('renders the line number component', () => { + expect(wrapper.contains(LineNumber)).toBe(true); + }); + + it('renders a span the provided text', () => { + expect(wrapper.find('span').text()).toBe(data.line.content[0].text); + }); + + it('renders the provided style as a class attribute', () => { + expect(wrapper.find('span').classes()).toContain(data.line.content[0].style); + }); + }); + + describe('when isCloses is true', () => { + beforeEach(() => { + createComponent({ ...data, isClosed: true }); + }); + + it('sets icon name to be angle-right', () => { + expect(wrapper.vm.iconName).toEqual('angle-right'); + }); + }); + + describe('when isCloses is false', () => { + beforeEach(() => { + createComponent({ ...data, isClosed: false }); + }); + + it('sets icon name to be angle-down', () => { + expect(wrapper.vm.iconName).toEqual('angle-down'); + }); + }); + + describe('on click', () => { + beforeEach(() => { + createComponent(data); + }); + + it('emits toggleLine event', () => { + wrapper.trigger('click'); + + expect(wrapper.emitted().toggleLine.length).toBe(1); + }); + }); +}); diff --git a/spec/javascripts/jobs/components/log/line_number_spec.js b/spec/javascripts/jobs/components/log/line_number_spec.js new file mode 100644 index 00000000000..fcf2edf9159 --- /dev/null +++ b/spec/javascripts/jobs/components/log/line_number_spec.js @@ -0,0 +1,40 @@ +import { shallowMount } from '@vue/test-utils'; +import LineNumber from '~/jobs/components/log/line_number.vue'; + +describe('Job Log Line Number', () => { + let wrapper; + + const data = { + lineNumber: 0, + path: '/jashkenas/underscore/-/jobs/335', + }; + + const createComponent = (props = {}) => { + wrapper = shallowMount(LineNumber, { + sync: false, + propsData: { + ...props, + }, + }); + }; + + beforeEach(() => { + createComponent(data); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders incremented lineNunber by 1', () => { + expect(wrapper.text()).toBe('1'); + }); + + it('renders link with lineNumber as an ID', () => { + expect(wrapper.attributes().id).toBe('L1'); + }); + + it('links to the provided path with line number as anchor', () => { + expect(wrapper.attributes().href).toBe(`${data.path}#L1`); + }); +}); diff --git a/spec/javascripts/jobs/components/log/line_spec.js b/spec/javascripts/jobs/components/log/line_spec.js new file mode 100644 index 00000000000..ea593e3c39a --- /dev/null +++ b/spec/javascripts/jobs/components/log/line_spec.js @@ -0,0 +1,49 @@ +import { shallowMount } from '@vue/test-utils'; +import Line from '~/jobs/components/log/line.vue'; +import LineNumber from '~/jobs/components/log/line_number.vue'; + +describe('Job Log Line', () => { + let wrapper; + + const data = { + line: { + content: [ + { + text: 'Running with gitlab-runner 12.1.0 (de7731dd)', + style: 'term-fg-l-green', + }, + ], + lineNumber: 0, + }, + path: '/jashkenas/underscore/-/jobs/335', + }; + + const createComponent = (props = {}) => { + wrapper = shallowMount(Line, { + sync: false, + propsData: { + ...props, + }, + }); + }; + + beforeEach(() => { + createComponent(data); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders the line number component', () => { + expect(wrapper.contains(LineNumber)).toBe(true); + }); + + it('renders a span the provided text', () => { + expect(wrapper.find('span').text()).toBe(data.line.content[0].text); + }); + + it('renders the provided style as a class attribute', () => { + expect(wrapper.find('span').classes()).toContain(data.line.content[0].style); + }); +}); diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js deleted file mode 100644 index 1e49a955815..00000000000 --- a/spec/javascripts/monitoring/charts/area_spec.js +++ /dev/null @@ -1,265 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { createStore } from '~/monitoring/stores'; -import { GlLink } from '@gitlab/ui'; -import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; -import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper'; -import Area from '~/monitoring/components/charts/area.vue'; -import * as types from '~/monitoring/stores/mutation_types'; -import { TEST_HOST } from 'spec/test_constants'; -import MonitoringMock, { deploymentData } from '../mock_data'; - -describe('Area component', () => { - const mockSha = 'mockSha'; - const mockWidgets = 'mockWidgets'; - const mockSvgPathContent = 'mockSvgPathContent'; - const projectPath = `${TEST_HOST}/path/to/project`; - const commitUrl = `${projectPath}/commit/${mockSha}`; - let mockGraphData; - let areaChart; - let spriteSpy; - let store; - - beforeEach(() => { - store = createStore(); - store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data); - store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData); - - [mockGraphData] = store.state.monitoringDashboard.groups[0].metrics; - - areaChart = shallowMount(Area, { - propsData: { - graphData: mockGraphData, - containerWidth: 0, - deploymentData: store.state.monitoringDashboard.deploymentData, - projectPath, - }, - slots: { - default: mockWidgets, - }, - store, - }); - - spriteSpy = spyOnDependency(Area, 'getSvgIconPathContent').and.callFake( - () => new Promise(resolve => resolve(mockSvgPathContent)), - ); - }); - - afterEach(() => { - areaChart.destroy(); - }); - - it('renders chart title', () => { - expect(areaChart.find({ ref: 'graphTitle' }).text()).toBe(mockGraphData.title); - }); - - it('contains graph widgets from slot', () => { - expect(areaChart.find({ ref: 'graphWidgets' }).text()).toBe(mockWidgets); - }); - - describe('wrapped components', () => { - describe('GitLab UI area chart', () => { - let glAreaChart; - - beforeEach(() => { - glAreaChart = areaChart.find(GlAreaChart); - }); - - it('is a Vue instance', () => { - expect(glAreaChart.isVueInstance()).toBe(true); - }); - - it('receives data properties needed for proper chart render', () => { - const props = glAreaChart.props(); - - expect(props.data).toBe(areaChart.vm.chartData); - expect(props.option).toBe(areaChart.vm.chartOptions); - expect(props.formatTooltipText).toBe(areaChart.vm.formatTooltipText); - expect(props.thresholds).toBe(areaChart.vm.thresholds); - }); - - it('recieves a tooltip title', () => { - const mockTitle = 'mockTitle'; - areaChart.vm.tooltip.title = mockTitle; - - expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipTitle', mockTitle)).toBe(true); - }); - - describe('when tooltip is showing deployment data', () => { - beforeEach(() => { - areaChart.vm.tooltip.isDeployment = true; - }); - - it('uses deployment title', () => { - expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipTitle', 'Deployed')).toBe( - true, - ); - }); - - it('renders clickable commit sha in tooltip content', () => { - areaChart.vm.tooltip.sha = mockSha; - areaChart.vm.tooltip.commitUrl = commitUrl; - - const commitLink = areaChart.find(GlLink); - - expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true); - expect(commitLink.attributes('href')).toEqual(commitUrl); - }); - }); - }); - }); - - describe('methods', () => { - describe('formatTooltipText', () => { - const mockDate = deploymentData[0].created_at; - const generateSeriesData = type => ({ - seriesData: [ - { - seriesName: areaChart.vm.chartData[0].name, - componentSubType: type, - value: [mockDate, 5.55555], - seriesIndex: 0, - }, - ], - value: mockDate, - }); - - describe('when series is of line type', () => { - beforeEach(() => { - areaChart.vm.formatTooltipText(generateSeriesData('line')); - }); - - it('formats tooltip title', () => { - expect(areaChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM'); - }); - - it('formats tooltip content', () => { - const name = 'Core Usage'; - const value = '5.556'; - const seriesLabel = areaChart.find(GlChartSeriesLabel); - - expect(seriesLabel.vm.color).toBe(''); - expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true); - expect(areaChart.vm.tooltip.content).toEqual([{ name, value, color: undefined }]); - expect( - shallowWrapperContainsSlotText(areaChart.find(GlAreaChart), 'tooltipContent', value), - ).toBe(true); - }); - }); - - describe('when series is of scatter type', () => { - beforeEach(() => { - areaChart.vm.formatTooltipText(generateSeriesData('scatter')); - }); - - it('formats tooltip title', () => { - expect(areaChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM'); - }); - - it('formats tooltip sha', () => { - expect(areaChart.vm.tooltip.sha).toBe('f5bcd1d9'); - }); - }); - }); - - describe('setSvg', () => { - const mockSvgName = 'mockSvgName'; - - beforeEach(() => { - areaChart.vm.setSvg(mockSvgName); - }); - - it('gets svg path content', () => { - expect(spriteSpy).toHaveBeenCalledWith(mockSvgName); - }); - - it('sets svg path content', done => { - areaChart.vm.$nextTick(() => { - expect(areaChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`); - done(); - }); - }); - }); - - describe('onResize', () => { - const mockWidth = 233; - - beforeEach(() => { - spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({ - width: mockWidth, - })); - areaChart.vm.onResize(); - }); - - it('sets area chart width', () => { - expect(areaChart.vm.width).toBe(mockWidth); - }); - }); - }); - - describe('computed', () => { - describe('chartData', () => { - let chartData; - const seriesData = () => chartData[0]; - - beforeEach(() => { - ({ chartData } = areaChart.vm); - }); - - it('utilizes all data points', () => { - expect(chartData.length).toBe(1); - expect(seriesData().data.length).toBe(297); - }); - - it('creates valid data', () => { - const { data } = seriesData(); - - expect( - data.filter(([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number') - .length, - ).toBe(data.length); - }); - - it('formats line width correctly', () => { - expect(chartData[0].lineStyle.width).toBe(2); - }); - }); - - describe('chartOptions', () => { - describe('dataZoom', () => { - it('contains an svg object within an array to properly render icon', () => { - const dataZoomObject = [{}]; - - expect(areaChart.vm.chartOptions.dataZoom).toEqual(dataZoomObject); - }); - }); - - describe('yAxis formatter', () => { - let format; - - beforeEach(() => { - format = areaChart.vm.chartOptions.yAxis.axisLabel.formatter; - }); - - it('rounds to 3 decimal places', () => { - expect(format(0.88888)).toBe('0.889'); - }); - }); - }); - - describe('scatterSeries', () => { - it('utilizes deployment data', () => { - expect(areaChart.vm.scatterSeries.data).toEqual([ - ['2017-05-31T21:23:37.881Z', 0], - ['2017-05-30T20:08:04.629Z', 0], - ['2017-05-30T17:42:38.409Z', 0], - ]); - }); - }); - - describe('yAxisLabel', () => { - it('constructs a label for the chart y-axis', () => { - expect(areaChart.vm.yAxisLabel).toBe('CPU'); - }); - }); - }); -}); diff --git a/spec/javascripts/monitoring/components/dashboard_spec.js b/spec/javascripts/monitoring/components/dashboard_spec.js index f3ec7520c6f..15e41e2fe93 100644 --- a/spec/javascripts/monitoring/components/dashboard_spec.js +++ b/spec/javascripts/monitoring/components/dashboard_spec.js @@ -378,7 +378,9 @@ describe('Dashboard', () => { }); }); - describe('link to chart', () => { + // https://gitlab.com/gitlab-org/gitlab-ce/issues/66922 + // eslint-disable-next-line jasmine/no-disabled-tests + xdescribe('link to chart', () => { let wrapper; const currentDashboard = 'TEST_DASHBOARD'; localVue.use(GlToast); diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb index 807f1b8bbd3..8e955ec3b6b 100644 --- a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb +++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Banzai::Filter::BlockquoteFenceFilter do include FilterSpecHelper diff --git a/spec/lib/banzai/filter/front_matter_filter_spec.rb b/spec/lib/banzai/filter/front_matter_filter_spec.rb index 90b383dbcff..1b5b1770615 100644 --- a/spec/lib/banzai/filter/front_matter_filter_spec.rb +++ b/spec/lib/banzai/filter/front_matter_filter_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Banzai::Filter::FrontMatterFilter do include FilterSpecHelper diff --git a/spec/lib/banzai/pipeline/description_pipeline_spec.rb b/spec/lib/banzai/pipeline/description_pipeline_spec.rb index d032ec71e45..5ecd3df5151 100644 --- a/spec/lib/banzai/pipeline/description_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/description_pipeline_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Banzai::Pipeline::DescriptionPipeline do def parse(html) diff --git a/spec/lib/banzai/pipeline/email_pipeline_spec.rb b/spec/lib/banzai/pipeline/email_pipeline_spec.rb index eea25320f3d..77186861225 100644 --- a/spec/lib/banzai/pipeline/email_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/email_pipeline_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Banzai::Pipeline::EmailPipeline do describe '.filters' do diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb index 2b4d1b58676..f63b86d1451 100644 --- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Banzai::Pipeline::FullPipeline do describe 'References' do diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb index 3a9ecd2fb81..7eb63fea413 100644 --- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Banzai::Pipeline::GfmPipeline do describe 'integration between parsing regular and external issue references' do diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb index 015af20f220..4a485fbc2bd 100644 --- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Banzai::Pipeline::WikiPipeline do describe 'TableOfContents' do diff --git a/spec/lib/gitlab/anonymous_session_spec.rb b/spec/lib/gitlab/anonymous_session_spec.rb index 628aae81ada..94daa0f2470 100644 --- a/spec/lib/gitlab/anonymous_session_spec.rb +++ b/spec/lib/gitlab/anonymous_session_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::AnonymousSession, :clean_gitlab_redis_shared_state do let(:default_session_id) { '6919a6f1bb119dd7396fadc38fd18d0d' } diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 098c33f9cb1..0365d63ea9c 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -587,7 +587,8 @@ describe Gitlab::Auth do :read_project, :build_download_code, :build_read_container_image, - :build_create_container_image + :build_create_container_image, + :build_destroy_container_image ] end diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb index ad4fa4fe03a..c1f6871a568 100644 --- a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, :migration, schema: 20190315191339 do let(:namespaces) { table(:namespaces) } diff --git a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb index 740781f1aa5..fd35ddc49a2 100644 --- a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb +++ b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::BackgroundMigration::ResetMergeStatus, :migration, schema: 20190528180441 do let(:namespaces) { table(:namespaces) } diff --git a/spec/lib/gitlab/checks/project_created_spec.rb b/spec/lib/gitlab/checks/project_created_spec.rb index 14cb5e6ec66..373fef2a240 100644 --- a/spec/lib/gitlab/checks/project_created_spec.rb +++ b/spec/lib/gitlab/checks/project_created_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do let(:user) { create(:user) } diff --git a/spec/lib/gitlab/checks/project_moved_spec.rb b/spec/lib/gitlab/checks/project_moved_spec.rb index 3ca977aa48d..3de397760b4 100644 --- a/spec/lib/gitlab/checks/project_moved_spec.rb +++ b/spec/lib/gitlab/checks/project_moved_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do let(:user) { create(:user) } diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index cff4eb398bf..49f92f14559 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -1283,33 +1283,19 @@ describe Gitlab::Database::MigrationHelpers do describe '#perform_background_migration_inline?' do it 'returns true in a test environment' do - allow(Rails.env) - .to receive(:test?) - .and_return(true) + stub_rails_env('test') expect(model.perform_background_migration_inline?).to eq(true) end it 'returns true in a development environment' do - allow(Rails.env) - .to receive(:test?) - .and_return(false) - - allow(Rails.env) - .to receive(:development?) - .and_return(true) + stub_rails_env('development') expect(model.perform_background_migration_inline?).to eq(true) end it 'returns false in a production environment' do - allow(Rails.env) - .to receive(:test?) - .and_return(false) - - allow(Rails.env) - .to receive(:development?) - .and_return(false) + stub_rails_env('production') expect(model.perform_background_migration_inline?).to eq(false) end diff --git a/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb index eed2a1b7b48..e6321d48e11 100644 --- a/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb +++ b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::DatabaseImporters::CommonMetrics::Importer do subject { described_class.new } diff --git a/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb b/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb index 94f544e59b3..e4e8a85e7bc 100644 --- a/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb +++ b/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::DatabaseImporters::CommonMetrics::PrometheusMetric do it 'group enum equals ::PrometheusMetric' do diff --git a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb index 3eb5db51224..6d61edaa870 100644 --- a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::DependencyLinker::CartfileLinker do describe '.support?' do diff --git a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb index 6bef6f57e64..cc1f09628ef 100644 --- a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::DependencyLinker::ComposerJsonLinker do describe '.support?' do diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb index 6ecdb0d1247..c6b6dfa77cb 100644 --- a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::DependencyLinker::GemfileLinker do describe '.support?' do diff --git a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb index 256fe58925c..c1cbfa31684 100644 --- a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::DependencyLinker::GemspecLinker do describe '.support?' do diff --git a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb index f620d1b590c..9f8542a76c9 100644 --- a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::DependencyLinker::GodepsJsonLinker do describe '.support?' do diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb index 71e9381eaad..eb0c5e0675a 100644 --- a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::DependencyLinker::PackageJsonLinker do describe '.support?' do diff --git a/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb b/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb index 9bfb1b13a2b..5b69ef5af24 100644 --- a/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb +++ b/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::DependencyLinker::Parser::Gemfile do describe '#parse' do diff --git a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb index eb81bc07760..77326e73505 100644 --- a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::DependencyLinker::PodfileLinker do describe '.support?' do diff --git a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb index 938726dd434..d522a08cdd9 100644 --- a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::DependencyLinker::PodspecJsonLinker do describe '.support?' do diff --git a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb index 540eb2fadfe..baabd0c0460 100644 --- a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::DependencyLinker::PodspecLinker do describe '.support?' do diff --git a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb index 957a87985a2..04ac5f10479 100644 --- a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::DependencyLinker::RequirementsTxtLinker do describe '.support?' do diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb index 98e46d62ca0..3ea3334caf0 100644 --- a/spec/lib/gitlab/dependency_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::DependencyLinker do describe '.link' do diff --git a/spec/lib/gitlab/favicon_spec.rb b/spec/lib/gitlab/favicon_spec.rb index dce56bbd2c4..d221f39c2ed 100644 --- a/spec/lib/gitlab/favicon_spec.rb +++ b/spec/lib/gitlab/favicon_spec.rb @@ -1,14 +1,14 @@ -require 'rails_helper' +require 'spec_helper' RSpec.describe Gitlab::Favicon, :request_store do describe '.main' do it 'defaults to favicon.png' do - allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production')) + stub_rails_env('production') expect(described_class.main).to match_asset_path '/assets/favicon.png' end it 'has blue favicon for development', unless: Gitlab.ee? do - allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development')) + stub_rails_env('development') expect(described_class.main).to match_asset_path '/assets/favicon-blue.png' end @@ -24,7 +24,7 @@ RSpec.describe Gitlab::Favicon, :request_store do context 'asset host' do before do - allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production')) + stub_rails_env('production') end it 'returns a relative url when the asset host is not configured' do diff --git a/spec/lib/gitlab/file_markdown_link_builder_spec.rb b/spec/lib/gitlab/file_markdown_link_builder_spec.rb index feb2776c5d0..d9e2e162ae8 100644 --- a/spec/lib/gitlab/file_markdown_link_builder_spec.rb +++ b/spec/lib/gitlab/file_markdown_link_builder_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::FileMarkdownLinkBuilder do let(:custom_class) do diff --git a/spec/lib/gitlab/file_type_detection_spec.rb b/spec/lib/gitlab/file_type_detection_spec.rb index 5e9b8988cc8..22ec7d414e8 100644 --- a/spec/lib/gitlab/file_type_detection_spec.rb +++ b/spec/lib/gitlab/file_type_detection_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::FileTypeDetection do def upload_fixture(filename) diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb index 81658874be7..be6ab0c1200 100644 --- a/spec/lib/gitlab/git/diff_collection_spec.rb +++ b/spec/lib/gitlab/git/diff_collection_spec.rb @@ -74,6 +74,11 @@ describe Gitlab::Git::DiffCollection, :seed_helper do end end + describe '#line_count' do + subject { super().line_count } + it { is_expected.to eq file_count * line_count } + end + context 'when limiting is disabled' do let(:limits) { false } @@ -100,6 +105,11 @@ describe Gitlab::Git::DiffCollection, :seed_helper do expect(subject.size).to eq(3) end end + + describe '#line_count' do + subject { super().line_count } + it { is_expected.to eq file_count * line_count } + end end end @@ -120,6 +130,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do subject { super().real_size } it { is_expected.to eq('0+') } end + + describe '#line_count' do + subject { super().line_count } + it { is_expected.to eq 1000 } + end + it { expect(subject.size).to eq(0) } context 'when limiting is disabled' do @@ -139,6 +155,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do subject { super().real_size } it { is_expected.to eq('3') } end + + describe '#line_count' do + subject { super().line_count } + it { is_expected.to eq file_count * line_count } + end + it { expect(subject.size).to eq(3) } end end @@ -164,6 +186,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do subject { super().real_size } it { is_expected.to eq('10+') } end + + describe '#line_count' do + subject { super().line_count } + it { is_expected.to eq 10 } + end + it { expect(subject.size).to eq(10) } context 'when limiting is disabled' do @@ -183,6 +211,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do subject { super().real_size } it { is_expected.to eq('11') } end + + describe '#line_count' do + subject { super().line_count } + it { is_expected.to eq file_count * line_count } + end + it { expect(subject.size).to eq(11) } end end @@ -204,6 +238,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do subject { super().real_size } it { is_expected.to eq('3+') } end + + describe '#line_count' do + subject { super().line_count } + it { is_expected.to eq 120 } + end + it { expect(subject.size).to eq(3) } context 'when limiting is disabled' do @@ -223,6 +263,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do subject { super().real_size } it { is_expected.to eq('11') } end + + describe '#line_count' do + subject { super().line_count } + it { is_expected.to eq file_count * line_count } + end + it { expect(subject.size).to eq(11) } end end @@ -248,6 +294,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do subject { super().real_size } it { is_expected.to eq('10') } end + + describe '#line_count' do + subject { super().line_count } + it { is_expected.to eq file_count * line_count } + end + it { expect(subject.size).to eq(10) } end end @@ -270,6 +322,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do subject { super().real_size } it { is_expected.to eq('9+') } end + + describe '#line_count' do + subject { super().line_count } + it { is_expected.to eq file_count * line_count } + end + it { expect(subject.size).to eq(9) } context 'when limiting is disabled' do @@ -289,6 +347,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do subject { super().real_size } it { is_expected.to eq('10') } end + + describe '#line_count' do + subject { super().line_count } + it { is_expected.to eq file_count * line_count } + end + it { expect(subject.size).to eq(10) } end end @@ -316,6 +380,11 @@ describe Gitlab::Git::DiffCollection, :seed_helper do subject { super().real_size } it { is_expected.to eq('0')} end + + describe '#line_count' do + subject { super().line_count } + it { is_expected.to eq 0 } + end end describe '#each' do diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb index 99d563e03ec..1c5f72a4396 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -265,7 +265,7 @@ describe Gitlab::GitalyClient do context 'in production and when RequestStore is enabled', :request_store do before do - allow(Rails.env).to receive(:production?).and_return(true) + stub_rails_env('production') end context 'when the maximum number of calls is enforced by a feature flag' do diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb index 47e6f5d4220..a3d7e42733d 100644 --- a/spec/lib/gitlab/gpg/commit_spec.rb +++ b/spec/lib/gitlab/gpg/commit_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe Gitlab::Gpg::Commit do describe '#signature' do diff --git a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb index 1a2c6ef25c4..1dfca0b056c 100644 --- a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb +++ b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do describe '#run' do diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb index 48bbd7f854c..77d318c9b23 100644 --- a/spec/lib/gitlab/gpg_spec.rb +++ b/spec/lib/gitlab/gpg_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe Gitlab::Gpg do describe '.fingerprints_from_key' do diff --git a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb index 7a7ae373058..aada9285b31 100644 --- a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb +++ b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb @@ -32,7 +32,8 @@ describe Gitlab::Graphql::Authorize::AuthorizeFieldService do let(:presented_type) { double('parent type', object: presented_object) } let(:query_type) { GraphQL::ObjectType.new } let(:schema) { GraphQL::Schema.define(query: query_type, mutation: nil)} - let(:context) { GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema), values: { current_user: current_user }, object: nil) } + let(:query_context) { OpenStruct.new(schema: schema) } + let(:context) { GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema, context: query_context), values: { current_user: current_user }, object: nil) } subject(:resolved) { service.authorized_resolve.call(presented_type, {}, context) } context 'scalar types' do diff --git a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb index 50138d272c4..23762666ba8 100644 --- a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb +++ b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb @@ -46,9 +46,9 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do end end - describe '#authorized?' do + describe '#authorized_resource?' do it 'is true' do - expect(loading_resource.authorized?(project)).to be(true) + expect(loading_resource.authorized_resource?(project)).to be(true) end end end @@ -72,9 +72,9 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do end end - describe '#authorized?' do + describe '#authorized_resource?' do it 'is false' do - expect(loading_resource.authorized?(project)).to be(false) + expect(loading_resource.authorized_resource?(project)).to be(false) end end end @@ -121,9 +121,9 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do end end - describe '#authorized?' do + describe '#authorized_resource?' do it 'raises a comprehensive error message' do - expect { loading_resource.authorized?(project) }.to raise_error(error) + expect { loading_resource.authorized_resource?(project) }.to raise_error(error) end end end diff --git a/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb b/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb index fefa2881b18..4eb121794e1 100644 --- a/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb +++ b/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::Graphql::Connections::KeysetConnection do end def encoded_property(value) - Base64.strict_encode64(value.to_s) + Base64Bp.urlsafe_encode64(value.to_s, padding: false) end describe '#cursor_from_nodes' do diff --git a/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb index 46dd1777285..22d8aa4274a 100644 --- a/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb +++ b/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb @@ -12,7 +12,7 @@ describe Gitlab::Graphql::Loaders::BatchLfsOidLoader do it 'batch-resolves LFS blob IDs' do expect(Gitlab::Git::Blob).to receive(:batch_lfs_pointers).once.and_call_original - result = batch do + result = batch_sync do [blob, otherblob].map { |b| described_class.new(repository, b.id).find } end diff --git a/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb index 4609593ef6a..a4bbd868558 100644 --- a/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb +++ b/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb @@ -9,8 +9,8 @@ describe Gitlab::Graphql::Loaders::BatchModelLoader do issue_result = described_class.new(Issue, issue.id).find user_result = described_class.new(User, user.id).find - expect(issue_result.__sync).to eq(issue) - expect(user_result.__sync).to eq(user) + expect(issue_result.sync).to eq(issue) + expect(user_result.sync).to eq(user) end it 'only queries once per model' do @@ -21,7 +21,7 @@ describe Gitlab::Graphql::Loaders::BatchModelLoader do expect do [described_class.new(User, other_user.id).find, described_class.new(User, user.id).find, - described_class.new(Issue, issue.id).find].map(&:__sync) + described_class.new(Issue, issue.id).find].map(&:sync) end.not_to exceed_query_limit(2) end end diff --git a/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb index 927476cc655..136027736c3 100644 --- a/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb +++ b/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb @@ -10,7 +10,7 @@ describe Gitlab::Graphql::Loaders::PipelineForShaLoader do pipeline2 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha) pipeline3 = create(:ci_pipeline, project: project, ref: 'improve/awesome', sha: project.commit('improve/awesome').sha) - result = batch(max_queries: 1) do + result = batch_sync(max_queries: 1) do [pipeline1.sha, pipeline3.sha].map { |sha| described_class.new(project, sha).find_last } end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index ec4a6ef05b9..47ba7eff8ed 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -26,6 +26,7 @@ issues: events: - author - project +- group - target - push_event_payload notes: @@ -62,6 +63,8 @@ milestone: - participants - events - boards +- milestone_release +- release snippets: - author - project @@ -72,6 +75,8 @@ releases: - author - project - links +- milestone_release +- milestone links: - release project_members: @@ -484,3 +489,6 @@ lists: - board - label - list_user_preferences +milestone_releases: +- milestone +- release diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index f0545176a90..516e62c4728 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -33,6 +33,7 @@ Event: - target_type - target_id - project_id +- group_id - created_at - updated_at - action diff --git a/spec/lib/gitlab/jwt_authenticatable_spec.rb b/spec/lib/gitlab/jwt_authenticatable_spec.rb new file mode 100644 index 00000000000..0c1c491b308 --- /dev/null +++ b/spec/lib/gitlab/jwt_authenticatable_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::JwtAuthenticatable do + let(:test_class) do + Class.new do + include Gitlab::JwtAuthenticatable + + def self.secret_path + Rails.root.join('tmp', 'tests', '.jwt_shared_secret') + end + end + end + + before do + begin + File.delete(test_class.secret_path) + rescue Errno::ENOENT + end + + test_class.write_secret + end + + describe '.secret' do + subject(:secret) { test_class.secret } + + it 'returns 32 bytes' do + expect(secret).to be_a(String) + expect(secret.length).to eq(32) + expect(secret.encoding).to eq(Encoding::ASCII_8BIT) + end + + it 'accepts a trailing newline' do + File.open(test_class.secret_path, 'a') { |f| f.write "\n" } + + expect(secret.length).to eq(32) + end + + it 'raises an exception if the secret file cannot be read' do + File.delete(test_class.secret_path) + + expect { secret }.to raise_exception(Errno::ENOENT) + end + + it 'raises an exception if the secret file contains the wrong number of bytes' do + File.truncate(test_class.secret_path, 0) + + expect { secret }.to raise_exception(RuntimeError) + end + end + + describe '.write_secret' do + it 'uses mode 0600' do + expect(File.stat(test_class.secret_path).mode & 0777).to eq(0600) + end + + it 'writes base64 data' do + bytes = Base64.strict_decode64(File.read(test_class.secret_path)) + + expect(bytes).not_to be_empty + end + end + + describe '.decode_jwt_for_issuer' do + let(:payload) { { 'iss' => 'test_issuer' } } + + it 'accepts a correct header' do + encoded_message = JWT.encode(payload, test_class.secret, 'HS256') + + expect { test_class.decode_jwt_for_issuer('test_issuer', encoded_message) }.not_to raise_error + end + + it 'raises an error when the JWT is not signed' do + encoded_message = JWT.encode(payload, nil, 'none') + + expect { test_class.decode_jwt_for_issuer('test_issuer', encoded_message) }.to raise_error(JWT::DecodeError) + end + + it 'raises an error when the header is signed with the wrong secret' do + encoded_message = JWT.encode(payload, 'wrongsecret', 'HS256') + + expect { test_class.decode_jwt_for_issuer('test_issuer', encoded_message) }.to raise_error(JWT::DecodeError) + end + + it 'raises an error when the issuer is incorrect' do + payload['iss'] = 'somebody else' + encoded_message = JWT.encode(payload, test_class.secret, 'HS256') + + expect { test_class.decode_jwt_for_issuer('test_issuer', encoded_message) }.to raise_error(JWT::DecodeError) + end + end +end diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb index 7395b095454..f7f510f01db 100644 --- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe Gitlab::Kubernetes::Helm::InstallCommand do let(:files) { { 'ca.pem': 'some file content' } } diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb index fce2aded786..64cadcc011c 100644 --- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe Gitlab::Kubernetes::Helm::Pod do describe '#generate' do diff --git a/spec/lib/gitlab/prometheus/metric_group_spec.rb b/spec/lib/gitlab/prometheus/metric_group_spec.rb index a45dd0af91e..787f14daf47 100644 --- a/spec/lib/gitlab/prometheus/metric_group_spec.rb +++ b/spec/lib/gitlab/prometheus/metric_group_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::Prometheus::MetricGroup do describe '.common_metrics' do diff --git a/spec/lib/gitlab/query_limiting/transaction_spec.rb b/spec/lib/gitlab/query_limiting/transaction_spec.rb index 39d5a575efc..4e906314b5a 100644 --- a/spec/lib/gitlab/query_limiting/transaction_spec.rb +++ b/spec/lib/gitlab/query_limiting/transaction_spec.rb @@ -86,9 +86,7 @@ describe Gitlab::QueryLimiting::Transaction do it 'returns false in a production environment' do transaction = described_class.new - expect(Rails.env) - .to receive(:test?) - .and_return(false) + stub_rails_env('production') expect(transaction.raise_error?).to eq(false) end diff --git a/spec/lib/gitlab/query_limiting_spec.rb b/spec/lib/gitlab/query_limiting_spec.rb index f0d0340cd6e..e9c6bbc35a3 100644 --- a/spec/lib/gitlab/query_limiting_spec.rb +++ b/spec/lib/gitlab/query_limiting_spec.rb @@ -9,14 +9,14 @@ describe Gitlab::QueryLimiting do end it 'returns true in a development environment' do - allow(Rails.env).to receive(:development?).and_return(true) + stub_rails_env('development') + stub_rails_env('development') expect(described_class.enable?).to eq(true) end it 'returns false on GitLab.com' do - expect(Rails.env).to receive(:development?).and_return(false) - expect(Rails.env).to receive(:test?).and_return(false) + stub_rails_env('production') allow(Gitlab).to receive(:com?).and_return(true) expect(described_class.enable?).to eq(false) @@ -24,8 +24,7 @@ describe Gitlab::QueryLimiting do it 'returns false in a non GitLab.com' do allow(Gitlab).to receive(:com?).and_return(false) - expect(Rails.env).to receive(:development?).and_return(false) - expect(Rails.env).to receive(:test?).and_return(false) + stub_rails_env('production') expect(described_class.enable?).to eq(false) end diff --git a/spec/lib/gitlab/sidekiq_config_spec.rb b/spec/lib/gitlab/sidekiq_config_spec.rb index 1e8ccb447b1..49efbac160a 100644 --- a/spec/lib/gitlab/sidekiq_config_spec.rb +++ b/spec/lib/gitlab/sidekiq_config_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Gitlab::SidekiqConfig do describe '.workers' do diff --git a/spec/lib/gitlab/tracing_spec.rb b/spec/lib/gitlab/tracing_spec.rb index db75ce2a998..e913bb600ec 100644 --- a/spec/lib/gitlab/tracing_spec.rb +++ b/spec/lib/gitlab/tracing_spec.rb @@ -59,7 +59,7 @@ describe Gitlab::Tracing do it 'returns the correct state for .tracing_url' do expect(described_class).to receive(:tracing_url_enabled?).and_return(tracing_url_enabled?) allow(described_class).to receive(:tracing_url_template).and_return(tracing_url_template) - allow(Gitlab::CorrelationId).to receive(:current_id).and_return(correlation_id) + allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return(correlation_id) allow(Gitlab).to receive(:process_name).and_return(process_name) expect(described_class.tracing_url).to eq(tracing_url) diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb index df8a1f82f81..6d1d7e48326 100644 --- a/spec/lib/gitlab/url_blocker_spec.rb +++ b/spec/lib/gitlab/url_blocker_spec.rb @@ -4,80 +4,114 @@ require 'spec_helper' describe Gitlab::UrlBlocker do + include StubRequests + describe '#validate!' do + subject { described_class.validate!(import_url) } + + shared_examples 'validates URI and hostname' do + it 'runs the url validations' do + uri, hostname = subject + + expect(uri).to eq(Addressable::URI.parse(expected_uri)) + expect(hostname).to eq(expected_hostname) + end + end + context 'when URI is nil' do let(:import_url) { nil } - it 'returns no URI and hostname' do - uri, hostname = described_class.validate!(import_url) - - expect(uri).to be(nil) - expect(hostname).to be(nil) + it_behaves_like 'validates URI and hostname' do + let(:expected_uri) { nil } + let(:expected_hostname) { nil } end end context 'when URI is internal' do let(:import_url) { 'http://localhost' } - it 'returns URI and no hostname' do - uri, hostname = described_class.validate!(import_url) - - expect(uri).to eq(Addressable::URI.parse('http://[::1]')) - expect(hostname).to eq('localhost') + it_behaves_like 'validates URI and hostname' do + let(:expected_uri) { 'http://[::1]' } + let(:expected_hostname) { 'localhost' } end end context 'when the URL hostname is a domain' do - let(:import_url) { 'https://example.org' } + context 'when domain can be resolved' do + let(:import_url) { 'https://example.org' } - it 'returns URI and hostname' do - uri, hostname = described_class.validate!(import_url) + before do + stub_dns(import_url, ip_address: '93.184.216.34') + end - expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34')) - expect(hostname).to eq('example.org') + it_behaves_like 'validates URI and hostname' do + let(:expected_uri) { 'https://93.184.216.34' } + let(:expected_hostname) { 'example.org' } + end + end + + context 'when domain cannot be resolved' do + let(:import_url) { 'http://foobar.x' } + + it 'raises an error' do + stub_env('RSPEC_ALLOW_INVALID_URLS', 'false') + + expect { subject }.to raise_error(described_class::BlockedUrlError) + end end end context 'when the URL hostname is an IP address' do let(:import_url) { 'https://93.184.216.34' } - it 'returns URI and no hostname' do - uri, hostname = described_class.validate!(import_url) + it_behaves_like 'validates URI and hostname' do + let(:expected_uri) { import_url } + let(:expected_hostname) { nil } + end + + context 'when the address is invalid' do + let(:import_url) { 'http://1.1.1.1.1' } - expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34')) - expect(hostname).to be(nil) + it 'raises an error' do + stub_env('RSPEC_ALLOW_INVALID_URLS', 'false') + + expect { subject }.to raise_error(described_class::BlockedUrlError) + end end end context 'disabled DNS rebinding protection' do + subject { described_class.validate!(import_url, dns_rebind_protection: false) } + context 'when URI is internal' do let(:import_url) { 'http://localhost' } - it 'returns URI and no hostname' do - uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false) - - expect(uri).to eq(Addressable::URI.parse('http://localhost')) - expect(hostname).to be(nil) + it_behaves_like 'validates URI and hostname' do + let(:expected_uri) { import_url } + let(:expected_hostname) { nil } end end context 'when the URL hostname is a domain' do let(:import_url) { 'https://example.org' } - it 'returns URI and no hostname' do - uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false) + before do + stub_env('RSPEC_ALLOW_INVALID_URLS', 'false') + end - expect(uri).to eq(Addressable::URI.parse('https://example.org')) - expect(hostname).to eq(nil) + context 'when domain can be resolved' do + it_behaves_like 'validates URI and hostname' do + let(:expected_uri) { import_url } + let(:expected_hostname) { nil } + end end - context 'when it cannot be resolved' do + context 'when domain cannot be resolved' do let(:import_url) { 'http://foobar.x' } - it 'raises error' do - stub_env('RSPEC_ALLOW_INVALID_URLS', 'false') - - expect { described_class.validate!(import_url) }.to raise_error(described_class::BlockedUrlError) + it_behaves_like 'validates URI and hostname' do + let(:expected_uri) { import_url } + let(:expected_hostname) { nil } end end end @@ -85,20 +119,17 @@ describe Gitlab::UrlBlocker do context 'when the URL hostname is an IP address' do let(:import_url) { 'https://93.184.216.34' } - it 'returns URI and no hostname' do - uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false) - - expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34')) - expect(hostname).to be(nil) + it_behaves_like 'validates URI and hostname' do + let(:expected_uri) { import_url } + let(:expected_hostname) { nil } end context 'when it is invalid' do let(:import_url) { 'http://1.1.1.1.1' } - it 'raises an error' do - stub_env('RSPEC_ALLOW_INVALID_URLS', 'false') - - expect { described_class.validate!(import_url) }.to raise_error(described_class::BlockedUrlError) + it_behaves_like 'validates URI and hostname' do + let(:expected_uri) { import_url } + let(:expected_hostname) { nil } end end end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 98421cd12d3..88bc5034da5 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -200,57 +200,6 @@ describe Gitlab::Workhorse do end end - describe ".secret" do - subject { described_class.secret } - - before do - described_class.instance_variable_set(:@secret, nil) - described_class.write_secret - end - - it 'returns 32 bytes' do - expect(subject).to be_a(String) - expect(subject.length).to eq(32) - expect(subject.encoding).to eq(Encoding::ASCII_8BIT) - end - - it 'accepts a trailing newline' do - File.open(described_class.secret_path, 'a') { |f| f.write "\n" } - expect(subject.length).to eq(32) - end - - it 'raises an exception if the secret file cannot be read' do - File.delete(described_class.secret_path) - expect { subject }.to raise_exception(Errno::ENOENT) - end - - it 'raises an exception if the secret file contains the wrong number of bytes' do - File.truncate(described_class.secret_path, 0) - expect { subject }.to raise_exception(RuntimeError) - end - end - - describe ".write_secret" do - let(:secret_path) { described_class.secret_path } - before do - begin - File.delete(secret_path) - rescue Errno::ENOENT - end - - described_class.write_secret - end - - it 'uses mode 0600' do - expect(File.stat(secret_path).mode & 0777).to eq(0600) - end - - it 'writes base64 data' do - bytes = Base64.strict_decode64(File.read(secret_path)) - expect(bytes).not_to be_empty - end - end - describe '#verify_api_request!' do let(:header_key) { described_class::INTERNAL_API_REQUEST_HEADER } let(:payload) { { 'iss' => 'gitlab-workhorse' } } diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index 1fc363460ae..589dac61528 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' - -require_dependency 'gitlab' +require 'spec_helper' describe Gitlab do describe '.root' do @@ -113,7 +111,7 @@ describe Gitlab do it 'is true when dev env' do allow(described_class).to receive_messages(com?: false, org?: false) - allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development')) + stub_rails_env('development') expect(described_class.dev_env_org_or_com?).to eq true end diff --git a/spec/mailers/abuse_report_mailer_spec.rb b/spec/mailers/abuse_report_mailer_spec.rb index f96870cc112..86153071cd3 100644 --- a/spec/mailers/abuse_report_mailer_spec.rb +++ b/spec/mailers/abuse_report_mailer_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe AbuseReportMailer do include EmailSpec::Matchers diff --git a/spec/mailers/email_rejection_mailer_spec.rb b/spec/mailers/email_rejection_mailer_spec.rb index bbe0a50ae8e..2ce2e3e1410 100644 --- a/spec/mailers/email_rejection_mailer_spec.rb +++ b/spec/mailers/email_rejection_mailer_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe EmailRejectionMailer do include EmailSpec::Matchers diff --git a/spec/mailers/repository_check_mailer_spec.rb b/spec/mailers/repository_check_mailer_spec.rb index 3dce89f5be2..757d3dfa797 100644 --- a/spec/mailers/repository_check_mailer_spec.rb +++ b/spec/mailers/repository_check_mailer_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe RepositoryCheckMailer do include EmailSpec::Matchers diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb index a5f8e999d5d..814df472389 100644 --- a/spec/models/abuse_report_spec.rb +++ b/spec/models/abuse_report_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe AbuseReport do set(:report) { create(:abuse_report) } diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb index 2a689754ee0..b1f93dc7189 100644 --- a/spec/models/active_session_spec.rb +++ b/spec/models/active_session_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do let(:user) do diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb index 209d138f956..2c32028c3e5 100644 --- a/spec/models/appearance_spec.rb +++ b/spec/models/appearance_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Appearance do subject { build(:appearance) } diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb index 8364293b908..62a75bde63b 100644 --- a/spec/models/blob_spec.rb +++ b/spec/models/blob_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Blob do include FakeBlobHelpers diff --git a/spec/models/board_spec.rb b/spec/models/board_spec.rb index 54452faa0e1..f6eee67e539 100644 --- a/spec/models/board_spec.rb +++ b/spec/models/board_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Board do describe 'relationships' do diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 7d84d094bdf..63ca383ac4b 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -323,6 +323,25 @@ describe Ci::Pipeline, :mailer do end end + describe '#merge_train_pipeline?' do + subject { pipeline.merge_train_pipeline? } + + let!(:pipeline) do + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, ref: ref, target_sha: 'xxx') + end + + let(:merge_request) { create(:merge_request) } + let(:ref) { 'refs/merge-requests/1/train' } + + it { is_expected.to be_truthy } + + context 'when ref is merge ref' do + let(:ref) { 'refs/merge-requests/1/merge' } + + it { is_expected.to be_falsy } + end + end + describe '#merge_request_ref?' do subject { pipeline.merge_request_ref? } @@ -333,6 +352,48 @@ describe Ci::Pipeline, :mailer do end end + describe '#merge_train_ref?' do + subject { pipeline.merge_train_ref? } + + it 'calls Mergetrain#merge_train_ref?' do + expect(MergeRequest).to receive(:merge_train_ref?).with(pipeline.ref) + + subject + end + end + + describe '#merge_request_event_type' do + subject { pipeline.merge_request_event_type } + + before do + allow(pipeline).to receive(:merge_request_event?) { true } + end + + context 'when pipeline is merge train pipeline' do + before do + allow(pipeline).to receive(:merge_train_pipeline?) { true } + end + + it { is_expected.to eq(:merge_train) } + end + + context 'when pipeline is merge request pipeline' do + before do + allow(pipeline).to receive(:merge_request_pipeline?) { true } + end + + it { is_expected.to eq(:merged_result) } + end + + context 'when pipeline is detached merge request pipeline' do + before do + allow(pipeline).to receive(:detached_merge_request_pipeline?) { true } + end + + it { is_expected.to eq(:detached) } + end + end + describe '#legacy_detached_merge_request_pipeline?' do subject { pipeline.legacy_detached_merge_request_pipeline? } @@ -782,7 +843,8 @@ describe Ci::Pipeline, :mailer do 'CI_MERGE_REQUEST_TITLE' => merge_request.title, 'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list, 'CI_MERGE_REQUEST_MILESTONE' => milestone.title, - 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).join(',')) + 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).join(','), + 'CI_MERGE_REQUEST_EVENT_TYPE' => pipeline.merge_request_event_type.to_s) end context 'when source project does not exist' do diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb index f6d5d05e4a0..bddc09decc3 100644 --- a/spec/models/clusters/applications/cert_manager_spec.rb +++ b/spec/models/clusters/applications/cert_manager_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Clusters::Applications::CertManager do let(:cert_manager) { create(:clusters_applications_cert_manager) } diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb index 00b5c72a3d3..9672129bb1e 100644 --- a/spec/models/clusters/applications/helm_spec.rb +++ b/spec/models/clusters/applications/helm_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Clusters::Applications::Helm do include_examples 'cluster application core specs', :clusters_applications_helm diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb index 057517d3820..f984f6ba0ce 100644 --- a/spec/models/clusters/applications/ingress_spec.rb +++ b/spec/models/clusters/applications/ingress_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Clusters::Applications::Ingress do let(:ingress) { create(:clusters_applications_ingress) } diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb index 3ff66a074e4..1b39328752d 100644 --- a/spec/models/clusters/applications/jupyter_spec.rb +++ b/spec/models/clusters/applications/jupyter_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Clusters::Applications::Jupyter do include_examples 'cluster application core specs', :clusters_applications_jupyter diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index 334f10526cb..3825994b733 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Clusters::Applications::Knative do let(:knative) { create(:clusters_applications_knative) } diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index eb6ccba5584..8fc3b7e4c40 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Clusters::Applications::Prometheus do include KubernetesHelpers diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index 4abe45a2152..b420a180524 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Clusters::Applications::Runner do let(:ci_runner) { create(:ci_runner) } diff --git a/spec/models/concerns/blocks_json_serialization_spec.rb b/spec/models/concerns/blocks_json_serialization_spec.rb index e8657c45a93..0ef5be3cb61 100644 --- a/spec/models/concerns/blocks_json_serialization_spec.rb +++ b/spec/models/concerns/blocks_json_serialization_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe BlocksJsonSerialization do DummyModel = Class.new do diff --git a/spec/models/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb index da46effe411..d8f940a808e 100644 --- a/spec/models/concerns/cacheable_attributes_spec.rb +++ b/spec/models/concerns/cacheable_attributes_spec.rb @@ -131,7 +131,7 @@ describe CacheableAttributes do context 'in production environment' do before do - expect(Rails.env).to receive(:production?).and_return(true) + stub_rails_env('production') end it 'returns an uncached record and logs a warning' do @@ -143,7 +143,7 @@ describe CacheableAttributes do context 'in other environments' do before do - expect(Rails.env).to receive(:production?).and_return(false) + stub_rails_env('development') end it 'returns an uncached record and logs a warning' do diff --git a/spec/models/concerns/deployable_spec.rb b/spec/models/concerns/deployable_spec.rb index bb73dd8ade0..ad2c0770a2c 100644 --- a/spec/models/concerns/deployable_spec.rb +++ b/spec/models/concerns/deployable_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Deployable do describe '#create_deployment' do diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb index 27f535487c8..220f244ad71 100644 --- a/spec/models/concerns/deployment_platform_spec.rb +++ b/spec/models/concerns/deployment_platform_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe DeploymentPlatform do let(:project) { create(:project) } diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index cff86afe768..cad705ee594 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -66,6 +66,13 @@ describe Group, 'Routable' do it { expect(described_class.find_by_full_path(group.to_param.upcase)).to eq(group) } it { expect(described_class.find_by_full_path(nested_group.to_param)).to eq(nested_group) } it { expect(described_class.find_by_full_path('unknown')).to eq(nil) } + + it 'includes route information when loading a record' do + path = group.to_param + control_count = ActiveRecord::QueryRecorder.new { described_class.find_by_full_path(path) }.count + + expect { described_class.find_by_full_path(path).route }.not_to exceed_all_query_limit(control_count) + end end context 'with redirect routes' do diff --git a/spec/models/concerns/sha_attribute_spec.rb b/spec/models/concerns/sha_attribute_spec.rb index a4a81ae126d..0d4dbfb215e 100644 --- a/spec/models/concerns/sha_attribute_spec.rb +++ b/spec/models/concerns/sha_attribute_spec.rb @@ -17,7 +17,7 @@ describe ShaAttribute do describe '#sha_attribute' do context 'when in non-production' do before do - allow(Rails.env).to receive(:production?).and_return(false) + stub_rails_env('development') end context 'when the table exists' do @@ -76,7 +76,7 @@ describe ShaAttribute do context 'when in production' do before do - allow(Rails.env).to receive(:production?).and_return(true) + stub_rails_env('production') end it 'defines a SHA attribute' do diff --git a/spec/models/concerns/triggerable_hooks_spec.rb b/spec/models/concerns/triggerable_hooks_spec.rb index f28e5f56411..ac1bc51d950 100644 --- a/spec/models/concerns/triggerable_hooks_spec.rb +++ b/spec/models/concerns/triggerable_hooks_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' RSpec.describe TriggerableHooks do before do diff --git a/spec/models/conversational_development_index/metric_spec.rb b/spec/models/conversational_development_index/metric_spec.rb index 60b1a860dfd..55ba466e614 100644 --- a/spec/models/conversational_development_index/metric_spec.rb +++ b/spec/models/conversational_development_index/metric_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe ConversationalDevelopmentIndex::Metric do let(:conv_dev_index) { create(:conversational_development_index_metric) } diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb index 479b39cd139..b9c914e2506 100644 --- a/spec/models/gpg_key_spec.rb +++ b/spec/models/gpg_key_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe GpgKey do describe "associations" do diff --git a/spec/models/gpg_key_subkey_spec.rb b/spec/models/gpg_key_subkey_spec.rb index 51d2f9cb9ac..5f80cc02924 100644 --- a/spec/models/gpg_key_subkey_spec.rb +++ b/spec/models/gpg_key_subkey_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe GpgKeySubkey do subject { build(:gpg_key_subkey) } diff --git a/spec/models/gpg_signature_spec.rb b/spec/models/gpg_signature_spec.rb index 47c343edf0e..4911375c962 100644 --- a/spec/models/gpg_signature_spec.rb +++ b/spec/models/gpg_signature_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' RSpec.describe GpgSignature do let(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' } diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index e0d4d2e4858..a4d202dc4f8 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -64,7 +64,7 @@ describe SystemHook do ).once end - it "project_create hook" do + it "project member create hook" do project.add_maintainer(user) expect(WebMock).to have_requested(:post, system_hook.url).with( @@ -73,7 +73,7 @@ describe SystemHook do ).once end - it "project_destroy hook" do + it "project member destroy hook" do project.add_maintainer(user) project.project_members.destroy_all # rubocop: disable DestroyAll @@ -83,6 +83,15 @@ describe SystemHook do ).once end + it "project member update hook" do + project.add_guest(user) + + expect(WebMock).to have_requested(:post, system_hook.url).with( + body: /user_update_for_team/, + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } + ).once + end + it 'group create hook' do create(:group) @@ -119,6 +128,16 @@ describe SystemHook do headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } ).once end + + it 'group member update hook' do + group.add_guest(user) + group.add_maintainer(user) + + expect(WebMock).to have_requested(:post, system_hook.url).with( + body: /user_update_for_group/, + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } + ).once + end end describe '.repository_update_hooks' do diff --git a/spec/models/hooks/web_hook_log_spec.rb b/spec/models/hooks/web_hook_log_spec.rb index f812149c9be..85934b81086 100644 --- a/spec/models/hooks/web_hook_log_spec.rb +++ b/spec/models/hooks/web_hook_log_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe WebHookLog do it { is_expected.to belong_to(:web_hook) } diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb index 28630f7d3fe..c73ade3f896 100644 --- a/spec/models/internal_id_spec.rb +++ b/spec/models/internal_id_spec.rb @@ -106,7 +106,8 @@ describe InternalId do end it 'always attempts to generate internal IDs in production mode' do - allow(Rails.env).to receive(:test?).and_return(false) + stub_rails_env('production') + val = rand(1..100) generator = double(generate: val) expect(InternalId::InternalIdGenerator).to receive(:new).and_return(generator) diff --git a/spec/models/lfs_download_object_spec.rb b/spec/models/lfs_download_object_spec.rb index 8b53effe98f..d7522fbb969 100644 --- a/spec/models/lfs_download_object_spec.rb +++ b/spec/models/lfs_download_object_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe LfsDownloadObject do let(:oid) { 'cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411' } diff --git a/spec/models/lfs_file_lock_spec.rb b/spec/models/lfs_file_lock_spec.rb index aa64d66944b..a42346c341d 100644 --- a/spec/models/lfs_file_lock_spec.rb +++ b/spec/models/lfs_file_lock_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe LfsFileLock do set(:lfs_file_lock) { create(:lfs_file_lock) } diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb index 2429cd408a6..dc28204d7aa 100644 --- a/spec/models/list_spec.rb +++ b/spec/models/list_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe List do it_behaves_like 'having unique enum values' diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb index 49573af0fed..bd97cabc11e 100644 --- a/spec/models/merge_request/metrics_spec.rb +++ b/spec/models/merge_request/metrics_spec.rb @@ -3,8 +3,6 @@ require 'spec_helper' describe MergeRequest::Metrics do - subject { described_class.new } - describe 'associations' do it { is_expected.to belong_to(:merge_request) } it { is_expected.to belong_to(:latest_closed_by).class_name('User') } diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb index ab2aadf7d88..c0a09dab0b0 100644 --- a/spec/models/merge_request_diff_commit_spec.rb +++ b/spec/models/merge_request_diff_commit_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe MergeRequestDiffCommit do let(:merge_request) { create(:merge_request) } diff --git a/spec/models/merge_request_diff_file_spec.rb b/spec/models/merge_request_diff_file_spec.rb index 66957c24fdc..97b30bb8607 100644 --- a/spec/models/merge_request_diff_file_spec.rb +++ b/spec/models/merge_request_diff_file_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe MergeRequestDiffFile do describe '#diff' do diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index e7dd7287a75..b86663fd7d9 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -400,6 +400,18 @@ describe MergeRequestDiff do end end + describe '#first_commit' do + it 'returns first commit' do + expect(diff_with_commits.first_commit.sha).to eq(diff_with_commits.merge_request_diff_commits.last.sha) + end + end + + describe '#last_commit' do + it 'returns last commit' do + expect(diff_with_commits.last_commit.sha).to eq(diff_with_commits.merge_request_diff_commits.first.sha) + end + end + describe '#commits_by_shas' do let(:commit_shas) { diff_with_commits.commit_shas } @@ -489,7 +501,7 @@ describe MergeRequestDiff do subject { diff_with_commits } it 'returns sum of all changed lines count in diff files' do - expect(subject.lines_count).to eq 109 + expect(subject.lines_count).to eq 189 end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index d344a6d0f0d..11234982dd4 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -3195,6 +3195,40 @@ describe MergeRequest do end end + describe '.merge_train_ref?' do + subject { described_class.merge_train_ref?(ref) } + + context 'when ref is ref name of a branch' do + let(:ref) { 'feature' } + + it { is_expected.to be_falsey } + end + + context 'when ref is HEAD ref path of a branch' do + let(:ref) { 'refs/heads/feature' } + + it { is_expected.to be_falsey } + end + + context 'when ref is HEAD ref path of a merge request' do + let(:ref) { 'refs/merge-requests/1/head' } + + it { is_expected.to be_falsey } + end + + context 'when ref is merge ref path of a merge request' do + let(:ref) { 'refs/merge-requests/1/merge' } + + it { is_expected.to be_falsey } + end + + context 'when ref is train ref path of a merge request' do + let(:ref) { 'refs/merge-requests/1/train' } + + it { is_expected.to be_truthy } + end + end + describe '#cleanup_refs' do subject { merge_request.cleanup_refs(only: only) } diff --git a/spec/models/milestone_release_spec.rb b/spec/models/milestone_release_spec.rb new file mode 100644 index 00000000000..d6f73275977 --- /dev/null +++ b/spec/models/milestone_release_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MilestoneRelease do + let(:project) { create(:project) } + let(:release) { create(:release, project: project) } + let(:milestone) { create(:milestone, project: project) } + + subject { build(:milestone_release, release: release, milestone: milestone) } + + describe 'associations' do + it { is_expected.to belong_to(:milestone) } + it { is_expected.to belong_to(:release) } + end + + describe 'validations' do + it { is_expected.to validate_uniqueness_of(:milestone_id).scoped_to(:release_id) } + + context 'when milestone and release do not have the same project' do + it 'is not valid' do + other_project = create(:project) + release = build(:release, project: other_project) + milestone_release = described_class.new(milestone: milestone, release: release) + expect(milestone_release).not_to be_valid + end + end + + context 'when milestone and release have the same project' do + it 'is valid' do + milestone_release = described_class.new(milestone: milestone, release: release) + expect(milestone_release).to be_valid + end + end + end +end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 3704a2d468d..64030f5b92a 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -54,11 +54,31 @@ describe Milestone do expect(milestone.errors[:due_date]).to include("date must not be after 9999-12-31") end end + + describe 'milestone_release' do + let(:milestone) { build(:milestone, project: project) } + + context 'when it is tied to a release for another project' do + it 'creates a validation error' do + other_project = create(:project) + milestone.release = build(:release, project: other_project) + expect(milestone).not_to be_valid + end + end + + context 'when it is tied to a release for the same project' do + it 'is valid' do + milestone.release = build(:release, project: project) + expect(milestone).to be_valid + end + end + end end describe "Associations" do it { is_expected.to belong_to(:project) } it { is_expected.to have_many(:issues) } + it { is_expected.to have_one(:release) } end let(:project) { create(:project, :public) } diff --git a/spec/models/note_diff_file_spec.rb b/spec/models/note_diff_file_spec.rb index b15bedd257e..11108016b8e 100644 --- a/spec/models/note_diff_file_spec.rb +++ b/spec/models/note_diff_file_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe NoteDiffFile do describe 'associations' do diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index 85128456918..820d233dbdc 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' RSpec.describe NotificationSetting do it_behaves_like 'having unique enum values' diff --git a/spec/models/project_deploy_token_spec.rb b/spec/models/project_deploy_token_spec.rb index 2a5fefc1ab0..8c8924762bd 100644 --- a/spec/models/project_deploy_token_spec.rb +++ b/spec/models/project_deploy_token_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' RSpec.describe ProjectDeployToken, type: :model do let(:project) { create(:project) } diff --git a/spec/models/project_import_state_spec.rb b/spec/models/project_import_state_spec.rb index 472bf8f9713..8a847bbe24e 100644 --- a/spec/models/project_import_state_spec.rb +++ b/spec/models/project_import_state_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe ProjectImportState, type: :model do subject { create(:import_state) } diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb index a164ed9bbea..4bc6130387a 100644 --- a/spec/models/project_statistics_spec.rb +++ b/spec/models/project_statistics_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe ProjectStatistics do let(:project) { create :project } diff --git a/spec/models/redirect_route_spec.rb b/spec/models/redirect_route_spec.rb index 6ecb5c31c7e..b9b2873f8f2 100644 --- a/spec/models/redirect_route_spec.rb +++ b/spec/models/redirect_route_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe RedirectRoute do let(:group) { create(:group) } diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb index e9d846e7291..c690390e24d 100644 --- a/spec/models/release_spec.rb +++ b/spec/models/release_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' RSpec.describe Release do let(:user) { create(:user) } @@ -13,6 +13,7 @@ RSpec.describe Release do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:author).class_name('User') } it { is_expected.to have_many(:links).class_name('Releases::Link') } + it { is_expected.to have_one(:milestone) } end describe 'validation' do @@ -34,6 +35,20 @@ RSpec.describe Release do expect(existing_release_without_name.name).to be_nil end end + + context 'when a release is tied to a milestone for another project' do + it 'creates a validation error' do + release.milestone = build(:milestone, project: create(:project)) + expect(release).not_to be_valid + end + end + + context 'when a release is tied to a milestone linked to the same project' do + it 'is valid' do + release.milestone = build(:milestone, project: project) + expect(release).to be_valid + end + end end describe '#assets_count' do diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb index f8d6e500e10..63d0bf3f314 100644 --- a/spec/models/remote_mirror_spec.rb +++ b/spec/models/remote_mirror_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe RemoteMirror, :mailer do include GitHelpers diff --git a/spec/models/resource_label_event_spec.rb b/spec/models/resource_label_event_spec.rb index cb52f154299..f4023dcb95a 100644 --- a/spec/models/resource_label_event_spec.rb +++ b/spec/models/resource_label_event_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' RSpec.describe ResourceLabelEvent, type: :model do subject { build(:resource_label_event, issue: issue) } diff --git a/spec/models/timelog_spec.rb b/spec/models/timelog_spec.rb index 9d69a0ab148..28fc82f2a32 100644 --- a/spec/models/timelog_spec.rb +++ b/spec/models/timelog_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' RSpec.describe Timelog do subject { build(:timelog) } diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb index 02702cb2497..d97bb8cfb90 100644 --- a/spec/models/upload_spec.rb +++ b/spec/models/upload_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Upload do describe 'assocations' do diff --git a/spec/models/user_agent_detail_spec.rb b/spec/models/user_agent_detail_spec.rb index f191d245045..5c28511b446 100644 --- a/spec/models/user_agent_detail_spec.rb +++ b/spec/models/user_agent_detail_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe UserAgentDetail do describe '.submittable?' do diff --git a/spec/models/user_callout_spec.rb b/spec/models/user_callout_spec.rb index b87f6f03d6f..de6534b480a 100644 --- a/spec/models/user_callout_spec.rb +++ b/spec/models/user_callout_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe UserCallout do let!(:callout) { create(:user_callout) } diff --git a/spec/policies/board_policy_spec.rb b/spec/policies/board_policy_spec.rb index 52c23951e37..35eac8a02c4 100644 --- a/spec/policies/board_policy_spec.rb +++ b/spec/policies/board_policy_spec.rb @@ -56,4 +56,57 @@ describe BoardPolicy do end end end + + context 'create_non_backlog_issues' do + context 'for project boards' do + let!(:current_user) { create(:user) } + + subject { described_class.new(current_user, project_board) } + + context 'when user can admin project issues' do + it 'allows to add non backlog issues from issue board' do + project.add_reporter(current_user) + + expect_allowed(:create_non_backlog_issues) + end + end + + context 'when user cannot admin project issues' do + it 'does not allow to add non backlog issues from issue board' do + project.add_guest(current_user) + + expect_disallowed(:create_non_backlog_issues) + end + end + end + + context 'for group boards' do + let!(:current_user) { create(:user) } + let!(:project_1) { create(:project, namespace: group) } + let!(:project_2) { create(:project, namespace: group) } + let!(:group_board) { create(:board, group: group) } + + subject { described_class.new(current_user, group_board) } + + before do + project_1.add_guest(current_user) + end + + context 'when user is at least reporter in one of the child projects' do + it 'allows to add non backlog issues from issue board' do + project_2.add_reporter(current_user) + + expect_allowed(:create_non_backlog_issues) + end + end + + context 'when user is not a reporter from any child projects' do + it 'does not allow to add non backlog issues from issue board' do + project_2.add_guest(current_user) + + expect_disallowed(:create_non_backlog_issues) + end + end + end + end end diff --git a/spec/presenters/ci/pipeline_presenter_spec.rb b/spec/presenters/ci/pipeline_presenter_spec.rb index cda07a0ae09..7e8bbedcf6d 100644 --- a/spec/presenters/ci/pipeline_presenter_spec.rb +++ b/spec/presenters/ci/pipeline_presenter_spec.rb @@ -77,6 +77,40 @@ describe Ci::PipelinePresenter do end end + describe '#name' do + subject { presenter.name } + + context 'when pipeline is detached merge request pipeline' do + let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } + let(:pipeline) { merge_request.all_pipelines.last } + + it { is_expected.to eq('Detached merge request pipeline') } + end + + context 'when pipeline is merge request pipeline' do + let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) } + let(:pipeline) { merge_request.all_pipelines.last } + + it { is_expected.to eq('Merged result pipeline') } + end + + context 'when pipeline is merge train pipeline' do + let(:pipeline) { create(:ci_pipeline, project: project) } + + before do + allow(pipeline).to receive(:merge_request_event_type) { :merge_train } + end + + it { is_expected.to eq('Merge train pipeline') } + end + + context 'when pipeline is branch pipeline' do + let(:pipeline) { create(:ci_pipeline, project: project) } + + it { is_expected.to eq('Pipeline') } + end + end + describe '#ref_text' do subject { presenter.ref_text } diff --git a/spec/presenters/clusterable_presenter_spec.rb b/spec/presenters/clusterable_presenter_spec.rb index 05afe5347d1..47ccc59ae45 100644 --- a/spec/presenters/clusterable_presenter_spec.rb +++ b/spec/presenters/clusterable_presenter_spec.rb @@ -78,4 +78,13 @@ describe ClusterablePresenter do it_behaves_like 'appropriate member permissions' end end + + describe '#environments_cluster_path' do + subject { described_class.new(clusterable).environments_cluster_path(cluster) } + + let(:clusterable) { create(:group) } + let(:cluster) { create(:cluster_for_group, groups: [clusterable]) } + + it { is_expected.to be_nil } + end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb deleted file mode 100644 index 671fd6c8666..00000000000 --- a/spec/rails_helper.rb +++ /dev/null @@ -1 +0,0 @@ -require "spec_helper" diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index 28676bb02f4..e1eb7c7f738 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -120,7 +120,7 @@ describe 'GitlabSchema configurations' do query_string: query, variables: {}.to_s, complexity: 181, - depth: 0, + depth: 13, duration: 7 } diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb index d75f0df9fd3..bbc477ba485 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb @@ -13,7 +13,16 @@ describe 'Setting WIP status of a merge request' do project_path: project.full_path, iid: merge_request.iid.to_s } - graphql_mutation(:merge_request_set_wip, variables.merge(input)) + graphql_mutation(:merge_request_set_wip, variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + mergeRequest { + id + title + } + QL + ) end def mutation_response diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 50f36141aed..0893dcb39b6 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -483,6 +483,22 @@ describe API::Groups do describe "GET /groups/:id/projects" do context "when authenticated as user" do + context 'with min access level' do + it 'returns projects with min access level or higher' do + group_guest = create(:user) + group1.add_guest(group_guest) + project4 = create(:project, group: group1) + project1.add_guest(group_guest) + project3.add_reporter(group_guest) + project4.add_developer(group_guest) + + get api("/groups/#{group1.id}/projects", group_guest), params: { min_access_level: Gitlab::Access::REPORTER } + + project_ids = json_response.map { |proj| proj['id'] } + expect(project_ids).to match_array([project3.id, project4.id]) + end + end + it "returns the group's projects" do get api("/groups/#{group1.id}/projects", user1) diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal/base_spec.rb index c94f6d22e74..a56527073c7 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal/base_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::Internal do +describe API::Internal::Base do set(:user) { create(:user) } let(:key) { create(:key, user: user) } set(:project) { create(:project, :repository, :wiki_repo) } diff --git a/spec/requests/api/pages_domains_spec.rb b/spec/requests/api/pages_domains_spec.rb index 449032b95b7..326b724666d 100644 --- a/spec/requests/api/pages_domains_spec.rb +++ b/spec/requests/api/pages_domains_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe API::PagesDomains do set(:project) { create(:project, path: 'my.project', pages_https_only: false) } diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 58a28e636f1..2e6e13aa927 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe API::ProjectSnippets do set(:project) { create(:project, :public) } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5465fe0c366..550c7d135a6 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -5,6 +5,8 @@ shared_examples 'languages and percentages JSON response' do let(:expected_languages) { project.repository.languages.map { |language| language.values_at(:label, :value)}.to_h } before do + allow(DetectRepositoryLanguagesWorker).to receive(:perform_async).and_call_original + allow(project.repository).to receive(:languages).and_return( [{ value: 66.69, label: "Ruby", color: "#701516", highlight: "#701516" }, { value: 22.98, label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" }, diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb index cc05b8d5b45..515912cb305 100644 --- a/spec/requests/api/snippets_spec.rb +++ b/spec/requests/api/snippets_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe API::Snippets do let!(:user) { create(:user) } diff --git a/spec/serializers/entity_date_helper_spec.rb b/spec/serializers/entity_date_helper_spec.rb index ae0f917415c..73506954965 100644 --- a/spec/serializers/entity_date_helper_spec.rb +++ b/spec/serializers/entity_date_helper_spec.rb @@ -57,6 +57,28 @@ describe EntityDateHelper do end end + context 'when milestone due date is today' do + let(:milestone_remaining) { date_helper_class.remaining_days_in_words(Date.today) } + + it 'returns today' do + expect(milestone_remaining).to eq("<strong>Today</strong>") + end + end + + context 'when milestone due date is tomorrow' do + let(:milestone_remaining) { date_helper_class.remaining_days_in_words(Date.tomorrow) } + + it 'returns 1 day remaining' do + expect(milestone_remaining).to eq("<strong>1</strong> day remaining") + end + + it 'returns 1 day remaining when queried mid-day' do + Timecop.freeze(Time.utc(2017, 3, 17, 13, 10)) do + expect(milestone_remaining).to eq("<strong>1</strong> day remaining") + end + end + end + context 'when less than 1 year and more than 30 days remaining' do let(:milestone_remaining) { date_helper_class.remaining_days_in_words(2.months.from_now.utc.to_date) } diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index 6be612ec226..eb9972d3e4d 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -41,7 +41,7 @@ describe PipelineEntity do it 'contains details' do expect(subject).to include :details expect(subject[:details]) - .to include :duration, :finished_at + .to include :duration, :finished_at, :name expect(subject[:details][:status]).to include :icon, :favicon, :text, :label, :tooltip end @@ -211,6 +211,10 @@ describe PipelineEntity do expect(subject[:source_sha]).to be_present expect(subject[:target_sha]).to be_present end + + it 'exposes merge request event type' do + expect(subject[:merge_request_event_type]).to be_present + end end end end diff --git a/spec/services/audit_event_service_spec.rb b/spec/services/audit_event_service_spec.rb index e42bff607b2..96df6689bb0 100644 --- a/spec/services/audit_event_service_spec.rb +++ b/spec/services/audit_event_service_spec.rb @@ -47,4 +47,16 @@ describe AuditEventService do expect(details[:target_id]).to eq(1) end end + + describe '#log_security_event_to_file' do + it 'logs security event to file' do + expect(service).to receive(:file_logger).and_return(logger) + expect(logger).to receive(:info).with(author_id: user.id, + entity_type: 'Project', + entity_id: project.id, + action: :destroy) + + service.log_security_event_to_file + end + end end diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index 3ca389ba25b..2807b8c8c85 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -476,7 +476,7 @@ describe Auth::ContainerRegistryAuthenticationService do let(:current_user) { create(:user) } let(:authentication_abilities) do - [:build_read_container_image, :build_create_container_image] + [:build_read_container_image, :build_create_container_image, :build_destroy_container_image] end before do @@ -507,19 +507,19 @@ describe Auth::ContainerRegistryAuthenticationService do end end - context 'disallow to delete images' do + context 'allow to delete images since registry 2.7' do let(:current_params) do - { scopes: ["repository:#{current_project.full_path}:*"] } + { scopes: ["repository:#{current_project.full_path}:delete"] } end - it_behaves_like 'an inaccessible' do + it_behaves_like 'a deletable since registry 2.7' do let(:project) { current_project } end end - context 'disallow to delete images since registry 2.7' do + context 'disallow to delete images' do let(:current_params) do - { scopes: ["repository:#{current_project.full_path}:delete"] } + { scopes: ["repository:#{current_project.full_path}:*"] } end it_behaves_like 'an inaccessible' do diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index deb68899309..fad865a4811 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -1140,10 +1140,26 @@ describe Ci::CreatePipelineService do context 'when pipeline on feature is created' do let(:ref_name) { 'refs/heads/feature' } - it 'does not create a pipeline as test_a depends on build_a' do - expect(pipeline).not_to be_persisted - expect(pipeline.builds).to be_empty - expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'") + context 'when save_on_errors is enabled' do + let(:pipeline) { execute_service(save_on_errors: true) } + + it 'does create a pipeline as test_a depends on build_a' do + expect(pipeline).to be_persisted + expect(pipeline.builds).to be_empty + expect(pipeline.yaml_errors).to eq("test_a: needs 'build_a'") + expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'") + end + end + + context 'when save_on_errors is disabled' do + let(:pipeline) { execute_service(save_on_errors: false) } + + it 'does not create a pipeline as test_a depends on build_a' do + expect(pipeline).not_to be_persisted + expect(pipeline.builds).to be_empty + expect(pipeline.yaml_errors).to be_nil + expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'") + end end end diff --git a/spec/services/clusters/applications/check_installation_progress_service_spec.rb b/spec/services/clusters/applications/check_installation_progress_service_spec.rb index 464a67649ff..02fd4b63c89 100644 --- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb +++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb @@ -142,7 +142,11 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do end it 'removes the installation POD' do - expect(service).to receive(:remove_installation_pod).once + expect_any_instance_of(Gitlab::Kubernetes::Helm::Api) + .to receive(:delete_pod!) + .with(kind_of(String)) + .once + expect(service).to receive(:remove_installation_pod).and_call_original service.execute end 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 1a9f7089c3d..68ad0208226 100644 --- a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb +++ b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb @@ -47,11 +47,15 @@ describe Clusters::Applications::CheckUninstallProgressService do context 'when installation POD succeeded' do let(:phase) { Gitlab::Kubernetes::Pod::SUCCEEDED } before do + expect_any_instance_of(Gitlab::Kubernetes::Helm::Api) + .to receive(:delete_pod!) + .with(kind_of(String)) + .once expect(service).to receive(:pod_phase).once.and_return(phase) end it 'removes the installation POD' do - expect(service).to receive(:remove_installation_pod).once + expect(service).to receive(:remove_uninstallation_pod).and_call_original service.execute end @@ -76,7 +80,7 @@ describe Clusters::Applications::CheckUninstallProgressService do end it 'still removes the installation POD' do - expect(service).to receive(:remove_installation_pod).once + expect(service).to receive(:remove_uninstallation_pod).and_call_original service.execute end diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb index f3af8cf5f3b..0cbb3122bb0 100644 --- a/spec/services/groups/transfer_service_spec.rb +++ b/spec/services/groups/transfer_service_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Groups::TransferService do let(:user) { create(:user) } diff --git a/spec/services/metrics/dashboard/project_dashboard_service_spec.rb b/spec/services/metrics/dashboard/project_dashboard_service_spec.rb index 1357914be2a..e76db868425 100644 --- a/spec/services/metrics/dashboard/project_dashboard_service_spec.rb +++ b/spec/services/metrics/dashboard/project_dashboard_service_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Metrics::Dashboard::ProjectDashboardService, :use_clean_rails_memory_store_caching do include MetricsDashboardHelpers diff --git a/spec/services/milestones/destroy_service_spec.rb b/spec/services/milestones/destroy_service_spec.rb index 3a22e4d4f92..ff1e1256166 100644 --- a/spec/services/milestones/destroy_service_spec.rb +++ b/spec/services/milestones/destroy_service_spec.rb @@ -65,5 +65,19 @@ describe Milestones::DestroyService do expect { service.execute(group_milestone) }.not_to change { Event.count } end end + + context 'when a release is tied to a milestone' do + it 'destroys the milestone but not the associated release' do + release = create( + :release, + tag: 'v1.0', + project: project, + milestone: milestone + ) + + expect { service.execute(milestone) }.not_to change { Release.count } + expect(release.reload).to be_persisted + end + end end end diff --git a/spec/services/milestones/find_or_create_service_spec.rb b/spec/services/milestones/find_or_create_service_spec.rb new file mode 100644 index 00000000000..ae3def30982 --- /dev/null +++ b/spec/services/milestones/find_or_create_service_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Milestones::FindOrCreateService do + describe '#execute' do + subject(:service) { described_class.new(project, user, params) } + + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } + let(:params) do + { + title: '1.0', + description: 'First Release', + start_date: Date.today, + due_date: Date.today + 1.month + }.with_indifferent_access + end + + context 'when finding milestone on project level' do + let!(:existing_project_milestone) { create(:milestone, project: project, title: '1.0') } + + it 'returns existing milestone' do + expect(service.execute).to eq(existing_project_milestone) + end + end + + context 'when finding milestone on group level' do + let!(:existing_group_milestone) { create(:milestone, group: group, title: '1.0') } + + it 'returns existing milestone' do + expect(service.execute).to eq(existing_group_milestone) + end + end + + context 'when not finding milestone' do + context 'when user has permissions' do + before do + project.add_developer(user) + end + + context 'when params are valid' do + it 'creates a new milestone at project level using params' do + expect { service.execute }.to change(project.milestones, :count).by(1) + + milestone = project.reload.milestones.last + + expect(milestone.title).to eq(params[:title]) + expect(milestone.description).to eq(params[:description]) + expect(milestone.start_date).to eq(params[:start_date]) + expect(milestone.due_date).to eq(params[:due_date]) + end + end + + context 'when params are not valid' do + before do + params[:start_date] = Date.today + 2.months + end + + it 'returns nil' do + expect(service.execute).to be_nil + end + end + end + + context 'when user does not have permissions' do + before do + project.add_guest(user) + end + + it 'does not create a new milestone' do + expect { service.execute }.not_to change(project.milestones, :count) + end + + it 'returns nil' do + expect(service.execute).to be_nil + end + end + end + end +end diff --git a/spec/services/milestones/transfer_service_spec.rb b/spec/services/milestones/transfer_service_spec.rb new file mode 100644 index 00000000000..b3d41eb0763 --- /dev/null +++ b/spec/services/milestones/transfer_service_spec.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Milestones::TransferService do + describe '#execute' do + subject(:service) { described_class.new(user, old_group, project) } + + context 'when old_group is present' do + let(:user) { create(:admin) } + let(:new_group) { create(:group) } + let(:old_group) { create(:group) } + let(:project) { create(:project, namespace: old_group) } + let(:group_milestone) { create(:milestone, group: old_group)} + let(:group_milestone2) { create(:milestone, group: old_group)} + let(:project_milestone) { create(:milestone, project: project)} + let!(:issue_with_group_milestone) { create(:issue, project: project, milestone: group_milestone) } + let!(:issue_with_project_milestone) { create(:issue, project: project, milestone: project_milestone) } + let!(:mr_with_group_milestone) { create(:merge_request, source_project: project, source_branch: 'branch-1', milestone: group_milestone) } + let!(:mr_with_project_milestone) { create(:merge_request, source_project: project, source_branch: 'branch-2', milestone: project_milestone) } + + before do + new_group.add_maintainer(user) + project.add_maintainer(user) + # simulate project transfer + project.update(group: new_group) + end + + context 'without existing milestone at the new group level' do + it 'recreates the missing group milestones at project level' do + expect { service.execute }.to change(project.milestones, :count).by(1) + end + + it 'applies new project milestone to issues with group milestone' do + service.execute + new_milestone = issue_with_group_milestone.reload.milestone + + expect(new_milestone).not_to eq(group_milestone) + expect(new_milestone.title).to eq(group_milestone.title) + expect(new_milestone.project_milestone?).to be_truthy + end + + it 'does not apply new project milestone to issues with project milestone' do + service.execute + + expect(issue_with_project_milestone.reload.milestone).to eq(project_milestone) + end + + it 'applies new project milestone to merge_requests with group milestone' do + service.execute + new_milestone = mr_with_group_milestone.reload.milestone + + expect(new_milestone).not_to eq(group_milestone) + expect(new_milestone.title).to eq(group_milestone.title) + expect(new_milestone.project_milestone?).to be_truthy + end + + it 'does not apply new project milestone to issuables with project milestone' do + service.execute + + expect(mr_with_project_milestone.reload.milestone).to eq(project_milestone) + end + + it 'does not recreate missing group milestones that are not applied to issues or merge requests' do + service.execute + new_milestone_title = project.reload.milestones.pluck(:title) + + expect(new_milestone_title).to include(group_milestone.title) + expect(new_milestone_title).not_to include(group_milestone2.title) + end + + context 'when find_or_create_milestone returns nil' do + before do + allow_any_instance_of(Milestones::FindOrCreateService).to receive(:execute).and_return(nil) + end + + it 'removes issues group milestone' do + service.execute + + expect(mr_with_group_milestone.reload.milestone).to be_nil + end + + it 'removes merge requests group milestone' do + service.execute + + expect(issue_with_group_milestone.reload.milestone).to be_nil + end + end + end + + context 'with existing milestone at the new group level' do + let!(:existing_milestone) { create(:milestone, group: new_group, title: group_milestone.title) } + + it 'does not create a new milestone' do + expect { service.execute }.not_to change(project.milestones, :count) + end + + it 'applies existing milestone to issues with group milestone' do + service.execute + + expect(issue_with_group_milestone.reload.milestone).to eq(existing_milestone) + end + + it 'applies existing milestone to merge_requests with group milestone' do + service.execute + + expect(mr_with_group_milestone.reload.milestone).to eq(existing_milestone) + end + end + end + end + + context 'when old_group is not present' do + let(:user) { create(:admin) } + let(:old_group) { project.group } + let(:project) { create(:project, namespace: user.namespace) } + + it 'returns nil' do + expect(described_class.new(user, old_group, project).execute).to be_nil + end + end +end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index ab0e01e27d7..bd6734634cb 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -278,6 +278,7 @@ describe NotificationService, :mailer do notification.new_note(note) should_email(note.author) + expect(find_email_for(note.author)).to have_header('X-GitLab-NotificationReason', 'own_activity') end it_behaves_like 'project emails are disabled' do @@ -335,6 +336,9 @@ describe NotificationService, :mailer do should_not_email(@u_participating) should_not_email(@u_disabled) should_not_email(@u_lazy_participant) + + expect(find_email_for(@u_mentioned)).to have_header('X-GitLab-NotificationReason', 'mentioned') + expect(find_email_for(@u_custom_global)).to have_header('X-GitLab-NotificationReason', '') end end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index a47c10d991a..6b906f9372c 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -259,7 +259,7 @@ describe Projects::TransferService do end context 'missing group labels applied to issues or merge requests' do - it 'delegates tranfer to Labels::TransferService' do + it 'delegates transfer to Labels::TransferService' do group.add_owner(user) expect_any_instance_of(Labels::TransferService).to receive(:execute).once.and_call_original @@ -268,6 +268,17 @@ describe Projects::TransferService do end end + context 'missing group milestones applied to issues or merge requests' do + it 'delegates transfer to Milestones::TransferService' do + group.add_owner(user) + + expect(Milestones::TransferService).to receive(:new).with(user, project.group, project).and_call_original + expect_any_instance_of(Milestones::TransferService).to receive(:execute).once + + transfer_project(project, user, group) + end + end + context 'when hashed storage in use' do let(:hashed_project) { create(:project, :repository, namespace: user.namespace) } diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index c9714964fc9..6ca0a3fa448 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -970,34 +970,6 @@ describe QuickActions::InterpretService do let(:issuable) { merge_request } end - it_behaves_like 'due command' do - let(:content) { '/due 2016-08-28' } - let(:issuable) { issue } - end - - it_behaves_like 'due command' do - let(:content) { '/due tomorrow' } - let(:issuable) { issue } - let(:expected_date) { Date.tomorrow } - end - - it_behaves_like 'due command' do - let(:content) { '/due 5 days from now' } - let(:issuable) { issue } - let(:expected_date) { 5.days.from_now.to_date } - end - - it_behaves_like 'due command' do - let(:content) { '/due in 2 days' } - let(:issuable) { issue } - let(:expected_date) { 2.days.from_now.to_date } - end - - it_behaves_like 'empty command' do - let(:content) { '/due foo bar' } - let(:issuable) { issue } - end - it_behaves_like 'empty command' do let(:content) { '/due 2016-08-28' } let(:issuable) { merge_request } @@ -1131,6 +1103,39 @@ describe QuickActions::InterpretService do end end + context '/due command' do + it 'returns invalid date format message when the due date is invalid' do + issue = build(:issue, project: project) + + _, _, message = service.execute('/due invalid date', issue) + + expect(message).to eq('Failed to set due date because the date format is invalid.') + end + + it_behaves_like 'due command' do + let(:content) { '/due 2016-08-28' } + let(:issuable) { issue } + end + + it_behaves_like 'due command' do + let(:content) { '/due tomorrow' } + let(:issuable) { issue } + let(:expected_date) { Date.tomorrow } + end + + it_behaves_like 'due command' do + let(:content) { '/due 5 days from now' } + let(:issuable) { issue } + let(:expected_date) { 5.days.from_now.to_date } + end + + it_behaves_like 'due command' do + let(:content) { '/due in 2 days' } + let(:issuable) { issue } + let(:expected_date) { 2.days.from_now.to_date } + end + end + context '/copy_metadata command' do let(:todo_label) { create(:label, project: project, title: 'To Do') } let(:inreview_label) { create(:label, project: project, title: 'In Review') } diff --git a/spec/services/releases/create_service_spec.rb b/spec/services/releases/create_service_spec.rb index e26676cdd55..5c9d6537df1 100644 --- a/spec/services/releases/create_service_spec.rb +++ b/spec/services/releases/create_service_spec.rb @@ -72,6 +72,15 @@ describe Releases::CreateService do expect(project.releases.find_by(tag: tag_name).description).to eq(description) end end + + context 'when a passed-in milestone does not exist for this project' do + it 'raises an error saying the milestone is inexistent' do + service = described_class.new(project, user, params.merge!({ milestone: 'v111.0' })) + result = service.execute + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq('Milestone does not exist') + end + end end describe '#find_or_build_release' do @@ -80,5 +89,58 @@ describe Releases::CreateService do expect(project.releases.count).to eq(0) end + + context 'when existing milestone is passed in' do + let(:title) { 'v1.0' } + let(:milestone) { create(:milestone, :active, project: project, title: title) } + let(:params_with_milestone) { params.merge!({ milestone: title }) } + + it 'creates a release and ties this milestone to it' do + service = described_class.new(milestone.project, user, params_with_milestone) + result = service.execute + + expect(project.releases.count).to eq(1) + expect(result[:status]).to eq(:success) + + release = project.releases.last + + expect(release.milestone).to eq(milestone) + end + + context 'when another release was previously created with that same milestone linked' do + it 'also creates another release tied to that same milestone' do + other_release = create(:release, milestone: milestone, project: project, tag: 'v1.0') + service = described_class.new(milestone.project, user, params_with_milestone) + service.execute + release = project.releases.last + + expect(release.milestone).to eq(milestone) + expect(other_release.milestone).to eq(milestone) + expect(release.id).not_to eq(other_release.id) + end + end + end + + context 'when no milestone is passed in' do + it 'creates a release without a milestone tied to it' do + expect(params.key? :milestone).to be_falsey + service.execute + release = project.releases.last + expect(release.milestone).to be_nil + end + + it 'does not create any new MilestoneRelease object' do + expect { service.execute }.not_to change { MilestoneRelease.count } + end + end + + context 'when an empty value is passed as a milestone' do + it 'creates a release without a milestone tied to it' do + service = described_class.new(project, user, params.merge!({ milestone: '' })) + service.execute + release = project.releases.last + expect(release.milestone).to be_nil + end + end end end diff --git a/spec/services/releases/destroy_service_spec.rb b/spec/services/releases/destroy_service_spec.rb index f4c901e6585..c3172e5edbc 100644 --- a/spec/services/releases/destroy_service_spec.rb +++ b/spec/services/releases/destroy_service_spec.rb @@ -57,5 +57,15 @@ describe Releases::DestroyService do http_status: 403) end end + + context 'when a milestone is tied to the release' do + let!(:milestone) { create(:milestone, :active, project: project, title: 'v1.0') } + let!(:release) { create(:release, milestone: milestone, project: project, tag: tag) } + + it 'destroys the release but leave the milestone intact' do + expect { subject }.not_to change { Milestone.count } + expect(milestone.reload).to be_persisted + end + end end end diff --git a/spec/services/releases/update_service_spec.rb b/spec/services/releases/update_service_spec.rb index 14e6a5f13c8..944f3d8c9ad 100644 --- a/spec/services/releases/update_service_spec.rb +++ b/spec/services/releases/update_service_spec.rb @@ -48,5 +48,42 @@ describe Releases::UpdateService do it_behaves_like 'a failed update' end + + context 'when a milestone is passed in' do + let(:old_title) { 'v1.0' } + let(:new_title) { 'v2.0' } + let(:milestone) { create(:milestone, project: project, title: old_title) } + let(:new_milestone) { create(:milestone, project: project, title: new_title) } + let(:params_with_milestone) { params.merge!({ milestone: new_title }) } + + before do + release.milestone = milestone + release.save! + + described_class.new(new_milestone.project, user, params_with_milestone).execute + release.reload + end + + it 'updates the related milestone accordingly' do + expect(release.milestone.title).to eq(new_title) + end + end + + context "when an 'empty' milestone is passed in" do + let(:milestone) { create(:milestone, project: project, title: 'v1.0') } + let(:params_with_empty_milestone) { params.merge!({ milestone: '' }) } + + before do + release.milestone = milestone + release.save! + + described_class.new(milestone.project, user, params_with_empty_milestone).execute + release.reload + end + + it 'removes the old milestone and does not associate any new milestone' do + expect(release.milestone).to be_nil + end + end end end diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index f5c6e972953..d72e5cc2b16 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -19,6 +19,7 @@ describe SystemHooksService do it { expect(event_data(project, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) } it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) } it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) } + it { expect(event_data(project_member, :update)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) } it { expect(event_data(key, :create)).to include(:username, :key, :id) } it { expect(event_data(key, :destroy)).to include(:username, :key, :id) } it { expect(event_data(deploy_key, :create)).to include(:key, :id) } @@ -70,6 +71,13 @@ describe SystemHooksService do ) end + it do + expect(event_data(group_member, :update)).to include( + :event_name, :created_at, :updated_at, :group_name, :group_path, + :group_id, :user_id, :user_username, :user_name, :user_email, :group_access + ) + end + it 'includes the correct project visibility level' do data = event_data(project, :create) @@ -145,6 +153,7 @@ describe SystemHooksService do it { expect(event_name(project, :update)).to eq "project_update" } it { expect(event_name(project_member, :create)).to eq "user_add_to_team" } it { expect(event_name(project_member, :destroy)).to eq "user_remove_from_team" } + it { expect(event_name(project_member, :update)).to eq "user_update_for_team" } it { expect(event_name(key, :create)).to eq 'key_create' } it { expect(event_name(key, :destroy)).to eq 'key_destroy' } it { expect(event_name(group, :create)).to eq 'group_create' } @@ -152,6 +161,7 @@ describe SystemHooksService do it { expect(event_name(group, :rename)).to eq 'group_rename' } it { expect(event_name(group_member, :create)).to eq 'user_add_to_group' } it { expect(event_name(group_member, :destroy)).to eq 'user_remove_from_group' } + it { expect(event_name(group_member, :update)).to eq 'user_update_for_group' } end def event_data(*args) diff --git a/spec/services/update_merge_request_metrics_service_spec.rb b/spec/services/update_merge_request_metrics_service_spec.rb index 12a2b287c72..bb07dfa1a0e 100644 --- a/spec/services/update_merge_request_metrics_service_spec.rb +++ b/spec/services/update_merge_request_metrics_service_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe MergeRequestMetricsService do let(:metrics) { create(:merge_request).metrics } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index bd504f1553b..47f09bf14d0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -109,6 +109,7 @@ RSpec.configure do |config| config.include PolicyHelpers, type: :policy config.include MemoryUsageHelper config.include ExpectRequestWithStatus, type: :request + config.include RailsHelpers if ENV['CI'] # This includes the first try, i.e. tests will be run 4 times before failing. @@ -148,6 +149,12 @@ RSpec.configure do |config| .with(:force_autodevops_on_by_default, anything) .and_return(false) + # Stub this call due to being an expensive operation + # It can be reenabled for specific tests via: + # + # allow(DetectRepositoryLanguagesWorker).to receive(:perform_async).and_call_original + allow(DetectRepositoryLanguagesWorker).to receive(:perform_async).and_return(true) + Gitlab::ThreadMemoryCache.cache_backend.clear end diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index d86371d70b9..beb346b2855 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -34,6 +34,14 @@ module GraphqlHelpers end end + # BatchLoader::GraphQL returns a wrapper, so we need to :sync in order + # to get the actual values + def batch_sync(max_queries: nil, &blk) + result = batch(max_queries: nil, &blk) + + result.is_a?(Array) ? result.map(&:sync) : result&.sync + end + def graphql_query_for(name, attributes = {}, fields = nil) <<~QUERY { @@ -114,7 +122,11 @@ module GraphqlHelpers FIELDS end - def all_graphql_fields_for(class_name, parent_types = Set.new) + def all_graphql_fields_for(class_name, parent_types = Set.new, max_depth: 3) + # pulling _all_ fields can generate a _huge_ query (like complexity 180,000), + # and significantly increase spec runtime. so limit the depth by default + return if max_depth <= 0 + allow_unlimited_graphql_complexity allow_unlimited_graphql_depth @@ -133,9 +145,9 @@ module GraphqlHelpers if nested_fields?(field) fields = - all_graphql_fields_for(singular_field_type, parent_types | [type]) + all_graphql_fields_for(singular_field_type, parent_types | [type], max_depth: max_depth - 1) - "#{name} { #{fields} }" + "#{name} { #{fields} }" unless fields.blank? else name end diff --git a/spec/support/helpers/project_forks_helper.rb b/spec/support/helpers/project_forks_helper.rb index b2d22853e4c..90d0d1845fc 100644 --- a/spec/support/helpers/project_forks_helper.rb +++ b/spec/support/helpers/project_forks_helper.rb @@ -45,7 +45,7 @@ module ProjectForksHelper # not reset the @exists variable of this forked_project.repository # so we have to explicitly call this method to clear the @exists variable. # of the instance we're returning here. - forked_project.repository.after_import + forked_project.repository.expire_content_cache end forked_project diff --git a/spec/support/helpers/rails_helpers.rb b/spec/support/helpers/rails_helpers.rb new file mode 100644 index 00000000000..e1875b2fb15 --- /dev/null +++ b/spec/support/helpers/rails_helpers.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module RailsHelpers + def stub_rails_env(env_name) + allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new(env_name)) + end +end diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index a4acf76e1a3..8ca362ce2df 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -244,7 +244,6 @@ module TestEnv FileUtils.mkdir_p(target_repo_path) FileUtils.cp_r("#{File.expand_path(bare_repo)}/.", target_repo_path) FileUtils.chmod_R 0755, target_repo_path - set_repo_refs(target_repo_path, refs) end def create_bare_repository(path) diff --git a/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb b/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb index 323d1c51ffd..9a60825855f 100644 --- a/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb +++ b/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb @@ -46,7 +46,7 @@ shared_context 'exposing regular notes on a noteable in GraphQL' do discussions { edges { node { - #{all_graphql_fields_for('Discussion')} + #{all_graphql_fields_for('Discussion', max_depth: 4)} } } } diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb index e6e4d9504d9..2f3fc7839c1 100644 --- a/spec/tasks/gitlab/gitaly_rake_spec.rb +++ b/spec/tasks/gitlab/gitaly_rake_spec.rb @@ -57,7 +57,7 @@ describe 'gitlab:gitaly namespace rake task' do stub_env('CI', false) FileUtils.mkdir_p(clone_path) expect(Dir).to receive(:chdir).with(clone_path).and_call_original - allow(Rails.env).to receive(:test?).and_return(false) + stub_rails_env('development') end context 'gmake is available' do @@ -93,7 +93,7 @@ describe 'gitlab:gitaly namespace rake task' do end before do - allow(Rails.env).to receive(:test?).and_return(true) + stub_rails_env('test') end it 'calls make in the gitaly directory with --no-deployment flag for bundle' do diff --git a/spec/uploaders/gitlab_uploader_spec.rb b/spec/uploaders/gitlab_uploader_spec.rb index 44718ed1212..3bee4875348 100644 --- a/spec/uploaders/gitlab_uploader_spec.rb +++ b/spec/uploaders/gitlab_uploader_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' require 'carrierwave/storage/fog' describe GitlabUploader do diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb index 6bad5d49b1c..0cf486c7087 100644 --- a/spec/uploaders/object_storage_spec.rb +++ b/spec/uploaders/object_storage_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' require 'carrierwave/storage/fog' class Implementation < GitlabUploader diff --git a/spec/uploaders/records_uploads_spec.rb b/spec/uploaders/records_uploads_spec.rb index 6134137d2b7..046ad3406d9 100644 --- a/spec/uploaders/records_uploads_spec.rb +++ b/spec/uploaders/records_uploads_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe RecordsUploads do let!(:uploader) do diff --git a/spec/uploaders/uploader_helper_spec.rb b/spec/uploaders/uploader_helper_spec.rb index fd6712d4645..7c9ec8ff59c 100644 --- a/spec/uploaders/uploader_helper_spec.rb +++ b/spec/uploaders/uploader_helper_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe UploaderHelper do let(:uploader) do diff --git a/spec/validators/addressable_url_validator_spec.rb b/spec/validators/addressable_url_validator_spec.rb index 387e84b2d04..6927a1f67a1 100644 --- a/spec/validators/addressable_url_validator_spec.rb +++ b/spec/validators/addressable_url_validator_spec.rb @@ -92,6 +92,15 @@ describe AddressableUrlValidator do expect(badge.errors).to be_empty expect(badge.link_url).to eq('https://127.0.0.1') end + + it 'allows urls that cannot be resolved' do + stub_env('RSPEC_ALLOW_INVALID_URLS', 'false') + badge.link_url = 'http://foobar.x' + + subject + + expect(badge.errors).to be_empty + end end context 'when message is set' do @@ -312,4 +321,32 @@ describe AddressableUrlValidator do end end end + + context 'when dns_rebind_protection is' do + let(:not_resolvable_url) { 'http://foobar.x' } + let(:validator) { described_class.new(attributes: [:link_url], dns_rebind_protection: dns_value) } + + before do + stub_env('RSPEC_ALLOW_INVALID_URLS', 'false') + badge.link_url = not_resolvable_url + + subject + end + + context 'true' do + let(:dns_value) { true } + + it 'raises error' do + expect(badge.errors).to be_present + end + end + + context 'false' do + let(:dns_value) { false } + + it 'allows urls that cannot be resolved' do + expect(badge.errors).to be_empty + end + end + end end diff --git a/spec/views/devise/shared/_signin_box.html.haml_spec.rb b/spec/views/devise/shared/_signin_box.html.haml_spec.rb index 5d521d18c70..0563984a03c 100644 --- a/spec/views/devise/shared/_signin_box.html.haml_spec.rb +++ b/spec/views/devise/shared/_signin_box.html.haml_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe 'devise/shared/_signin_box' do describe 'Crowd form' do diff --git a/spec/views/help/index.html.haml_spec.rb b/spec/views/help/index.html.haml_spec.rb index 257991549a9..f25e05dca7f 100644 --- a/spec/views/help/index.html.haml_spec.rb +++ b/spec/views/help/index.html.haml_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe 'help/index' do include StubVersion diff --git a/spec/views/help/instance_configuration.html.haml_spec.rb b/spec/views/help/instance_configuration.html.haml_spec.rb index ceb7e34a540..18628ddebf7 100644 --- a/spec/views/help/instance_configuration.html.haml_spec.rb +++ b/spec/views/help/instance_configuration.html.haml_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe 'help/instance_configuration' do describe 'General Sections:' do diff --git a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb index ff2d491539b..697e44be065 100644 --- a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb +++ b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb @@ -17,10 +17,16 @@ describe 'projects/settings/ci_cd/_autodevops_form' do context 'when the project has an available kubernetes cluster' do let!(:cluster) { create(:cluster, cluster_type: :project_type, projects: [project]) } - it 'does not show a warning message' do + it 'does not show a warning message about Kubernetes cluster' do render expect(rendered).not_to have_text('You must add a Kubernetes cluster') end + + it 'shows a warning message about base domain' do + render + + expect(rendered).to have_text('You must add a base domain to your Kubernetes cluster in order for your deployment strategy to work.') + end end end diff --git a/spec/views/projects/settings/operations/show.html.haml_spec.rb b/spec/views/projects/settings/operations/show.html.haml_spec.rb index 6762fe3759b..ece9c16650f 100644 --- a/spec/views/projects/settings/operations/show.html.haml_spec.rb +++ b/spec/views/projects/settings/operations/show.html.haml_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require 'spec_helper' -require 'rails_helper' describe 'projects/settings/operations/show' do let(:project) { create(:project) } diff --git a/spec/workers/repository_remove_remote_worker_spec.rb b/spec/workers/repository_remove_remote_worker_spec.rb index 6eba5c50960..afac9212fab 100644 --- a/spec/workers/repository_remove_remote_worker_spec.rb +++ b/spec/workers/repository_remove_remote_worker_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe RepositoryRemoveRemoteWorker do include ExclusiveLeaseHelpers diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb index 66d517332ba..7bc499d480d 100644 --- a/spec/workers/repository_update_remote_mirror_worker_spec.rb +++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe RepositoryUpdateRemoteMirrorWorker, :clean_gitlab_redis_shared_state do subject { described_class.new } diff --git a/spec/workers/upload_checksum_worker_spec.rb b/spec/workers/upload_checksum_worker_spec.rb index 7202c8001b4..ad054f0ff72 100644 --- a/spec/workers/upload_checksum_worker_spec.rb +++ b/spec/workers/upload_checksum_worker_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe UploadChecksumWorker do describe '#perform' do diff --git a/yarn.lock b/yarn.lock index 4cf3a9584f1..92da409f544 100644 --- a/yarn.lock +++ b/yarn.lock @@ -996,10 +996,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.71.0.tgz#c8e6e8f500ea91e5cbba4ac08df533fb2e622a00" integrity sha512-kkeNic/FFwaqKnzwio4NE7whBOZ/toRJ8cS0587DBotajAzSYhph5ij4TCY2GTjPa33zIJ5OUr/k90C0Kr71hQ== -"@gitlab/ui@5.20.2": - version "5.20.2" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.20.2.tgz#a51270d5a521e71059c5fd05f86cfc835f5e28ae" - integrity sha512-TSaD5Cz0YXBTsRtQwsa7LbS2O5h0CL3YkdYmBKrMZkphL76xQaN08ZImkQ5Xl8cD1ZiWN2CsTvoUbF19UP2V1w== +"@gitlab/ui@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.21.0.tgz#975cf0bca3d16dd080d67ed392b9d24cd64695ac" + integrity sha512-8TMVM+pJXf7omHgKMMZ1FiltuyMOTwfQ3iFgorQzcuhio9u35DJpWi45S2TF7m6CrlpJi7dMX3BsXLbF7ViSUw== dependencies: "@babel/standalone" "^7.0.0" "@gitlab/vue-toasted" "^1.2.1" @@ -2993,6 +2993,13 @@ commander@~2.17.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== +commander@~2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" + integrity sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q= + dependencies: + graceful-readlink ">= 1.0.0" + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -3890,6 +3897,11 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +deep-extend@~0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.1.tgz#b894a9dd90d3023fbf1c55a394fb858eb2066f1f" + integrity sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w== + deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -4671,9 +4683,11 @@ eslint-scope@^4.0.0: estraverse "^4.1.1" eslint-utils@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512" - integrity sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q== + version "1.4.2" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab" + integrity sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q== + dependencies: + eslint-visitor-keys "^1.0.0" eslint-visitor-keys@^1.0.0: version "1.0.0" @@ -4909,7 +4923,7 @@ exports-loader@^0.7.0: express@^4.16.2, express@^4.16.3: version "4.16.3" - resolved "http://registry.npmjs.org/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" + resolved "https://registry.npmjs.org/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" integrity sha1-avilAjUNsyRuzEvs9rWjTSL37VM= dependencies: accepts "~1.3.5" @@ -5416,6 +5430,11 @@ get-stdin@^7.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6" integrity sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ== +get-stdin@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" + integrity sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g= + get-stream@3.0.0, get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -5532,7 +5551,7 @@ glob-to-regexp@^0.4.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -"glob@5 - 7", glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1: +"glob@5 - 7", glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1, glob@~7.1.2: version "7.1.4" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== @@ -5724,6 +5743,11 @@ graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b" integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg== +"graceful-readlink@>= 1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= + graphlibrary@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/graphlibrary/-/graphlibrary-2.2.0.tgz#017a14899775228dec4497a39babfdd6bf56eac6" @@ -7744,11 +7768,21 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.differencewith@~4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.differencewith/-/lodash.differencewith-4.5.0.tgz#bafafbc918b55154e179176a00bb0aefaac854b7" + integrity sha1-uvr7yRi1UVTheRdqALsK76rIVLc= + lodash.escaperegexp@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= +lodash.flatten@~4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" @@ -7933,6 +7967,17 @@ markdown-escapes@^1.0.0: resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.2.tgz#e639cbde7b99c841c0bacc8a07982873b46d2122" integrity sha512-lbRZ2mE3Q9RtLjxZBZ9+IMl68DKIXaVAhwvwn9pmjnPLS0h/6kyBMgNhqi1xFJ/2yv6cSyv0jbiZavZv93JkkA== +markdown-it@9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-9.0.1.tgz#aafe363c43718720b6575fd10625cde6e4ff2d47" + integrity sha512-XC9dMBHg28Xi7y5dPuLjM61upIGPJG8AiHNHYqIaXER2KNnn7eKnM5/sF0ImNnyoV224Ogn9b1Pck8VH4k0bxw== + dependencies: + argparse "^1.0.7" + entities "~1.1.1" + linkify-it "^2.0.0" + mdurl "^1.0.1" + uc.micro "^1.0.5" + markdown-it@^8.4.2: version "8.4.2" resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54" @@ -7949,6 +7994,29 @@ markdown-table@^1.1.0: resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.2.tgz#c78db948fa879903a41bce522e3b96f801c63786" integrity sha512-NcWuJFHDA8V3wkDgR/j4+gZx+YQwstPgfQDV8ndUeWWzta3dnDTBxpVzqS9lkmJAuV5YX35lmyojl6HO5JXAgw== +markdownlint-cli@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/markdownlint-cli/-/markdownlint-cli-0.18.0.tgz#bd1cee72739049d42dcea5f6db0c0f57c6eb8096" + integrity sha512-mQ2zvjMLoy0P2kb9Y03SqC24WPH4fTRN0/CyCorB122c4Chg9vWJKgUKBz3KR7swpzqmlI0SYq/7Blbqe4kb2g== + dependencies: + commander "~2.9.0" + deep-extend "~0.5.1" + get-stdin "~5.0.1" + glob "~7.1.2" + js-yaml "^3.13.1" + lodash.differencewith "~4.5.0" + lodash.flatten "~4.4.0" + markdownlint "~0.16.0" + minimatch "~3.0.4" + rc "~1.2.7" + +markdownlint@~0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/markdownlint/-/markdownlint-0.16.0.tgz#69f73cc755a44231fbe5dc7c37a5909cedc0ac6e" + integrity sha512-Zo+iPezP3eM6lLhKepkUw+X98H44lipIdx4d6faaugfB0+7VuDB3R0hXmx7z9F1N3/ypn46oOFgAD9iF++Ie6A== + dependencies: + markdown-it "9.0.1" + marked@^0.3.12, marked@~0.3.6: version "0.3.19" resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790" @@ -8233,7 +8301,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: +minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2, minimatch@~3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -8304,7 +8372,7 @@ mixin-deep@^1.2.0: mkdirp@0.5.x, mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" - resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= dependencies: minimist "0.0.8" @@ -9931,7 +9999,7 @@ raw-loader@^1.0.0: loader-utils "^1.1.0" schema-utils "^1.0.0" -rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: +rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@~1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== |