diff options
93 files changed, 756 insertions, 528 deletions
@@ -28,7 +28,7 @@ gem 'sprockets', '~> 3.7.0' gem 'view_component', '~> 2.82.0' # Supported DBs -gem 'pg', '~> 1.4.6' +gem 'pg', '~> 1.5.3' gem 'neighbor', '~> 0.2.3' diff --git a/Gemfile.checksum b/Gemfile.checksum index 0af30976daa..5471e4e7ea7 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -439,10 +439,10 @@ {"name":"parslet","version":"1.8.2","platform":"ruby","checksum":"08d1ab3721cd3f175bfbee8788b2ddff71f92038f2d69bd65454c22bb9fbd98a"}, {"name":"pastel","version":"0.8.0","platform":"ruby","checksum":"481da9fb7d2f6e6b1a08faf11fa10363172dc40fd47848f096ae21209f805a75"}, {"name":"peek","version":"1.1.0","platform":"ruby","checksum":"d6501ead8cde46d8d8ed0d59eb6f0ba713d0a41c11a2c4a81447b2dce37b3ecc"}, -{"name":"pg","version":"1.4.6","platform":"ruby","checksum":"d98f3dcb4a6ae29780a2219340cb0e55dbafbb7eb4ccc2b99f892f2569a7a61e"}, -{"name":"pg","version":"1.4.6","platform":"x64-mingw-ucrt","checksum":"1efb4f932d5579b87b1c37e0ef49d101925d4f0e3fcf282569aed0382a522b68"}, -{"name":"pg","version":"1.4.6","platform":"x64-mingw32","checksum":"26c4a010fe2cefe61f56f0c4ba9a86b6e99d0965af100f30eaba1602a167af56"}, -{"name":"pg","version":"1.4.6","platform":"x86-mingw32","checksum":"14376f8a122ec58b9e1b4123774e7eafb59222544b7c6cfaa379c6ef28785ae6"}, +{"name":"pg","version":"1.5.3","platform":"ruby","checksum":"6b9ee5e2d5aee975588232c41f8203e766157cf71dba54ee85b343a45ced9bfd"}, +{"name":"pg","version":"1.5.3","platform":"x64-mingw-ucrt","checksum":"1f2a6b2afaf0ccb8afe8b6a00131bce8151fbd6e8826b2d944288f6f2b615389"}, +{"name":"pg","version":"1.5.3","platform":"x64-mingw32","checksum":"ab7f5f3020323094a2b16f9638166b04c103e152a9079a1b8e795f4bf79765e0"}, +{"name":"pg","version":"1.5.3","platform":"x86-mingw32","checksum":"aa6ddda9887462d30a6d49d875eb9d27fca8cdb7185103b650e7351b38f15ddf"}, {"name":"pg_query","version":"2.2.1","platform":"ruby","checksum":"6086972bbf4eab86d8425b35f14ca8b6fe41e4341423582801c1ec86ff5f8cea"}, {"name":"plist","version":"3.6.0","platform":"ruby","checksum":"f468bcf6b72ec6d1585ed6744eb4817c1932a5bf91895ed056e69b7f12ca10f2"}, {"name":"png_quantizator","version":"0.2.1","platform":"ruby","checksum":"6023d4d064125c3a7e02929c95b7320ed6ac0d7341f9e8de0c9ea6576ef3106b"}, diff --git a/Gemfile.lock b/Gemfile.lock index 82ac228c6cd..e2054d98a13 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1136,7 +1136,7 @@ GEM tty-color (~> 0.5) peek (1.1.0) railties (>= 4.0.0) - pg (1.4.6) + pg (1.5.3) pg_query (2.2.1) google-protobuf (>= 3.19.2) plist (3.6.0) @@ -1866,7 +1866,7 @@ DEPENDENCIES parallel (~> 1.19) parslet (~> 1.8) peek (~> 1.1) - pg (~> 1.4.6) + pg (~> 1.5.3) pg_query (~> 2.2, >= 2.2.1) png_quantizator (~> 0.2.1) premailer-rails (~> 1.10.3) diff --git a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue index 9db3011ba5d..2cf71de7ea2 100644 --- a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue +++ b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue @@ -3,6 +3,7 @@ * Render modal to confirm rollback/redeploy. */ import { GlModal, GlSprintf, GlLink } from '@gitlab/ui'; +import * as Sentry from '@sentry/browser'; import { escape } from 'lodash'; import csrf from '~/lib/utils/csrf'; import { __, s__, sprintf } from '~/locale'; @@ -125,10 +126,17 @@ export default { }, onOk() { if (this.graphql) { - this.$apollo.mutate({ - mutation: rollbackEnvironment, - variables: { environment: this.environment }, - }); + this.$apollo + .mutate({ + mutation: rollbackEnvironment, + variables: { environment: this.environment }, + }) + .then(() => { + this.$emit('rollback'); + }) + .catch((e) => { + Sentry.captureException(e); + }); } else { eventHub.$emit('rollbackEnvironment', this.environment); } diff --git a/app/assets/javascripts/environments/environment_details/constants.js b/app/assets/javascripts/environments/environment_details/constants.js index 07579092e23..e7b10aed20d 100644 --- a/app/assets/javascripts/environments/environment_details/constants.js +++ b/app/assets/javascripts/environments/environment_details/constants.js @@ -1,6 +1,7 @@ import { __, s__ } from '~/locale'; import { helpPagePath } from '~/helpers/help_page_helper'; +export const ENVIRONMENT_DETAILS_QUERY_POLLING_INTERVAL = 3000; export const ENVIRONMENT_DETAILS_PAGE_SIZE = 20; export const ENVIRONMENT_DETAILS_TABLE_FIELDS = [ { diff --git a/app/assets/javascripts/environments/environment_details/index.vue b/app/assets/javascripts/environments/environment_details/index.vue index f91e68e793f..1b7320df674 100644 --- a/app/assets/javascripts/environments/environment_details/index.vue +++ b/app/assets/javascripts/environments/environment_details/index.vue @@ -1,6 +1,7 @@ <script> import { GlLoadingIcon } from '@gitlab/ui'; import { logError } from '~/lib/logger'; +import { toggleQueryPollingByVisibility, etagQueryHeaders } from '~/graphql_shared/utils'; import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue'; import environmentDetailsQuery from '../graphql/queries/environment_details.query.graphql'; import environmentToRollbackQuery from '../graphql/queries/environment_to_rollback.query.graphql'; @@ -8,7 +9,10 @@ import { convertToDeploymentTableRow } from '../helpers/deployment_data_transfor import EmptyState from './empty_state.vue'; import DeploymentsTable from './deployments_table.vue'; import Pagination from './pagination.vue'; -import { ENVIRONMENT_DETAILS_PAGE_SIZE } from './constants'; +import { + ENVIRONMENT_DETAILS_QUERY_POLLING_INTERVAL, + ENVIRONMENT_DETAILS_PAGE_SIZE, +} from './constants'; export default { components: { @@ -18,6 +22,7 @@ export default { EmptyState, GlLoadingIcon, }, + inject: { graphqlEtagKey: { default: '' } }, props: { projectFullPath: { type: String, @@ -51,6 +56,12 @@ export default { before: this.before, }; }, + pollInterval() { + return this.graphqlEtagKey ? ENVIRONMENT_DETAILS_QUERY_POLLING_INTERVAL : null; + }, + context() { + return etagQueryHeaders('environment_details', this.graphqlEtagKey); + }, }, environmentToRollback: { query: environmentToRollbackQuery, @@ -136,6 +147,19 @@ export default { this.isPrefetchingPages = false; }, }, + mounted() { + if (this.graphqlEtagKey) { + toggleQueryPollingByVisibility( + this.$apollo.queries.project, + ENVIRONMENT_DETAILS_QUERY_POLLING_INTERVAL, + ); + } + }, + methods: { + resetPage() { + this.$router.push({ query: {} }); + }, + }, }; </script> <template> @@ -150,6 +174,6 @@ export default { <pagination :page-info="pageInfo" :disabled="isPaginationDisabled" /> </div> <empty-state v-if="!isDeploymentTableShown && !isLoading" /> - <confirm-rollback-modal :environment="environmentToRollback" graphql /> + <confirm-rollback-modal :environment="environmentToRollback" graphql @rollback="resetPage" /> </div> </template> diff --git a/app/assets/javascripts/environments/graphql/client.js b/app/assets/javascripts/environments/graphql/client.js index bb6f57e7e80..b7754558b10 100644 --- a/app/assets/javascripts/environments/graphql/client.js +++ b/app/assets/javascripts/environments/graphql/client.js @@ -13,6 +13,7 @@ import typeDefs from './typedefs.graphql'; export const apolloProvider = (endpoint) => { const defaultClient = createDefaultClient(resolvers(endpoint), { typeDefs, + useGet: true, }); const { cache } = defaultClient; diff --git a/app/assets/javascripts/environments/mount_show.js b/app/assets/javascripts/environments/mount_show.js index 5e812c85c96..364f68cefb7 100644 --- a/app/assets/javascripts/environments/mount_show.js +++ b/app/assets/javascripts/environments/mount_show.js @@ -94,6 +94,7 @@ export const initPage = async () => { router, provide: { projectPath: dataSet.projectFullPath, + graphqlEtagKey: dataSet.graphqlEtagPath, }, render(createElement) { return createElement('router-view'); diff --git a/app/assets/javascripts/graphql_shared/utils.js b/app/assets/javascripts/graphql_shared/utils.js index 806e89d6e9f..198d9f980f0 100644 --- a/app/assets/javascripts/graphql_shared/utils.js +++ b/app/assets/javascripts/graphql_shared/utils.js @@ -1,4 +1,5 @@ import { isArray } from 'lodash'; +import Visibility from 'visibilityjs'; /** * Ids generated by GraphQL endpoints are usually in the format @@ -116,3 +117,29 @@ export const convertNodeIdsFromGraphQLIds = (nodes) => { export const getNodesOrDefault = (queryData, nodesField = 'nodes') => { return queryData?.[nodesField] ?? []; }; + +export const toggleQueryPollingByVisibility = (queryRef, interval = 10000) => { + const stopStartQuery = (query) => { + if (!Visibility.hidden()) { + query.startPolling(interval); + } else { + query.stopPolling(); + } + }; + + stopStartQuery(queryRef); + Visibility.change(stopStartQuery.bind(null, queryRef)); +}; + +export const etagQueryHeaders = (featureCorrelation, etagResource = '') => { + return { + fetchOptions: { + method: 'GET', + }, + headers: { + 'X-GITLAB-GRAPHQL-FEATURE-CORRELATION': featureCorrelation, + 'X-GITLAB-GRAPHQL-RESOURCE-ETAG': etagResource, + 'X-Requested-With': 'XMLHttpRequest', + }, + }; +}; diff --git a/app/assets/javascripts/pipelines/components/graph/utils.js b/app/assets/javascripts/pipelines/components/graph/utils.js index 54985a24593..c888c8a5537 100644 --- a/app/assets/javascripts/pipelines/components/graph/utils.js +++ b/app/assets/javascripts/pipelines/components/graph/utils.js @@ -1,11 +1,12 @@ import { isEmpty } from 'lodash'; -import Visibility from 'visibilityjs'; -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { getIdFromGraphQLId, etagQueryHeaders } from '~/graphql_shared/utils'; import { reportToSentry } from '../../utils'; import { listByLayers } from '../parsing_utils'; import { unwrapStagesWithNeedsAndLookup } from '../unwrapping_utils'; import { beginPerfMeasure, finishPerfMeasureAndSend } from './perf_utils'; +export { toggleQueryPollingByVisibility } from '~/graphql_shared/utils'; + const addMulti = (mainPipelineProjectPath, linkedPipeline) => { return { ...linkedPipeline, @@ -35,18 +36,8 @@ const calculatePipelineLayersInfo = (pipeline, componentName, metricsPath) => { return layers; }; -const getQueryHeaders = (etagResource) => { - return { - fetchOptions: { - method: 'GET', - }, - headers: { - 'X-GITLAB-GRAPHQL-FEATURE-CORRELATION': 'verify/ci/pipeline-graph', - 'X-GITLAB-GRAPHQL-RESOURCE-ETAG': etagResource, - 'X-Requested-With': 'XMLHttpRequest', - }, - }; -}; +const getQueryHeaders = (etagResource) => + etagQueryHeaders('verify/ci/pipeline-graph', etagResource); const serializeGqlErr = (gqlError) => { const { locations = [], message = '', path = [] } = gqlError; @@ -80,19 +71,6 @@ const serializeLoadErrors = (errors) => { return message; }; -const toggleQueryPollingByVisibility = (queryRef, interval = 10000) => { - const stopStartQuery = (query) => { - if (!Visibility.hidden()) { - query.startPolling(interval); - } else { - query.stopPolling(); - } - }; - - stopStartQuery(queryRef); - Visibility.change(stopStartQuery.bind(null, queryRef)); -}; - const transformId = (linkedPipeline) => { return { ...linkedPipeline, id: getIdFromGraphQLId(linkedPipeline.id) }; }; @@ -133,7 +111,6 @@ export { getQueryHeaders, serializeGqlErr, serializeLoadErrors, - toggleQueryPollingByVisibility, unwrapPipelineData, validateConfigPaths, }; diff --git a/app/assets/javascripts/super_sidebar/components/menu_section.vue b/app/assets/javascripts/super_sidebar/components/menu_section.vue index 86bf3837c10..49e481feb04 100644 --- a/app/assets/javascripts/super_sidebar/components/menu_section.vue +++ b/app/assets/javascripts/super_sidebar/components/menu_section.vue @@ -1,4 +1,5 @@ <script> +import { kebabCase } from 'lodash'; import { GlCollapse, GlIcon } from '@gitlab/ui'; import NavItem from './nav_item.vue'; @@ -27,7 +28,7 @@ export default { tag: { type: String, required: false, - default: 'section', + default: 'div', }, }, data() { @@ -36,6 +37,13 @@ export default { }; }, computed: { + buttonProps() { + return { + 'aria-controls': this.itemId, + 'aria-expanded': String(this.isExpanded), + 'data-qa-menu-item': this.item.title, + }; + }, collapseIcon() { return this.isExpanded ? 'chevron-up' : 'chevron-down'; }, @@ -44,16 +52,12 @@ export default { 'gl-bg-t-gray-a-08': this.isActive, }; }, - linkProps() { - return { - 'aria-controls': this.itemId, - 'aria-expanded': String(this.isExpanded), - 'data-qa-menu-item': this.item.title, - }; - }, isActive() { return !this.isExpanded && this.item.is_active; }, + itemId() { + return kebabCase(this.item.title); + }, }, watch: { isExpanded(newIsExpanded) { @@ -66,10 +70,11 @@ export default { <template> <component :is="tag"> <button - class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-py-3 gl-px-0 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! gl-appearance-none gl-border-0 gl-bg-transparent gl-text-left gl-w-full gl-focus--focus" + class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-mb-1 gl-py-3 gl-px-0 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! gl-appearance-none gl-border-0 gl-bg-transparent gl-text-left gl-w-full gl-focus--focus" :class="computedLinkClasses" data-qa-selector="menu_section_button" :data-qa-section-name="item.title" + v-bind="buttonProps" @click="isExpanded = !isExpanded" > <span @@ -94,21 +99,22 @@ export default { </button> <gl-collapse + :id="itemId" v-model="isExpanded" :aria-label="item.title" + class="gl-list-style-none gl-p-0 gl-m-0" data-qa-selector="menu_section" :data-qa-section-name="item.title" + tag="ul" > <slot> - <ul class="gl-list-style-none gl-p-0 gl-m-0"> - <nav-item - v-for="subItem of item.items" - :key="`${item.title}-${subItem.title}`" - :item="subItem" - @pin-add="(itemId) => $emit('pin-add', itemId)" - @pin-remove="(itemId) => $emit('pin-remove', itemId)" - /> - </ul> + <nav-item + v-for="subItem of item.items" + :key="`${item.title}-${subItem.title}`" + :item="subItem" + @pin-add="(itemId) => $emit('pin-add', itemId)" + @pin-remove="(itemId) => $emit('pin-remove', itemId)" + /> </slot> </gl-collapse> <hr v-if="separated" aria-hidden="true" class="gl-mx-4 gl-my-2" /> diff --git a/app/assets/javascripts/super_sidebar/components/nav_item.vue b/app/assets/javascripts/super_sidebar/components/nav_item.vue index abf5d73069c..1d8c5b30e13 100644 --- a/app/assets/javascripts/super_sidebar/components/nav_item.vue +++ b/app/assets/javascripts/super_sidebar/components/nav_item.vue @@ -1,5 +1,4 @@ <script> -import { kebabCase } from 'lodash'; import { GlButton, GlIcon, GlBadge } from '@gitlab/ui'; import { s__ } from '~/locale'; import { @@ -47,9 +46,6 @@ export default { }, }, computed: { - itemId() { - return kebabCase(this.item.title); - }, pillData() { return this.item.pill_count; }, diff --git a/app/assets/javascripts/super_sidebar/components/pinned_section.vue b/app/assets/javascripts/super_sidebar/components/pinned_section.vue index b93e8e2ed1d..4fc86e41ef2 100644 --- a/app/assets/javascripts/super_sidebar/components/pinned_section.vue +++ b/app/assets/javascripts/super_sidebar/components/pinned_section.vue @@ -89,8 +89,8 @@ export default { @pin-remove="(itemId) => $emit('pin-remove', itemId)" /> </draggable> - <div v-else class="gl-text-secondary gl-font-sm gl-py-3" style="margin-left: 2.5rem"> + <li v-else class="gl-text-secondary gl-font-sm gl-py-3" style="margin-left: 2.5rem"> {{ $options.i18n.emptyHint }} - </div> + </li> </menu-section> </template> diff --git a/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue b/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue index 16923e44a77..12abd727ef0 100644 --- a/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue +++ b/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue @@ -132,20 +132,18 @@ export default { <template> <nav class="gl-p-2 gl-relative"> - <section v-if="staticItems.length > 0"> + <template v-if="staticItems.length > 0"> <ul class="gl-p-0 gl-m-0"> <nav-item v-for="item in staticItems" :key="item.id" :item="item" is-static /> </ul> <hr aria-hidden="true" class="gl-my-2 gl-mx-4" /> - </section> - + </template> <pinned-section v-if="supportsPins" :items="pinnedItems" @pin-remove="destroyPin" @pin-reorder="movePin" /> - <ul class="gl-p-0 gl-list-style-none"> <component :is="item.items && item.items.length ? 'MenuSection' : 'NavItem'" diff --git a/app/assets/stylesheets/framework/super_sidebar.scss b/app/assets/stylesheets/framework/super_sidebar.scss index 3973be54795..9da860c838b 100644 --- a/app/assets/stylesheets/framework/super_sidebar.scss +++ b/app/assets/stylesheets/framework/super_sidebar.scss @@ -128,7 +128,7 @@ @include gl-font-sm; } - .context-switcher-toggle { + .gl-new-dropdown-custom-toggle .context-switcher-toggle { &[aria-expanded='true'] { background-color: $t-gray-a-08; } diff --git a/app/controllers/admin/instance_review_controller.rb b/app/controllers/admin/instance_review_controller.rb index cc801bce5b7..87fb1fa1a66 100644 --- a/app/controllers/admin/instance_review_controller.rb +++ b/app/controllers/admin/instance_review_controller.rb @@ -5,7 +5,7 @@ class Admin::InstanceReviewController < Admin::ApplicationController urgency :low def index - redirect_to("#{Gitlab::SubscriptionPortal.subscriptions_instance_review_url}?#{instance_review_params}") + redirect_to("#{subscription_portal_instance_review_url}?#{instance_review_params}") end def instance_review_params diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index 80bc92c0b69..4a9282432fd 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -27,9 +27,8 @@ class Projects::RepositoriesController < Projects::ApplicationController end def archive - return render_404 if html_request? - set_cache_headers + return if archive_not_modified? send_git_archive @repository, **repo_params diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb index 2b3700a9f21..d914c63b753 100644 --- a/app/helpers/environment_helper.rb +++ b/app/helpers/environment_helper.rb @@ -88,7 +88,8 @@ module EnvironmentHelper environment_terminal_path: terminal_project_environment_path(project, environment), has_terminals: environment.has_terminals?, is_environment_available: environment.available?, - auto_stop_at: environment.auto_stop_at + auto_stop_at: environment.auto_stop_at, + graphql_etag_key: environment.etag_cache_key } end diff --git a/app/models/design_management/git_repository.rb b/app/models/design_management/git_repository.rb index 92db82f7bd1..38c457c7991 100644 --- a/app/models/design_management/git_repository.rb +++ b/app/models/design_management/git_repository.rb @@ -12,28 +12,6 @@ module DesignManagement /#{DesignManagement.designs_directory}/* filter=lfs diff=lfs merge=lfs -text GA - # Passing the `project` explicitly saves on one query on the `project` table - # in Mutations::DesignManagement::Delete - - def initialize(project) - @project = project - - full_path = @project.full_path + Gitlab::GlRepository::DESIGN.path_suffix - disk_path = @project.disk_path + Gitlab::GlRepository::DESIGN.path_suffix - - # Ideally a DesignManagement::Repository, not a project would be - # the container to this Git repository. - # See https://gitlab.com/gitlab-org/gitlab/-/issues/394816. - - super( - full_path, - @project, - shard: @project.repository_storage, - disk_path: disk_path, - repo_type: Gitlab::GlRepository::DESIGN - ) - end - # Override of a method called on Repository instances but sent via # method_missing to Gitlab::Git::Repository where it is defined def info_attributes diff --git a/app/models/design_management/repository.rb b/app/models/design_management/repository.rb index c1b14a875ac..33c5dc15fa4 100644 --- a/app/models/design_management/repository.rb +++ b/app/models/design_management/repository.rb @@ -3,22 +3,34 @@ module DesignManagement class Repository < ApplicationRecord include ::Gitlab::Utils::StrongMemoize + include HasRepository belongs_to :project, inverse_of: :design_management_repository validates :project, presence: true, uniqueness: true - # This is so that git_repo is initialized once `project` has been - # set. If it is not set after intialization and saving the record - # fails for some reason, the first call to `git_repo`` (initiated by - # `delegate_missing_to`) will throw an error because project would - # be missing. - after_initialize :git_repo + delegate :lfs_enabled?, :storage, :repository_storage, to: :project - delegate_missing_to :git_repo + def repository + ::DesignManagement::GitRepository.new( + full_path, + self, + shard: repository_storage, + disk_path: disk_path, + repo_type: repo_type + ) + end + strong_memoize_attr :repository + + def full_path + project.full_path + repo_type.path_suffix + end + + def disk_path + project.disk_path + repo_type.path_suffix + end - def git_repo - project ? GitRepository.new(project) : nil + def repo_type + Gitlab::GlRepository::DESIGN end - strong_memoize_attr :git_repo end end diff --git a/app/models/project.rb b/app/models/project.rb index 2f976e98003..5271fbb138d 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1202,6 +1202,10 @@ class Project < ApplicationRecord @repository ||= Gitlab::GlRepository::PROJECT.repository_for(self) end + def design_management_repository + super || create_design_management_repository + end + def design_repository strong_memoize(:design_repository) do Gitlab::GlRepository::DESIGN.repository_for(self) diff --git a/app/models/user.rb b/app/models/user.rb index 9e542e9f451..70d52ff801d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1647,9 +1647,19 @@ class User < ApplicationRecord end # rubocop: enable CodeReuse/ServiceClass + DELETION_DELAY_IN_DAYS = 7.days + def delete_async(deleted_by:, params: {}) - block if params[:hard_delete] - DeleteUserWorker.perform_async(deleted_by.id, id, params.to_h) + is_deleting_own_record = deleted_by.id == id + + if is_deleting_own_record && ::Feature.enabled?(:delay_delete_own_user) + block + DeleteUserWorker.perform_in(DELETION_DELAY_IN_DAYS, deleted_by.id, id, params.to_h) + else + block if params[:hard_delete] + + DeleteUserWorker.perform_async(deleted_by.id, id, params.to_h) + end end # rubocop: disable CodeReuse/ServiceClass diff --git a/app/services/ci/job_token_scope/add_project_service.rb b/app/services/ci/job_token_scope/add_project_service.rb index 704aa28f8c5..8fb543a2796 100644 --- a/app/services/ci/job_token_scope/add_project_service.rb +++ b/app/services/ci/job_token_scope/add_project_service.rb @@ -29,3 +29,5 @@ module Ci end end end + +Ci::JobTokenScope::AddProjectService.prepend_mod_with('Ci::JobTokenScope::AddProjectService') diff --git a/app/services/ci/job_token_scope/remove_project_service.rb b/app/services/ci/job_token_scope/remove_project_service.rb index 864f9318c68..d6a2defd5b9 100644 --- a/app/services/ci/job_token_scope/remove_project_service.rb +++ b/app/services/ci/job_token_scope/remove_project_service.rb @@ -31,3 +31,5 @@ module Ci end end end + +Ci::JobTokenScope::RemoveProjectService.prepend_mod_with('Ci::JobTokenScope::RemoveProjectService') diff --git a/app/services/merge_requests/after_create_service.rb b/app/services/merge_requests/after_create_service.rb index f3b1c663fa2..f174778e12e 100644 --- a/app/services/merge_requests/after_create_service.rb +++ b/app/services/merge_requests/after_create_service.rb @@ -7,6 +7,7 @@ module MergeRequests def execute(merge_request) merge_request.ensure_merge_request_diff + execute_hooks(merge_request) prepare_for_mergeability(merge_request) prepare_merge_request(merge_request) diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 0d59e442dce..ec8a17162ca 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -26,6 +26,10 @@ module MergeRequests end def execute_hooks(merge_request, action = 'open', old_rev: nil, old_associations: {}) + # NOTE: Due to the async merge request diffs generation, we need to skip this for CreateService and execute it in + # AfterCreateService instead so that the webhook consumers receive the update when diffs are ready. + return if merge_request.skip_ensure_merge_request_diff + merge_data = Gitlab::Lazy.new { hook_data(merge_request, action, old_rev: old_rev, old_associations: old_associations) } merge_request.project.execute_hooks(merge_data, :merge_request_hooks) merge_request.project.execute_integrations(merge_data, :merge_request_hooks) diff --git a/app/views/admin/application_settings/appearances/show.html.haml b/app/views/admin/application_settings/appearances/show.html.haml index cd255d961f4..82fdd13672a 100644 --- a/app/views/admin/application_settings/appearances/show.html.haml +++ b/app/views/admin/application_settings/appearances/show.html.haml @@ -1,4 +1,5 @@ - page_title _("Appearance") - add_page_specific_style 'page_bundles/settings' +- @force_desktop_expanded_sidebar = true = render 'form' diff --git a/app/views/admin/application_settings/ci_cd.html.haml b/app/views/admin/application_settings/ci_cd.html.haml index c2dc3c3707e..a9a16f72ebe 100644 --- a/app/views/admin/application_settings/ci_cd.html.haml +++ b/app/views/admin/application_settings/ci_cd.html.haml @@ -1,6 +1,7 @@ - breadcrumb_title _("CI/CD") - page_title _("CI/CD") - add_page_specific_style 'page_bundles/settings' +- @force_desktop_expanded_sidebar = true %section.settings.no-animate#js-ci-cd-variables{ class: ('expanded' if expanded_by_default?) } .settings-header diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml index a7c80abdbc9..e6c27c1bc84 100644 --- a/app/views/admin/application_settings/general.html.haml +++ b/app/views/admin/application_settings/general.html.haml @@ -1,6 +1,7 @@ - breadcrumb_title _("General") - page_title _("General") - add_page_specific_style 'page_bundles/settings' +- @force_desktop_expanded_sidebar = true %section.settings.as-visibility-access.no-animate#js-visibility-settings{ class: ('expanded' if expanded_by_default?), data: { testid: 'admin-visibility-access-settings' } } .settings-header diff --git a/app/views/admin/application_settings/integrations.html.haml b/app/views/admin/application_settings/integrations.html.haml index 396e6f3e7d6..68c62eb98ee 100644 --- a/app/views/admin/application_settings/integrations.html.haml +++ b/app/views/admin/application_settings/integrations.html.haml @@ -1,6 +1,7 @@ - breadcrumb_title s_('Integrations|Instance-level integration management') - page_title s_('Integrations|Instance-level integration management') - add_page_specific_style 'page_bundles/settings' +- @force_desktop_expanded_sidebar = true %h3= s_('Integrations|Instance-level integration management') diff --git a/app/views/admin/application_settings/metrics_and_profiling.html.haml b/app/views/admin/application_settings/metrics_and_profiling.html.haml index d0fb275e53a..8bc5d5cbaa6 100644 --- a/app/views/admin/application_settings/metrics_and_profiling.html.haml +++ b/app/views/admin/application_settings/metrics_and_profiling.html.haml @@ -3,6 +3,7 @@ - breadcrumb_title _("Metrics and profiling") - page_title _("Metrics and profiling") - add_page_specific_style 'page_bundles/settings' +- @force_desktop_expanded_sidebar = true %section.settings.as-prometheus.no-animate#js-prometheus-settings{ class: ('expanded' if expanded_by_default?) } .settings-header diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml index 32b10fd36e8..1809496bb9f 100644 --- a/app/views/admin/application_settings/network.html.haml +++ b/app/views/admin/application_settings/network.html.haml @@ -1,6 +1,7 @@ - breadcrumb_title _("Network") - page_title _("Network") - add_page_specific_style 'page_bundles/settings' +- @force_desktop_expanded_sidebar = true %section.settings.as-performance.no-animate#js-performance-settings{ class: ('expanded' if expanded_by_default?) } .settings-header diff --git a/app/views/admin/application_settings/preferences.html.haml b/app/views/admin/application_settings/preferences.html.haml index 3843fc8e863..ab59e05c10f 100644 --- a/app/views/admin/application_settings/preferences.html.haml +++ b/app/views/admin/application_settings/preferences.html.haml @@ -1,6 +1,7 @@ - breadcrumb_title _("Preferences") - page_title _("Preferences") - add_page_specific_style 'page_bundles/settings' +- @force_desktop_expanded_sidebar = true %section.settings.as-email.no-animate#js-email-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'email_content' } } .settings-header diff --git a/app/views/admin/application_settings/reporting.html.haml b/app/views/admin/application_settings/reporting.html.haml index 0046275c7d1..6ea2fb80505 100644 --- a/app/views/admin/application_settings/reporting.html.haml +++ b/app/views/admin/application_settings/reporting.html.haml @@ -1,6 +1,7 @@ - breadcrumb_title _("Reporting") - page_title _("Reporting") - add_page_specific_style 'page_bundles/settings' +- @force_desktop_expanded_sidebar = true %section.settings.as-spam.no-animate#js-spam-settings{ class: ('expanded' if expanded_by_default?) } .settings-header diff --git a/app/views/admin/application_settings/repository.html.haml b/app/views/admin/application_settings/repository.html.haml index 518b40a0326..1907544ea14 100644 --- a/app/views/admin/application_settings/repository.html.haml +++ b/app/views/admin/application_settings/repository.html.haml @@ -1,6 +1,7 @@ - breadcrumb_title _("Repository") - page_title _("Repository") - add_page_specific_style 'page_bundles/settings' +- @force_desktop_expanded_sidebar = true %section.settings.as-default-branch-name.no-animate#js-default-branch-name{ class: ('expanded' if expanded_by_default?) } .settings-header diff --git a/app/views/admin/application_settings/service_usage_data.html.haml b/app/views/admin/application_settings/service_usage_data.html.haml index af646d79c29..ead2b2fd666 100644 --- a/app/views/admin/application_settings/service_usage_data.html.haml +++ b/app/views/admin/application_settings/service_usage_data.html.haml @@ -4,6 +4,7 @@ - page_title name - add_page_specific_style 'page_bundles/settings' - payload_class = 'js-service-ping-payload' +- @force_desktop_expanded_sidebar = true %section.js-search-settings-section %h3= name diff --git a/app/workers/delete_user_worker.rb b/app/workers/delete_user_worker.rb index bca156ff84c..6a375a0cdd4 100644 --- a/app/workers/delete_user_worker.rb +++ b/app/workers/delete_user_worker.rb @@ -11,8 +11,13 @@ class DeleteUserWorker # rubocop:disable Scalability/IdempotentWorker loggable_arguments 2 def perform(current_user_id, delete_user_id, options = {}) - delete_user = User.find(delete_user_id) - current_user = User.find(current_user_id) + delete_user = User.find_by_id(delete_user_id) + return unless delete_user.present? + + return if delete_user.banned? && ::Feature.enabled?(:delay_delete_own_user) + + current_user = User.find_by_id(current_user_id) + return unless current_user.present? Users::DestroyService.new(current_user).execute(delete_user, options.symbolize_keys) rescue Gitlab::Access::AccessDeniedError => e diff --git a/config/feature_flags/development/delay_delete_own_user.yml b/config/feature_flags/development/delay_delete_own_user.yml new file mode 100644 index 00000000000..030ccd29c00 --- /dev/null +++ b/config/feature_flags/development/delay_delete_own_user.yml @@ -0,0 +1,8 @@ +--- +name: delay_delete_own_user +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118887 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/409025 +milestone: '16.0' +type: development +group: group::anti-abuse +default_enabled: false diff --git a/config/routes/directs.rb b/config/routes/directs.rb index f28750b5c2e..407e2a9d8a5 100644 --- a/config/routes/directs.rb +++ b/config/routes/directs.rb @@ -3,3 +3,4 @@ # Custom URL definitions for the Community Edition. draw 'directs/milestone' +draw 'directs/subscription_portal' diff --git a/config/routes/directs/subscription_portal.rb b/config/routes/directs/subscription_portal.rb new file mode 100644 index 00000000000..188725d16c1 --- /dev/null +++ b/config/routes/directs/subscription_portal.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +direct :subscription_portal_staging do + ENV.fetch('STAGING_CUSTOMER_PORTAL_URL', 'https://customers.staging.gitlab.com') +end + +direct :subscription_portal do + default_subscriptions_url = if ::Gitlab.dev_or_test_env? + subscription_portal_staging_url + else + 'https://customers.gitlab.com' + end + + ENV.fetch('CUSTOMER_PORTAL_URL', default_subscriptions_url) +end + +direct :subscription_portal_instance_review do + Addressable::URI.join(subscription_portal_url, '/instance_review').to_s +end diff --git a/config/routes/project.rb b/config/routes/project.rb index ef5f95eee75..93505d84aa6 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -28,7 +28,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do # Begin of the /-/ scope. # Use this scope for all new project routes. scope '-' do - get 'archive/*id', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+?/ }, to: 'repositories#archive', as: 'archive' + get 'archive/*id', format: true, constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+?/ }, to: 'repositories#archive', as: 'archive' get 'metrics(/:dashboard_path)', constraints: { dashboard_path: /.+\.yml/ }, to: 'metrics_dashboard#show', as: :metrics_dashboard, format: false get 'metrics(/:dashboard_path)/panel/new', constraints: { dashboard_path: /.+\.yml/ }, diff --git a/data/removals/16_0/16-0-pull-thru-cache-container-registry.yml b/data/removals/16_0/16-0-pull-thru-cache-container-registry.yml new file mode 100644 index 00000000000..88f8c31eeef --- /dev/null +++ b/data/removals/16_0/16-0-pull-thru-cache-container-registry.yml @@ -0,0 +1,11 @@ +# REQUIRED FIELDS +# +- title: "Container Registry pull-through cache is removed" # (required) Clearly explain the change. For example, "The `confidential` field for a `Note` is removed" or "CI/CD job names are limited to 250 characters." + announcement_milestone: "15.8" # (required) The milestone when this feature was deprecated. + removal_milestone: "16.0" # (required) The milestone when this feature is being removed. + breaking_change: true # (required) Change to false if this is not a breaking change. + reporter: trizzi # (required) GitLab username of the person reporting the removal + stage: Package # (required) String value of the stage that the feature was created in. e.g., Growth + issue_url: https://gitlab.com/gitlab-org/container-registry/-/issues/937 # (required) Link to the deprecation issue in GitLab + body: | # (required) Do not modify this line, instead modify the lines below. + The Container Registry [pull-through cache](https://docs.docker.com/registry/recipes/mirror/) was deprecated in GitLab 15.8 and removed in GitLab 16.0. This feature is part of the upstream [Docker Distribution project](https://github.com/distribution/distribution) but we are removing that code in favor of the GitLab Dependency Proxy. Use the GitLab Dependency Proxy to proxy and cache container images from Docker Hub. diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md index eb0576d0c05..5f00aa355be 100644 --- a/doc/development/documentation/styleguide/word_list.md +++ b/doc/development/documentation/styleguide/word_list.md @@ -57,6 +57,7 @@ Instead of: - In GitLab 14.4 and above... - In GitLab 14.4 and higher... +- In GitLab 14.4 and newer... ## access level @@ -423,6 +424,7 @@ Use: Instead of: - In GitLab 14.1 and lower. +- In GitLab 14.1 and older. ## easily @@ -766,6 +768,7 @@ Instead of: - In GitLab 14.1 and higher... - In GitLab 14.1 and above... +- In GitLab 14.1 and newer... ## list @@ -814,6 +817,7 @@ Use: Instead of: - In GitLab 14.1 and lower. +- In GitLab 14.1 and older. ## Maintainer @@ -919,6 +923,20 @@ When the variable is **optional**: - You can set the variable. +## newer + +Do not use **newer** when talking about version numbers. + +Use: + +- In GitLab 14.4 and later... + +Instead of: + +- In GitLab 14.4 and higher... +- In GitLab 14.4 and above... +- In GitLab 14.4 and newer... + ## normal, normally Don't use **normal** to mean the usual, typical, or standard way of doing something. @@ -949,6 +967,19 @@ Instead of: - Note that you can change the settings. +## older + +Do not use **older** when talking about version numbers. + +Use: + +- In GitLab 14.1 and earlier. + +Instead of: + +- In GitLab 14.1 and lower. +- In GitLab 14.1 and older. + ## Omnibus GitLab When referring to the installation method that uses the Linux package, refer to it diff --git a/doc/policy/alpha-beta-support.md b/doc/policy/alpha-beta-support.md index 2eb07e1ddaf..e142fe9e908 100644 --- a/doc/policy/alpha-beta-support.md +++ b/doc/policy/alpha-beta-support.md @@ -20,8 +20,8 @@ Support is not provided for features listed as "Experimental" or "Alpha" or any - Can be removed at any time. - Data loss may occur. - Documentation may not exist or just be in a blog format. -- Behind a feature flag that is on by default and the UI reflects Experiment status. -- Behind a toggle that is off by default and the UI reflects Experiment status. +- Behind a feature flag that is on by default and the [UI reflects Experiment status](https://design.gitlab.com/usability/feature-management#highlighting-feature-versions). +- Behind a toggle that is off by default and the [UI reflects Experiment status](https://design.gitlab.com/usability/feature-management#highlighting-feature-versions). - Feedback issue to engage with team. - UX not finalized, might be just quick action access. - Not announced in a release post. @@ -38,8 +38,8 @@ Commercially-reasonable efforts are made to provide limited support for features - Support on a commercially-reasonable effort basis. - Documentation reflects Beta status. - UX complete or near completion. -- Behind a feature flag that is on by default and the UI reflects Beta status. -- Behind a toggle that is off by default and the UI reflects Beta status. +- Behind a feature flag that is on by default and the [UI reflects Beta status](https://design.gitlab.com/usability/feature-management#highlighting-feature-versions). +- Behind a toggle that is off by default and the [UI reflects Beta status](https://design.gitlab.com/usability/feature-management#highlighting-feature-versions). - Can be announced in a release post that reflects Beta status. ## Generally Available (GA) @@ -58,3 +58,8 @@ We will get higher quality (more diverse) feedback if people from different orga We've also learned that internal only as a state slows us down more than it speeds us up. Release the experiment instead of testing internally or waiting for the feature to be in a Beta state. The experimental features are only shown when people/organizations opt-in to experiments, we are allowed to make mistakes here and literally experiment. + +## All features are in production + +All features that are available on GitLab.com are considered "in production." +Because all Experiment, Beta, and Generally Available features are available on GitLab.com, they are all considered to be in production. diff --git a/doc/update/removals.md b/doc/update/removals.md index b92c90379cc..83a17c2e116 100644 --- a/doc/update/removals.md +++ b/doc/update/removals.md @@ -59,6 +59,14 @@ The Azure Storage Driver used to write to `//` as the default root directory. Th In GitLab 16.0, the new default configuration for the storage driver uses `trimlegacyrootprefix: true`, and `/` is the default root directory. You can set your configuration to `trimlegacyrootprefix: false` if needed, to revert to the previous behavior. +### Container Registry pull-through cache is removed + +WARNING: +This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/). +Review the details carefully before upgrading. + +The Container Registry [pull-through cache](https://docs.docker.com/registry/recipes/mirror/) was deprecated in GitLab 15.8 and removed in GitLab 16.0. This feature is part of the upstream [Docker Distribution project](https://github.com/distribution/distribution) but we are removing that code in favor of the GitLab Dependency Proxy. Use the GitLab Dependency Proxy to proxy and cache container images from Docker Hub. + ### Project REST API field `operations_access_level` removed WARNING: diff --git a/lib/feature/gitaly.rb b/lib/feature/gitaly.rb index fd798862fa8..eadf98e090e 100644 --- a/lib/feature/gitaly.rb +++ b/lib/feature/gitaly.rb @@ -53,11 +53,19 @@ module Feature end def project_actor(container) - ::Feature::Gitaly::ActorWrapper.new(::Project, container.id) if container.is_a?(::Project) + return actor_wrapper(::Project, container.id) if container.is_a?(::Project) + return actor_wrapper(::Project, container.project.id) if container.is_a?(DesignManagement::Repository) end def group_actor(container) - ::Feature::Gitaly::ActorWrapper.new(::Group, container.namespace_id) if container.is_a?(::Project) + return actor_wrapper(::Group, container.namespace_id) if container.is_a?(::Project) + return actor_wrapper(::Group, container.project.namespace_id) if container.is_a?(DesignManagement::Repository) + end + + private + + def actor_wrapper(actor_type, id) + ::Feature::Gitaly::ActorWrapper.new(actor_type, id) end end end diff --git a/lib/gitlab/ci/reports/codequality_mr_diff.rb b/lib/gitlab/ci/reports/codequality_mr_diff.rb index bfa7b05e4a9..0595b6f966a 100644 --- a/lib/gitlab/ci/reports/codequality_mr_diff.rb +++ b/lib/gitlab/ci/reports/codequality_mr_diff.rb @@ -30,20 +30,9 @@ module Gitlab codequality_files[degradation.dig(:location, :path)] << { line: degradation.dig(:location, :lines, :begin) || degradation.dig(:location, :positions, :begin, :line), description: degradation[:description], - severity: degradation[:severity], - engine_name: degradation[:engine_name], - categories: degradation[:categories], - content: convert_body(degradation[:content]), - location: degradation[:location], - other_locations: degradation[:other_locations], - type: degradation[:type] + severity: degradation[:severity] } end - - def convert_body(content) - content["body"] = ::MarkupHelper.markdown(content["body"]) - content - end end end end diff --git a/lib/gitlab/ci/templates/Jobs/Secret-Detection.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Secret-Detection.latest.gitlab-ci.yml index f343dfaa28f..56a8ad794dc 100644 --- a/lib/gitlab/ci/templates/Jobs/Secret-Detection.latest.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Secret-Detection.latest.gitlab-ci.yml @@ -8,7 +8,7 @@ variables: SECURE_ANALYZERS_PREFIX: "$CI_TEMPLATE_REGISTRY_HOST/security-products" SECRET_DETECTION_IMAGE_SUFFIX: "" - SECRETS_ANALYZER_VERSION: "4" + SECRETS_ANALYZER_VERSION: "5" SECRET_DETECTION_EXCLUDED_PATHS: "" .secret-analyzer: diff --git a/lib/gitlab/etag_caching/router/graphql.rb b/lib/gitlab/etag_caching/router/graphql.rb index b53164ac94c..92b21a0859d 100644 --- a/lib/gitlab/etag_caching/router/graphql.rb +++ b/lib/gitlab/etag_caching/router/graphql.rb @@ -22,6 +22,11 @@ module Gitlab %r(\Aon_demand_scan/counts/), 'on_demand_scans', 'dynamic_application_security_testing' + ], + [ + %r(\A/projects/.+/-/environments.json\z), + 'environment_details', + 'continuous_delivery' ] ].map { |attrs| build_graphql_route(*attrs) }.freeze diff --git a/lib/gitlab/git_access_design.rb b/lib/gitlab/git_access_design.rb index bf89c01305a..fb8df0d217a 100644 --- a/lib/gitlab/git_access_design.rb +++ b/lib/gitlab/git_access_design.rb @@ -4,6 +4,23 @@ module Gitlab class GitAccessDesign < GitAccess extend ::Gitlab::Utils::Override + # TODO Re-factor so that correct container is passed to the constructor + # and this method can be removed from here + # https://gitlab.com/gitlab-org/gitlab/-/issues/409454 + def initialize( + actor, container, protocol, authentication_abilities:, repository_path: nil, redirected_path: nil, + auth_result_type: nil) + super( + actor, + select_container(container), + protocol, + authentication_abilities: authentication_abilities, + repository_path: repository_path, + redirected_path: redirected_path, + auth_result_type: auth_result_type + ) + end + def check(_cmd, _changes) check_protocol! check_can_create_design! @@ -18,6 +35,10 @@ module Gitlab private + def select_container(container) + container.is_a?(::DesignManagement::Repository) ? container.project : container + end + def check_protocol! if protocol != 'web' raise ::Gitlab::GitAccess::ForbiddenError, "Designs are only accessible using the web interface" diff --git a/lib/gitlab/gl_repository.rb b/lib/gitlab/gl_repository.rb index efdb205b8eb..268d5d3e564 100644 --- a/lib/gitlab/gl_repository.rb +++ b/lib/gitlab/gl_repository.rb @@ -34,8 +34,9 @@ module Gitlab DESIGN = ::Gitlab::GlRepository::RepoType.new( name: :design, access_checker_class: ::Gitlab::GitAccessDesign, - repository_resolver: -> (project) { ::DesignManagement::Repository.new(project: project) }, - suffix: :design + repository_resolver: -> (project) { project.design_management_repository.repository }, + suffix: :design, + container_class: DesignManagement::Repository ).freeze TYPES = { diff --git a/lib/gitlab/gl_repository/repo_type.rb b/lib/gitlab/gl_repository/repo_type.rb index 7792ef55b28..26b0ff86f67 100644 --- a/lib/gitlab/gl_repository/repo_type.rb +++ b/lib/gitlab/gl_repository/repo_type.rb @@ -55,11 +55,11 @@ module Gitlab def repository_for(container) return unless container - repository_resolver.call(container) + repository_resolver.call(select_container(container)) end def project_for(container) - return container unless project_resolver + return select_container(container) unless project_resolver project_resolver.call(container) end @@ -74,6 +74,10 @@ module Gitlab private + def select_container(container) + container.is_a?(::DesignManagement::Repository) ? container.project : container + end + def default_container_class Project end diff --git a/lib/gitlab/patch/draw_route.rb b/lib/gitlab/patch/draw_route.rb index 61b25065e8f..67da6c9c943 100644 --- a/lib/gitlab/patch/draw_route.rb +++ b/lib/gitlab/patch/draw_route.rb @@ -27,7 +27,7 @@ module Gitlab def draw_route(path) if File.exist?(path) - instance_eval(File.read(path)) + instance_eval(File.read(path), path.to_s) true else false diff --git a/lib/gitlab/subscription_portal.rb b/lib/gitlab/subscription_portal.rb index 723b8265479..1d9ecb624b2 100644 --- a/lib/gitlab/subscription_portal.rb +++ b/lib/gitlab/subscription_portal.rb @@ -2,22 +2,6 @@ module Gitlab module SubscriptionPortal - def self.default_subscriptions_url - if ::Gitlab.dev_or_test_env? - 'https://customers.staging.gitlab.com' - else - 'https://customers.gitlab.com' - end - end - - def self.subscriptions_url - ENV.fetch('CUSTOMER_PORTAL_URL', default_subscriptions_url) - end - - def self.payment_form_url - "#{self.subscriptions_url}/payment_forms/cc_validation" - end - def self.payment_validation_form_id "payment_method_validation" end @@ -26,58 +10,6 @@ module Gitlab "cc_registration_validation" end - def self.registration_validation_form_url - "#{self.subscriptions_url}/payment_forms/cc_registration_validation" - end - - def self.subscriptions_comparison_url - 'https://about.gitlab.com/pricing/gitlab-com/feature-comparison' - end - - def self.subscriptions_graphql_url - "#{self.subscriptions_url}/graphql" - end - - def self.subscriptions_more_minutes_url - "#{self.subscriptions_url}/buy_pipeline_minutes" - end - - def self.subscriptions_more_storage_url - "#{self.subscriptions_url}/buy_storage" - end - - def self.subscriptions_manage_url - "#{self.subscriptions_url}/subscriptions" - end - - def self.subscriptions_gitlab_plans_url - "#{self.subscriptions_url}/gitlab_plans" - end - - def self.subscriptions_instance_review_url - "#{self.subscriptions_url}/instance_review" - end - - def self.add_extra_seats_url(group_id) - "#{self.subscriptions_url}/gitlab/namespaces/#{group_id}/extra_seats" - end - - def self.upgrade_subscription_url(group_id, plan_id) - "#{self.subscriptions_url}/gitlab/namespaces/#{group_id}/upgrade/#{plan_id}" - end - - def self.renew_subscription_url(group_id) - "#{self.subscriptions_url}/gitlab/namespaces/#{group_id}/renew" - end - - def self.subscriptions_legacy_sign_in_url - "#{self.subscriptions_url}/customers/sign_in?legacy=true" - end - - def self.edit_account_url - "#{self.subscriptions_url}/customers/edit" - end - def self.subscription_portal_admin_email ENV.fetch('SUBSCRIPTION_PORTAL_ADMIN_EMAIL', 'gl_com_api@gitlab.com') end @@ -93,13 +25,8 @@ module Gitlab end Gitlab::SubscriptionPortal.prepend_mod -Gitlab::SubscriptionPortal::SUBSCRIPTIONS_URL = Gitlab::SubscriptionPortal.subscriptions_url.freeze -Gitlab::SubscriptionPortal::SUBSCRIPTIONS_LEGACY_SIGN_IN_URL = Gitlab::SubscriptionPortal.subscriptions_legacy_sign_in_url.freeze -Gitlab::SubscriptionPortal::PAYMENT_FORM_URL = Gitlab::SubscriptionPortal.payment_form_url.freeze Gitlab::SubscriptionPortal::PAYMENT_VALIDATION_FORM_ID = Gitlab::SubscriptionPortal.payment_validation_form_id.freeze Gitlab::SubscriptionPortal::RENEWAL_SERVICE_EMAIL = Gitlab::SubscriptionPortal.renewal_service_email.freeze -Gitlab::SubscriptionPortal::REGISTRATION_VALIDATION_FORM_URL = Gitlab::SubscriptionPortal.registration_validation_form_url.freeze Gitlab::SubscriptionPortal::REGISTRATION_VALIDATION_FORM_ID = Gitlab::SubscriptionPortal.registration_validation_form_id.freeze Gitlab::SubscriptionPortal::SUBSCRIPTION_PORTAL_ADMIN_EMAIL = Gitlab::SubscriptionPortal.subscription_portal_admin_email.freeze Gitlab::SubscriptionPortal::SUBSCRIPTION_PORTAL_ADMIN_TOKEN = Gitlab::SubscriptionPortal.subscription_portal_admin_token.freeze -Gitlab::SubscriptionPortal::SUBSCRIPTIONS_MANAGE_URL = ::Gitlab::SubscriptionPortal.subscriptions_manage_url.freeze diff --git a/package.json b/package.json index c3bb53b8f28..9ffe1afd370 100644 --- a/package.json +++ b/package.json @@ -278,7 +278,7 @@ "timezone-mock": "^1.0.8", "vue-loader-vue3": "npm:vue-loader@17", "vue-test-utils-compat": "^0.0.11", - "webpack-dev-server": "4.13.2", + "webpack-dev-server": "4.13.3", "xhr-mock": "^2.5.1", "yarn-check-webpack-plugin": "^1.2.0", "yarn-deduplicate": "^6.0.0" diff --git a/spec/controllers/admin/instance_review_controller_spec.rb b/spec/controllers/admin/instance_review_controller_spec.rb index 6eab135b3a6..f0225a71e00 100644 --- a/spec/controllers/admin/instance_review_controller_spec.rb +++ b/spec/controllers/admin/instance_review_controller_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Admin::InstanceReviewController, feature_category: :service_ping include UsageDataHelpers let(:admin) { create(:admin) } - let(:subscriptions_instance_review_url) { Gitlab::SubscriptionPortal.subscriptions_instance_review_url } + let(:subscriptions_instance_review_url) { ::Gitlab::Routing.url_helpers.subscription_portal_instance_review_url } before do sign_in(admin) diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index 8186176a46b..0efed45336f 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -106,19 +106,11 @@ RSpec.describe Projects::RepositoriesController, feature_category: :source_code_ end end - context "when the request format is HTML" do - it "renders 404" do - get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'master' }, format: "html" - - expect(response).to have_gitlab_http_status(:not_found) - end - end - describe 'rate limiting' do it 'rate limits user when thresholds hit' do allow(Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true) - get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'master' }, format: "html" + get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'master' }, format: "zip" expect(response).to have_gitlab_http_status(:too_many_requests) end diff --git a/spec/factories/design_management/repositories.rb b/spec/factories/design_management/repositories.rb new file mode 100644 index 00000000000..d903fd88c13 --- /dev/null +++ b/spec/factories/design_management/repositories.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :design_management_repository, class: 'DesignManagement::Repository' do + project + end +end diff --git a/spec/features/nav/pinned_nav_items_spec.rb b/spec/features/nav/pinned_nav_items_spec.rb index 2047eaa9659..ea0404e7cf6 100644 --- a/spec/features/nav/pinned_nav_items_spec.rb +++ b/spec/features/nav/pinned_nav_items_spec.rb @@ -94,7 +94,7 @@ RSpec.describe 'Navigation menu item pinning', :js, feature_category: :navigatio it 'can be unpinned from within its section' do section = find("button", text: 'Operate') - within(section.sibling('div')) do + within(section.sibling('ul')) do remove_pin('Terraform modules') end diff --git a/spec/fixtures/pipeline_artifacts/code_quality_mr_diff.json b/spec/fixtures/pipeline_artifacts/code_quality_mr_diff.json index 97026cf1180..5489330fc1d 100644 --- a/spec/fixtures/pipeline_artifacts/code_quality_mr_diff.json +++ b/spec/fixtures/pipeline_artifacts/code_quality_mr_diff.json @@ -3,78 +3,21 @@ "files": { "file_a.rb": [ { - "categories": [ - "Complexity" - ], "line": 10, "description": "Avoid parameter lists longer than 5 parameters. [12/5]", - "severity": "major", - "location": { - "path": "file_a.rb", - "lines": { - "begin": 10, - "end": 10 - } - }, - "other_locations": [ - - ], - "content": { - "body": "" - }, - "type": "issue", - "engine_name": "structure" + "severity": "major" }, { - "categories": [ - "Complexity" - ], "line": 10, "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", - "severity": "minor", - "location": { - "path": "file_a.rb", - "lines": { - "begin": 10, - "end": 10 - } - }, - "other_locations": [ - - ], - "content": { - "body": "" - }, - "type": "issue", - "engine_name": "structure" + "severity": "minor" } ], "file_b.rb": [ { - "categories": [ - "Complexity" - ], "line": 10, "description": "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count.", - "severity": "minor", - "location": { - "path": "file_b.rb", - "positions": { - "begin": { - "column": 14, - "line": 10 - }, - "end": { - "column": 39, - "line": 10 - } - } - }, - "content": { - "body": "" - }, - "type": "Issue", - "engine_name": "rubocop" + "severity": "minor" } ] } diff --git a/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js b/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js index cc251104811..7be68df61de 100644 --- a/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js +++ b/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js @@ -11,7 +11,7 @@ import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeli import { COMMIT_BOX_POLL_INTERVAL } from '~/projects/commit_box/info/constants'; import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql'; import getPipelineStagesQuery from '~/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql'; -import * as graphQlUtils from '~/pipelines/components/graph/utils'; +import * as sharedGraphQlUtils from '~/graphql_shared/utils'; import { mockDownstreamQueryResponse, mockPipelineStagesQueryResponse, @@ -241,16 +241,16 @@ describe('Commit box pipeline mini graph', () => { }); it('toggles query polling with visibility check', async () => { - jest.spyOn(graphQlUtils, 'toggleQueryPollingByVisibility'); + jest.spyOn(sharedGraphQlUtils, 'toggleQueryPollingByVisibility'); createComponent(); await waitForPromises(); - expect(graphQlUtils.toggleQueryPollingByVisibility).toHaveBeenCalledWith( + expect(sharedGraphQlUtils.toggleQueryPollingByVisibility).toHaveBeenCalledWith( wrapper.vm.$apollo.queries.pipelineStages, ); - expect(graphQlUtils.toggleQueryPollingByVisibility).toHaveBeenCalledWith( + expect(sharedGraphQlUtils.toggleQueryPollingByVisibility).toHaveBeenCalledWith( wrapper.vm.$apollo.queries.pipeline, ); }); diff --git a/spec/frontend/commit/components/commit_box_pipeline_status_spec.js b/spec/frontend/commit/components/commit_box_pipeline_status_spec.js index 5df35cc6dda..80b75a0a65e 100644 --- a/spec/frontend/commit/components/commit_box_pipeline_status_spec.js +++ b/spec/frontend/commit/components/commit_box_pipeline_status_spec.js @@ -12,7 +12,7 @@ import { PIPELINE_STATUS_FETCH_ERROR, } from '~/projects/commit_box/info/constants'; import getLatestPipelineStatusQuery from '~/projects/commit_box/info/graphql/queries/get_latest_pipeline_status.query.graphql'; -import * as graphQlUtils from '~/pipelines/components/graph/utils'; +import * as sharedGraphQlUtils from '~/graphql_shared/utils'; import { mockPipelineStatusResponse } from '../mock_data'; const mockProvide = { @@ -132,13 +132,13 @@ describe('Commit box pipeline status', () => { }); it('toggles pipelineStatus polling with visibility check', async () => { - jest.spyOn(graphQlUtils, 'toggleQueryPollingByVisibility'); + jest.spyOn(sharedGraphQlUtils, 'toggleQueryPollingByVisibility'); createComponent(); await waitForPromises(); - expect(graphQlUtils.toggleQueryPollingByVisibility).toHaveBeenCalledWith( + expect(sharedGraphQlUtils.toggleQueryPollingByVisibility).toHaveBeenCalledWith( wrapper.vm.$apollo.queries.pipelineStatus, ); }); diff --git a/spec/frontend/environments/confirm_rollback_modal_spec.js b/spec/frontend/environments/confirm_rollback_modal_spec.js index 2163814528a..d6601447cff 100644 --- a/spec/frontend/environments/confirm_rollback_modal_spec.js +++ b/spec/frontend/environments/confirm_rollback_modal_spec.js @@ -5,6 +5,7 @@ import VueApollo from 'vue-apollo'; import { trimText } from 'helpers/text_helper'; import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue'; import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import eventHub from '~/environments/event_hub'; describe('Confirm Rollback Modal Component', () => { @@ -53,6 +54,8 @@ describe('Confirm Rollback Modal Component', () => { }); }; + const findModal = () => component.findComponent(GlModal); + describe.each` hasMultipleCommits | environmentData | retryUrl | primaryPropsAttrs ${true} | ${envWithLastDeployment} | ${null} | ${[{ variant: 'danger' }]} @@ -73,7 +76,7 @@ describe('Confirm Rollback Modal Component', () => { hasMultipleCommits, retryUrl, }); - const modal = component.findComponent(GlModal); + const modal = findModal(); expect(modal.attributes('title')).toContain('Rollback'); expect(modal.attributes('title')).toContain('test'); @@ -92,7 +95,7 @@ describe('Confirm Rollback Modal Component', () => { hasMultipleCommits, }); - const modal = component.findComponent(GlModal); + const modal = findModal(); expect(modal.attributes('title')).toContain('Re-deploy'); expect(modal.attributes('title')).toContain('test'); @@ -110,7 +113,7 @@ describe('Confirm Rollback Modal Component', () => { }); const eventHubSpy = jest.spyOn(eventHub, '$emit'); - const modal = component.findComponent(GlModal); + const modal = findModal(); modal.vm.$emit('ok'); expect(eventHubSpy).toHaveBeenCalledWith('rollbackEnvironment', env); @@ -155,7 +158,7 @@ describe('Confirm Rollback Modal Component', () => { }, { apolloProvider }, ); - const modal = component.findComponent(GlModal); + const modal = findModal(); expect(trimText(modal.text())).toContain('commit abc0123'); expect(modal.text()).toContain('Are you sure you want to continue?'); @@ -177,7 +180,7 @@ describe('Confirm Rollback Modal Component', () => { }, { apolloProvider }, ); - const modal = component.findComponent(GlModal); + const modal = findModal(); expect(modal.attributes('title')).toContain('Rollback'); expect(modal.attributes('title')).toContain('test'); @@ -201,7 +204,7 @@ describe('Confirm Rollback Modal Component', () => { { apolloProvider }, ); - const modal = component.findComponent(GlModal); + const modal = findModal(); expect(modal.attributes('title')).toContain('Re-deploy'); expect(modal.attributes('title')).toContain('test'); @@ -220,7 +223,7 @@ describe('Confirm Rollback Modal Component', () => { { apolloProvider }, ); - const modal = component.findComponent(GlModal); + const modal = findModal(); modal.vm.$emit('ok'); await nextTick(); @@ -231,6 +234,25 @@ describe('Confirm Rollback Modal Component', () => { expect.anything(), ); }); + + it('should emit the "rollback" event when "ok" is clicked', async () => { + const env = { ...environmentData, isLastDeployment: true }; + + createComponent( + { + environment: env, + hasMultipleCommits, + graphql: true, + }, + { apolloProvider }, + ); + + const modal = findModal(); + modal.vm.$emit('ok'); + + await waitForPromises(); + expect(component.emitted('rollback')).toEqual([[]]); + }); }, ); }); diff --git a/spec/frontend/environments/environment_details/page_spec.js b/spec/frontend/environments/environment_details/index_spec.js index ed7e0feb6ed..4bf5194b86e 100644 --- a/spec/frontend/environments/environment_details/page_spec.js +++ b/spec/frontend/environments/environment_details/index_spec.js @@ -5,15 +5,19 @@ import resolvedEnvironmentDetails from 'test_fixtures/graphql/environments/graph import emptyEnvironmentDetails from 'test_fixtures/graphql/environments/graphql/queries/environment_details.query.graphql.empty.json'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import EnvironmentsDetailPage from '~/environments/environment_details/index.vue'; +import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue'; import EmptyState from '~/environments/environment_details/empty_state.vue'; import getEnvironmentDetails from '~/environments/graphql/queries/environment_details.query.graphql'; -import createMockApollo from '../../__helpers__/mock_apollo_helper'; -import waitForPromises from '../../__helpers__/wait_for_promises'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; -describe('~/environments/environment_details/page.vue', () => { +const GRAPHQL_ETAG_KEY = '/graphql/environments'; + +describe('~/environments/environment_details/index.vue', () => { Vue.use(VueApollo); let wrapper; + let routerMock; const emptyEnvironmentToRollbackData = { id: '', name: '', lastDeployment: null, retryUrl: '' }; const environmentToRollbackMock = jest.fn(); @@ -41,16 +45,23 @@ describe('~/environments/environment_details/page.vue', () => { environmentToRollbackData || emptyEnvironmentToRollbackData, ); const projectFullPath = 'gitlab-group/test-project'; + routerMock = { + push: jest.fn(), + }; return mountExtended(EnvironmentsDetailPage, { apolloProvider: mockApollo, provide: { projectPath: projectFullPath, + graphqlEtagKey: GRAPHQL_ETAG_KEY, }, propsData: { projectFullPath, environmentName: 'test-environment-name', }, + mocks: { + $router: routerMock, + }, }); }; @@ -73,6 +84,14 @@ describe('~/environments/environment_details/page.vue', () => { expect(wrapper.findComponent(GlLoadingIcon).exists()).not.toBe(true); expect(wrapper.findComponent(GlTableLite).exists()).toBe(true); }); + + describe('on rollback', () => { + it('sets the page back to default', () => { + wrapper.findComponent(ConfirmRollbackModal).vm.$emit('rollback'); + + expect(routerMock.push).toHaveBeenCalledWith({ query: {} }); + }); + }); }); describe('and there are no deployments', () => { diff --git a/spec/frontend/graphql_shared/utils_spec.js b/spec/frontend/graphql_shared/utils_spec.js index cd334ef0d97..35ae8de1b1f 100644 --- a/spec/frontend/graphql_shared/utils_spec.js +++ b/spec/frontend/graphql_shared/utils_spec.js @@ -1,3 +1,5 @@ +import Visibility from 'visibilityjs'; + import { isGid, getIdFromGraphQLId, @@ -6,6 +8,8 @@ import { convertFromGraphQLIds, convertNodeIdsFromGraphQLIds, getNodesOrDefault, + toggleQueryPollingByVisibility, + etagQueryHeaders, } from '~/graphql_shared/utils'; const mockType = 'Group'; @@ -160,3 +164,52 @@ describe('getNodesOrDefault', () => { expect(result).toEqual(expected); }); }); + +describe('toggleQueryPollingByVisibility', () => { + let query; + let changeFn; + let interval; + let hidden; + + beforeEach(() => { + hidden = jest.spyOn(Visibility, 'hidden').mockReturnValue(true); + jest.spyOn(Visibility, 'change').mockImplementation((fn) => { + changeFn = fn; + }); + + query = { startPolling: jest.fn(), stopPolling: jest.fn() }; + interval = 5000; + + toggleQueryPollingByVisibility(query, 5000); + }); + + it('starts polling not hidden', () => { + hidden.mockReturnValue(false); + + changeFn(); + expect(query.startPolling).toHaveBeenCalledWith(interval); + }); + + it('stops polling when hidden', () => { + query.stopPolling.mockReset(); + hidden.mockReturnValue(true); + + changeFn(); + expect(query.stopPolling).toHaveBeenCalled(); + }); +}); + +describe('etagQueryHeaders', () => { + it('returns headers necessary for etag caching', () => { + expect(etagQueryHeaders('myFeature', 'myResource')).toEqual({ + fetchOptions: { + method: 'GET', + }, + headers: { + 'X-GITLAB-GRAPHQL-FEATURE-CORRELATION': 'myFeature', + 'X-GITLAB-GRAPHQL-RESOURCE-ETAG': 'myResource', + 'X-Requested-With': 'XMLHttpRequest', + }, + }); + }); +}); diff --git a/spec/frontend/super_sidebar/components/menu_section_spec.js b/spec/frontend/super_sidebar/components/menu_section_spec.js index 751c4da5a3d..556e07a2e31 100644 --- a/spec/frontend/super_sidebar/components/menu_section_spec.js +++ b/spec/frontend/super_sidebar/components/menu_section_spec.js @@ -7,6 +7,7 @@ import { stubComponent } from 'helpers/stub_component'; describe('MenuSection component', () => { let wrapper; + const findButton = () => wrapper.find('button'); const findCollapse = () => wrapper.getComponent(GlCollapse); const findNavItems = () => wrapper.findAllComponents(NavItem); const createWrapper = (item, otherProps) => { @@ -22,7 +23,7 @@ describe('MenuSection component', () => { it('renders its title', () => { createWrapper({ title: 'Asdf' }); - expect(wrapper.find('button').text()).toBe('Asdf'); + expect(findButton().text()).toBe('Asdf'); }); it('renders all its subitems', () => { @@ -36,6 +37,12 @@ describe('MenuSection component', () => { expect(findNavItems().length).toBe(2); }); + it('associates button with list with aria-controls', () => { + createWrapper({ title: 'Asdf' }); + expect(findButton().attributes('aria-controls')).toBe('asdf'); + expect(findCollapse().attributes('id')).toBe('asdf'); + }); + describe('collapse behavior', () => { describe('when active', () => { it('is expanded', () => { @@ -47,6 +54,7 @@ describe('MenuSection component', () => { describe('when set to expanded', () => { it('is expanded', () => { createWrapper({ title: 'Asdf' }, { expanded: true }); + expect(findButton().attributes('aria-expanded')).toBe('true'); expect(findCollapse().props('visible')).toBe(true); }); }); @@ -54,6 +62,7 @@ describe('MenuSection component', () => { describe('when not active nor set to expanded', () => { it('is not expanded', () => { createWrapper({ title: 'Asdf' }); + expect(findButton().attributes('aria-expanded')).toBe('false'); expect(findCollapse().props('visible')).toBe(false); }); }); @@ -77,9 +86,9 @@ describe('MenuSection component', () => { describe('`tag` prop', () => { describe('by default', () => { - it('renders as <section> tag', () => { + it('renders as <div> tag', () => { createWrapper({ title: 'Asdf' }); - expect(wrapper.element.tagName).toBe('SECTION'); + expect(wrapper.element.tagName).toBe('DIV'); }); }); diff --git a/spec/graphql/mutations/design_management/delete_spec.rb b/spec/graphql/mutations/design_management/delete_spec.rb index 79196d4965d..a76943b9ff8 100644 --- a/spec/graphql/mutations/design_management/delete_spec.rb +++ b/spec/graphql/mutations/design_management/delete_spec.rb @@ -86,7 +86,7 @@ RSpec.describe Mutations::DesignManagement::Delete do end end - it 'runs no more than 30 queries' do + it 'runs no more than 31 queries' do allow(Gitlab::Tracking).to receive(:event) # rubocop:disable RSpec/ExpectGitlabTracking filenames.each(&:present?) # ignore setup @@ -107,22 +107,23 @@ RSpec.describe Mutations::DesignManagement::Delete do # 14. project.authorizations for user (same query as 5) # 15. current designs by filename and issue # 16, 17 project.authorizations for user (same query as 5) - # 18. find route by id and source_type - # 19. find plan for standard context + # 18. find design_management_repository for project + # 19. find route by id and source_type + # 20. find plan for standard context # ------------- our queries are below: - # 20. start transaction 1 - # 21. start transaction 2 - # 22. find version by sha and issue - # 23. exists version with sha and issue? - # 24. leave transaction 2 - # 25. create version with sha and issue - # 26. create design-version links - # 27. validate version.actions.present? - # 28. validate version.issue.present? - # 29. validate version.sha is unique - # 30. leave transaction 1 + # 21. start transaction 1 + # 22. start transaction 2 + # 23. find version by sha and issue + # 24. exists version with sha and issue? + # 25. leave transaction 2 + # 26. create version with sha and issue + # 27. create design-version links + # 28. validate version.actions.present? + # 29. validate version.issue.present? + # 30. validate version.sha is unique + # 31. leave transaction 1 # - expect { run_mutation }.not_to exceed_query_limit(30) + expect { run_mutation }.not_to exceed_query_limit(31) end end diff --git a/spec/helpers/environment_helper_spec.rb b/spec/helpers/environment_helper_spec.rb index c8d67d6dac2..c2d17832e8c 100644 --- a/spec/helpers/environment_helper_spec.rb +++ b/spec/helpers/environment_helper_spec.rb @@ -65,7 +65,8 @@ RSpec.describe EnvironmentHelper do environment_terminal_path: terminal_project_environment_path(project, environment), has_terminals: false, is_environment_available: true, - auto_stop_at: auto_stop_at + auto_stop_at: auto_stop_at, + graphql_etag_key: environment.etag_cache_key }.to_json) end end diff --git a/spec/lib/backup/repositories_spec.rb b/spec/lib/backup/repositories_spec.rb index c75f6c2ac89..b11538b93b7 100644 --- a/spec/lib/backup/repositories_spec.rb +++ b/spec/lib/backup/repositories_spec.rb @@ -159,6 +159,7 @@ RSpec.describe Backup::Repositories, feature_category: :backup_restore do describe '#restore' do let_it_be(:project) { create(:project, :repository) } + let_it_be(:personal_snippet) { create(:personal_snippet, :repository, author: project.first_owner) } let_it_be(:project_snippet) { create(:project_snippet, :repository, project: project, author: project.first_owner) } diff --git a/spec/lib/gitlab/ci/reports/codequality_mr_diff_spec.rb b/spec/lib/gitlab/ci/reports/codequality_mr_diff_spec.rb index 8b094c64b54..79fa1c3ec75 100644 --- a/spec/lib/gitlab/ci/reports/codequality_mr_diff_spec.rb +++ b/spec/lib/gitlab/ci/reports/codequality_mr_diff_spec.rb @@ -18,24 +18,8 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityMrDiff, feature_category: :code_q it 'generates quality report for mr diff' do expect(report.files).to match( "file_a.rb" => [ - { line: 10, - description: "Avoid parameter lists longer than 5 parameters. [12/5]", - severity: "major", - engine_name: "structure", - categories: ["Complexity"], - content: { "body" => "" }, - location: { "lines" => { "begin" => 10, "end" => 10 }, "path" => "file_a.rb" }, - other_locations: [], - type: "issue" }, - { line: 10, - description: "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", - severity: "major", - engine_name: "structure", - categories: ["Complexity"], - content: { "body" => "" }, - location: { "lines" => { "begin" => 10, "end" => 10 }, "path" => "file_a.rb" }, - other_locations: [], - type: "issue" } + { line: 10, description: "Avoid parameter lists longer than 5 parameters. [12/5]", severity: "major" }, + { line: 10, description: "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", severity: "major" } ] ) end @@ -44,14 +28,16 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityMrDiff, feature_category: :code_q context 'with several degradations on several files' do let(:new_degradations) { [degradation_1, degradation_2, degradation_3] } - it 'returns quality report including the files' do - expect(report.files.keys).to match_array(["file_a.rb", "file_b.rb"]) - end - - it 'converts the content body to html' do - body = report.files["file_b.rb"].first[:content]["body"] - - expect(body).to eq('<p data-sourcepos="1:1-3:66" dir="auto">This cop checks for methods with too many parameters.
The maximum number of parameters is configurable.
Keyword arguments can optionally be excluded from the total count.</p>') + it 'returns quality report for mr diff' do + expect(report.files).to match( + "file_a.rb" => [ + { line: 10, description: "Avoid parameter lists longer than 5 parameters. [12/5]", severity: "major" }, + { line: 10, description: "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", severity: "major" } + ], + "file_b.rb" => [ + { line: 10, description: "Avoid parameter lists longer than 5 parameters. [12/5]", severity: "minor" } + ] + ) end end end diff --git a/spec/lib/gitlab/gitaly_client/with_feature_flag_actors_spec.rb b/spec/lib/gitlab/gitaly_client/with_feature_flag_actors_spec.rb index 61945cc06b8..42153a9a3d8 100644 --- a/spec/lib/gitlab/gitaly_client/with_feature_flag_actors_spec.rb +++ b/spec/lib/gitlab/gitaly_client/with_feature_flag_actors_spec.rb @@ -131,23 +131,23 @@ RSpec.describe Gitlab::GitalyClient::WithFeatureFlagActors do end context 'when project design' do - let_it_be(:project) { create(:project, group: create(:group)) } - let(:issue) { create(:issue, project: project) } - let(:design) { create(:design, issue: issue) } + let_it_be(:design_repo) do + create(:design_management_repository, project: create(:project, group: create(:group))) + end - let(:expected_project) { project } - let(:expected_group) { project.group } + let(:expected_project) { design_repo.project } + let(:expected_group) { design_repo.project.group } it_behaves_like 'Gitaly feature flag actors are inferred from repository' do - let(:repository) { design.repository } + let(:repository) { design_repo.repository } end it_behaves_like 'Gitaly feature flag actors are inferred from repository' do - let(:repository) { design.repository.raw } + let(:repository) { design_repo.repository.raw } end it_behaves_like 'Gitaly feature flag actors are inferred from repository' do - let(:repository) { raw_repo_without_container(design.repository) } + let(:repository) { raw_repo_without_container(design_repo.repository) } end end end diff --git a/spec/lib/gitlab/gl_repository/identifier_spec.rb b/spec/lib/gitlab/gl_repository/identifier_spec.rb index 0a8559dd800..dbdcafea6d6 100644 --- a/spec/lib/gitlab/gl_repository/identifier_spec.rb +++ b/spec/lib/gitlab/gl_repository/identifier_spec.rb @@ -68,10 +68,12 @@ RSpec.describe Gitlab::GlRepository::Identifier do end describe 'design' do + let(:design_repository_container) { project.design_repository.container } + it_behaves_like 'parsing gl_repository identifier' do let(:record_id) { project.id } - let(:identifier) { "design-#{project.id}" } - let(:expected_container) { project } + let(:identifier) { "design-#{design_repository_container.id}" } + let(:expected_container) { design_repository_container } let(:expected_type) { Gitlab::GlRepository::DESIGN } end end diff --git a/spec/lib/gitlab/gl_repository/repo_type_spec.rb b/spec/lib/gitlab/gl_repository/repo_type_spec.rb index 40dcbe16688..2ac2fc1fd4b 100644 --- a/spec/lib/gitlab/gl_repository/repo_type_spec.rb +++ b/spec/lib/gitlab/gl_repository/repo_type_spec.rb @@ -12,6 +12,8 @@ RSpec.describe Gitlab::GlRepository::RepoType do let(:personal_snippet_path) { "snippets/#{personal_snippet.id}" } let(:project_snippet_path) { "#{project.full_path}/snippets/#{project_snippet.id}" } + let(:expected_repository_resolver) { expected_container } + describe Gitlab::GlRepository::PROJECT do it_behaves_like 'a repo type' do let(:expected_id) { project.id } @@ -133,11 +135,12 @@ RSpec.describe Gitlab::GlRepository::RepoType do describe Gitlab::GlRepository::DESIGN do it_behaves_like 'a repo type' do - let(:expected_identifier) { "design-#{project.id}" } - let(:expected_id) { project.id } + let(:expected_repository) { project.design_repository } + let(:expected_container) { project.design_management_repository } + let(:expected_id) { expected_container.id } + let(:expected_identifier) { "design-#{expected_id}" } let(:expected_suffix) { '.design' } - let(:expected_repository) { project.design_management_repository } - let(:expected_container) { project } + let(:expected_repository_resolver) { project } end it 'uses the design access checker' do @@ -162,5 +165,17 @@ RSpec.describe Gitlab::GlRepository::RepoType do expect(described_class.valid?(project_snippet_path)).to be_falsey end end + + describe '.project_for' do + it 'returns a project' do + expect(described_class.project_for(project.design_repository.container)).to be_instance_of(Project) + end + end + + describe '.repository_for' do + it 'returns a DesignManagement::GitRepository when a project is passed' do + expect(described_class.repository_for(project)).to be_instance_of(DesignManagement::GitRepository) + end + end end end diff --git a/spec/lib/gitlab/gl_repository_spec.rb b/spec/lib/gitlab/gl_repository_spec.rb index 05914f92c01..7be01507a82 100644 --- a/spec/lib/gitlab/gl_repository_spec.rb +++ b/spec/lib/gitlab/gl_repository_spec.rb @@ -6,6 +6,7 @@ RSpec.describe ::Gitlab::GlRepository do describe '.parse' do let_it_be(:project) { create(:project, :repository) } let_it_be(:snippet) { create(:personal_snippet) } + let(:design_repository_container) { project.design_repository.container } it 'parses a project gl_repository' do expect(described_class.parse("project-#{project.id}")).to eq([project, project, Gitlab::GlRepository::PROJECT]) @@ -20,7 +21,13 @@ RSpec.describe ::Gitlab::GlRepository do end it 'parses a design gl_repository' do - expect(described_class.parse("design-#{project.id}")).to eq([project, project, Gitlab::GlRepository::DESIGN]) + expect(described_class.parse("design-#{design_repository_container.id}")).to eq( + [ + design_repository_container, + project, + Gitlab::GlRepository::DESIGN + ] + ) end it 'throws an argument error on an invalid gl_repository type' do diff --git a/spec/lib/gitlab/patch/draw_route_spec.rb b/spec/lib/gitlab/patch/draw_route_spec.rb index 4d1c7bf9fcf..d983f6f15bb 100644 --- a/spec/lib/gitlab/patch/draw_route_spec.rb +++ b/spec/lib/gitlab/patch/draw_route_spec.rb @@ -20,8 +20,10 @@ RSpec.describe Gitlab::Patch::DrawRoute do it 'evaluates CE only route' do subject.draw(:help) + route_file_path = subject.route_path('config/routes/help.rb') + expect(subject).to have_received(:instance_eval) - .with(File.read(subject.route_path('config/routes/help.rb'))) + .with(File.read(route_file_path), route_file_path) .once expect(subject).to have_received(:instance_eval) diff --git a/spec/lib/gitlab/subscription_portal_spec.rb b/spec/lib/gitlab/subscription_portal_spec.rb index 52c7a68921b..96d3e855843 100644 --- a/spec/lib/gitlab/subscription_portal_spec.rb +++ b/spec/lib/gitlab/subscription_portal_spec.rb @@ -12,62 +12,10 @@ RSpec.describe ::Gitlab::SubscriptionPortal do stub_env('CUSTOMER_PORTAL_URL', env_value) end - describe '.default_subscriptions_url' do - where(:test, :development, :result) do - false | false | prod_customers_url - false | true | staging_customers_url - true | false | staging_customers_url - end - - before do - allow(Rails).to receive_message_chain(:env, :test?).and_return(test) - allow(Rails).to receive_message_chain(:env, :development?).and_return(development) - end - - with_them do - subject { described_class.default_subscriptions_url } - - it { is_expected.to eq(result) } - end - end - - describe '.subscriptions_url' do - subject { described_class.subscriptions_url } - - context 'when CUSTOMER_PORTAL_URL ENV is unset' do - it { is_expected.to eq(staging_customers_url) } - end - - context 'when CUSTOMER_PORTAL_URL ENV is set' do - let(:env_value) { 'https://customers.example.com' } - - it { is_expected.to eq(env_value) } - end - end - - describe '.subscriptions_comparison_url' do - subject { described_class.subscriptions_comparison_url } - - link_match = %r{\Ahttps://about\.gitlab\.((cn/pricing/saas)|(com/pricing/gitlab-com))/feature-comparison\z} - - it { is_expected.to match(link_match) } - end - describe 'class methods' do where(:method_name, :result) do - :default_subscriptions_url | staging_customers_url - :payment_form_url | "#{staging_customers_url}/payment_forms/cc_validation" :payment_validation_form_id | 'payment_method_validation' - :registration_validation_form_url | "#{staging_customers_url}/payment_forms/cc_registration_validation" :registration_validation_form_id | 'cc_registration_validation' - :subscriptions_graphql_url | "#{staging_customers_url}/graphql" - :subscriptions_more_minutes_url | "#{staging_customers_url}/buy_pipeline_minutes" - :subscriptions_more_storage_url | "#{staging_customers_url}/buy_storage" - :subscriptions_manage_url | "#{staging_customers_url}/subscriptions" - :subscriptions_legacy_sign_in_url | "#{staging_customers_url}/customers/sign_in?legacy=true" - :subscriptions_instance_review_url | "#{staging_customers_url}/instance_review" - :subscriptions_gitlab_plans_url | "#{staging_customers_url}/gitlab_plans" - :edit_account_url | "#{staging_customers_url}/customers/edit" end with_them do @@ -77,40 +25,6 @@ RSpec.describe ::Gitlab::SubscriptionPortal do end end - describe '.add_extra_seats_url' do - subject { described_class.add_extra_seats_url(group_id) } - - let(:group_id) { 153 } - - it do - url = "#{staging_customers_url}/gitlab/namespaces/#{group_id}/extra_seats" - is_expected.to eq(url) - end - end - - describe '.upgrade_subscription_url' do - subject { described_class.upgrade_subscription_url(group_id, plan_id) } - - let(:group_id) { 153 } - let(:plan_id) { 5 } - - it do - url = "#{staging_customers_url}/gitlab/namespaces/#{group_id}/upgrade/#{plan_id}" - is_expected.to eq(url) - end - end - - describe '.renew_subscription_url' do - subject { described_class.renew_subscription_url(group_id) } - - let(:group_id) { 153 } - - it do - url = "#{staging_customers_url}/gitlab/namespaces/#{group_id}/renew" - is_expected.to eq(url) - end - end - describe 'constants' do where(:constant_name, :result) do 'REGISTRATION_VALIDATION_FORM_ID' | 'cc_registration_validation' diff --git a/spec/models/design_management/design_collection_spec.rb b/spec/models/design_management/design_collection_spec.rb index bc8330c7dd3..06596d37fde 100644 --- a/spec/models/design_management/design_collection_spec.rb +++ b/spec/models/design_management/design_collection_spec.rb @@ -128,7 +128,7 @@ RSpec.describe DesignManagement::DesignCollection do describe "#repository" do it "builds a design repository" do - expect(collection.repository).to be_a(DesignManagement::Repository) + expect(collection.repository).to be_a(DesignManagement::GitRepository) end end diff --git a/spec/models/design_management/design_spec.rb b/spec/models/design_management/design_spec.rb index 52bb23588e3..72c0d1d1a64 100644 --- a/spec/models/design_management/design_spec.rb +++ b/spec/models/design_management/design_spec.rb @@ -463,7 +463,7 @@ RSpec.describe DesignManagement::Design, feature_category: :design_management do it 'is a design repository' do design = build(:design, issue: issue) - expect(design.repository).to be_a(DesignManagement::Repository) + expect(design.repository).to be_a(DesignManagement::GitRepository) end end diff --git a/spec/models/design_management/git_repository_spec.rb b/spec/models/design_management/git_repository_spec.rb index 1b07e337cde..736f2ad45cf 100644 --- a/spec/models/design_management/git_repository_spec.rb +++ b/spec/models/design_management/git_repository_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe DesignManagement::GitRepository, feature_category: :design_management do - let_it_be(:project) { create(:project) } - let(:git_repository) { described_class.new(project) } + let_it_be(:container_repo) { DesignManagement::Repository.new(project: create(:project)) } + let(:git_repository) { container_repo.repository } shared_examples 'returns parsed git attributes that enable LFS for all file types' do it do @@ -16,6 +16,12 @@ RSpec.describe DesignManagement::GitRepository, feature_category: :design_manage end end + describe '.container' do + it 'is of class DesignManagement::Repository' do + expect(git_repository.container).to be_a_kind_of(DesignManagement::Repository) + end + end + describe "#info_attributes" do subject { git_repository.info_attributes } diff --git a/spec/models/design_management/repository_spec.rb b/spec/models/design_management/repository_spec.rb index 67cdba40f82..74f393306dc 100644 --- a/spec/models/design_management/repository_spec.rb +++ b/spec/models/design_management/repository_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe DesignManagement::Repository, feature_category: :design_management do let_it_be(:project) { create(:project) } - let(:subject) { ::DesignManagement::Repository.new({ project: project }) } + let(:subject) { described_class.new({ project: project }) } describe 'associations' do it { is_expected.to belong_to(:project).inverse_of(:design_management_repository) } @@ -14,4 +14,12 @@ RSpec.describe DesignManagement::Repository, feature_category: :design_managemen it { is_expected.to validate_presence_of(:project) } it { is_expected.to validate_uniqueness_of(:project) } end + + it "returns the project's full path" do + expect(subject.full_path).to eq(project.full_path + Gitlab::GlRepository::DESIGN.path_suffix) + end + + it "returns the project's disk path" do + expect(subject.disk_path).to eq(project.disk_path + Gitlab::GlRepository::DESIGN.path_suffix) + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a54ba0ba40a..0e432edebb0 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -5784,6 +5784,34 @@ RSpec.describe User, feature_category: :user_profile do expect(user).not_to be_blocked end + + context 'when target user is the same as deleted_by' do + let(:deleted_by) { user } + + it 'blocks the user and schedules the record for deletion with the correct delay' do + freeze_time do + expect(DeleteUserWorker).to receive(:perform_in).with(7.days, user.id, user.id, {}) + + user.delete_async(deleted_by: deleted_by) + + expect(user).to be_blocked + end + end + + context 'when delay_delete_own_user feature flag is disabled' do + before do + stub_feature_flags(delay_delete_own_user: false) + end + + it 'schedules user for deletion without blocking them' do + expect(DeleteUserWorker).to receive(:perform_async).with(user.id, user.id, {}) + + user.delete_async(deleted_by: deleted_by) + + expect(user).not_to be_blocked + end + end + end end describe '#max_member_access_for_project_ids' do diff --git a/spec/presenters/ci/pipeline_artifacts/code_quality_mr_diff_presenter_spec.rb b/spec/presenters/ci/pipeline_artifacts/code_quality_mr_diff_presenter_spec.rb index 3f40b5469ad..99c82795210 100644 --- a/spec/presenters/ci/pipeline_artifacts/code_quality_mr_diff_presenter_spec.rb +++ b/spec/presenters/ci/pipeline_artifacts/code_quality_mr_diff_presenter_spec.rb @@ -36,24 +36,8 @@ RSpec.describe Ci::PipelineArtifacts::CodeQualityMrDiffPresenter, feature_catego expect(quality_data).to match( files: { "file_a.rb" => [ - { line: 10, - description: "Avoid parameter lists longer than 5 parameters. [12/5]", - severity: "major", - engine_name: "structure", - categories: ["Complexity"], - content: { "body" => "" }, - location: { "lines" => { "begin" => 10, "end" => 10 }, "path" => "file_a.rb" }, - other_locations: [], - type: "issue" }, - { line: 10, - description: "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", - severity: "minor", - engine_name: "structure", - categories: ["Complexity"], - content: { "body" => "" }, - location: { "lines" => { "begin" => 10, "end" => 10 }, "path" => "file_a.rb" }, - other_locations: [], - type: "issue" } + { line: 10, description: "Avoid parameter lists longer than 5 parameters. [12/5]", severity: "major" }, + { line: 10, description: "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", severity: "minor" } ] } ) @@ -67,34 +51,11 @@ RSpec.describe Ci::PipelineArtifacts::CodeQualityMrDiffPresenter, feature_catego expect(quality_data).to match( files: { "file_a.rb" => [ - { line: 10, - description: "Avoid parameter lists longer than 5 parameters. [12/5]", - severity: "major", - engine_name: "structure", - categories: ["Complexity"], - content: { "body" => "" }, - location: { "lines" => { "begin" => 10, "end" => 10 }, "path" => "file_a.rb" }, - other_locations: [], - type: "issue" }, - { line: 10, - description: "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", - severity: "minor", - engine_name: "structure", - categories: ["Complexity"], - content: { "body" => "" }, - location: { "lines" => { "begin" => 10, "end" => 10 }, "path" => "file_a.rb" }, - other_locations: [], - type: "issue" } + { line: 10, description: "Avoid parameter lists longer than 5 parameters. [12/5]", severity: "major" }, + { line: 10, description: "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", severity: "minor" } ], "file_b.rb" => [ - { line: 10, - description: "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count.", - severity: "minor", - engine_name: "rubocop", - categories: ["Complexity"], - content: { "body" => "" }, - location: { "positions" => { "begin" => { "column" => 14, "line" => 10 }, "end" => { "column" => 39, "line" => 10 } }, "path" => "file_b.rb" }, - type: "Issue" } + { line: 10, description: "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count.", severity: "minor" } ] } ) diff --git a/spec/routing/directs/subscription_portal_spec.rb b/spec/routing/directs/subscription_portal_spec.rb new file mode 100644 index 00000000000..768990fae62 --- /dev/null +++ b/spec/routing/directs/subscription_portal_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Custom URLs', 'Subscription Portal', feature_category: :subscription_management do + using RSpec::Parameterized::TableSyntax + include SubscriptionPortalHelper + + let(:env_value) { nil } + let(:staging_env_value) { nil } + + before do + stub_env('CUSTOMER_PORTAL_URL', env_value) + stub_env('STAGING_CUSTOMER_PORTAL_URL', staging_env_value) + end + + describe 'subscription_portal_staging_url' do + subject { subscription_portal_staging_url } + + context 'when STAGING_CUSTOMER_PORTAL_URL is unset' do + it { is_expected.to eq(staging_customers_url) } + end + + context 'when STAGING_CUSTOMER_PORTAL_URL is set' do + let(:staging_env_value) { 'https://customers.staging.example.com' } + + it { is_expected.to eq(staging_env_value) } + end + end + + describe 'subscription_portal_url' do + subject { subscription_portal_url } + + context 'when CUSTOMER_PORTAL_URL ENV is unset' do + where(:test, :development, :expected_url) do + false | false | prod_customers_url + false | true | subscription_portal_staging_url + true | false | subscription_portal_staging_url + end + + before do + allow(Rails).to receive_message_chain(:env, :test?).and_return(test) + allow(Rails).to receive_message_chain(:env, :development?).and_return(development) + end + + with_them do + it { is_expected.to eq(expected_url) } + end + end + + context 'when CUSTOMER_PORTAL_URL ENV is set' do + let(:env_value) { 'https://customers.example.com' } + + it { is_expected.to eq(env_value) } + end + end + + describe 'subscription_portal_instance_review_url' do + subject { subscription_portal_instance_review_url } + + it { is_expected.to eq("#{staging_customers_url}/instance_review") } + end +end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index c2a3afc0e57..c2458d3485f 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -128,6 +128,18 @@ RSpec.describe 'project routing' do it 'to #archive with "/" in route' do expect(get('/gitlab/gitlabhq/-/archive/improve/awesome/gitlabhq-improve-awesome.tar.gz')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.gz', id: 'improve/awesome/gitlabhq-improve-awesome') end + + it 'to #archive format:html' do + expect(get('/gitlab/gitlabhq/-/archive/master.html')).to route_to_route_not_found + end + + it 'to #archive format:yaml' do + expect(get('/gitlab/gitlabhq/-/archive/master.yaml')).to route_to_route_not_found + end + + it 'to #archive format:yml' do + expect(get('/gitlab/gitlabhq/-/archive/master.yml')).to route_to_route_not_found + end end describe Projects::BranchesController, 'routing' do diff --git a/spec/serializers/ci/codequality_mr_diff_entity_spec.rb b/spec/serializers/ci/codequality_mr_diff_entity_spec.rb index a6e29a3914d..19b872b68db 100644 --- a/spec/serializers/ci/codequality_mr_diff_entity_spec.rb +++ b/spec/serializers/ci/codequality_mr_diff_entity_spec.rb @@ -19,17 +19,8 @@ RSpec.describe Ci::CodequalityMrDiffEntity, feature_category: :code_quality do end it 'contains correct codequality mr diff report', :aggregate_failures do - expect(report[:files].keys).to match_array(["file_a.rb"]) - expect(report[:files]["file_a.rb"].first).to include( - :line, - :description, - :severity, - :engine_name, - :categories, - :content, - :location, - :other_locations, - :type) + expect(report[:files].keys).to eq(["file_a.rb"]) + expect(report[:files]["file_a.rb"].first).to include(:line, :description, :severity) end end end diff --git a/spec/services/design_management/save_designs_service_spec.rb b/spec/services/design_management/save_designs_service_spec.rb index a87494d87f7..ea53fcc3b12 100644 --- a/spec/services/design_management/save_designs_service_spec.rb +++ b/spec/services/design_management/save_designs_service_spec.rb @@ -11,7 +11,10 @@ RSpec.describe DesignManagement::SaveDesignsService, feature_category: :design_m let(:project) { issue.project } let(:user) { developer } let(:files) { [rails_sample] } - let(:design_repository) { ::Gitlab::GlRepository::DESIGN.repository_resolver.call(project) } + let(:design_repository) do + ::Gitlab::GlRepository::DESIGN.repository_resolver.call(project) + end + let(:rails_sample_name) { 'rails_sample.jpg' } let(:rails_sample) { sample_image(rails_sample_name) } let(:dk_png) { sample_image('dk.png') } diff --git a/spec/services/merge_requests/after_create_service_spec.rb b/spec/services/merge_requests/after_create_service_spec.rb index 9361ec44e30..50a3d49d4a3 100644 --- a/spec/services/merge_requests/after_create_service_spec.rb +++ b/spec/services/merge_requests/after_create_service_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe MergeRequests::AfterCreateService, feature_category: :code_review_workflow do let_it_be(:merge_request) { create(:merge_request) } + let(:project) { merge_request.project } subject(:after_create_service) do described_class.new(project: merge_request.target_project, current_user: merge_request.author) @@ -68,6 +69,12 @@ RSpec.describe MergeRequests::AfterCreateService, feature_category: :code_review execute_service end + it 'executes hooks with default action' do + expect(project).to receive(:execute_hooks) + + execute_service + end + it_behaves_like 'records an onboarding progress action', :merge_request_created do let(:namespace) { merge_request.target_project.namespace } end diff --git a/spec/services/merge_requests/base_service_spec.rb b/spec/services/merge_requests/base_service_spec.rb index deabb1c9804..1ca4bfe622c 100644 --- a/spec/services/merge_requests/base_service_spec.rb +++ b/spec/services/merge_requests/base_service_spec.rb @@ -15,6 +15,7 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl let_it_be(:project) { create(:project, :repository) } + let(:user) { project.first_owner } let(:title) { 'Awesome merge_request' } let(:params) do { @@ -25,14 +26,14 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl } end - subject { MergeRequests::CreateService.new(project: project, current_user: project.first_owner, params: params) } - describe '#execute_hooks' do + subject { MergeRequests::CreateService.new(project: project, current_user: user, params: params).execute } + shared_examples 'enqueues Jira sync worker' do specify :aggregate_failures do expect(JiraConnect::SyncMergeRequestWorker).to receive(:perform_async).with(kind_of(Numeric), kind_of(Numeric)).and_call_original Sidekiq::Testing.fake! do - expect { subject.execute }.to change(JiraConnect::SyncMergeRequestWorker.jobs, :size).by(1) + expect { subject }.to change(JiraConnect::SyncMergeRequestWorker.jobs, :size).by(1) end end end @@ -40,7 +41,7 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl shared_examples 'does not enqueue Jira sync worker' do it do Sidekiq::Testing.fake! do - expect { subject.execute }.not_to change(JiraConnect::SyncMergeRequestWorker.jobs, :size) + expect { subject }.not_to change(JiraConnect::SyncMergeRequestWorker.jobs, :size) end end end @@ -53,7 +54,20 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl context 'MR contains Jira issue key' do let(:title) { 'Awesome merge_request with issue JIRA-123' } - it_behaves_like 'enqueues Jira sync worker' + it_behaves_like 'does not enqueue Jira sync worker' + + context 'for UpdateService' do + subject { MergeRequests::UpdateService.new(project: project, current_user: user, params: params).execute(merge_request) } + + let(:merge_request) do + create(:merge_request, :simple, title: 'Old title', + assignee_ids: [user.id], + source_project: project, + author: user) + end + + it_behaves_like 'enqueues Jira sync worker' + end end context 'MR does not contain Jira issue key' do @@ -69,13 +83,13 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl describe `#create_pipeline_for` do let_it_be(:merge_request) { create(:merge_request) } - subject { MergeRequests::ExampleService.new(project: project, current_user: project.first_owner, params: params) } + subject { MergeRequests::ExampleService.new(project: project, current_user: user, params: params) } context 'async: false' do it 'creates a pipeline directly' do expect(MergeRequests::CreatePipelineService) .to receive(:new) - .with(hash_including(project: project, current_user: project.first_owner, params: { allow_duplicate: false })) + .with(hash_including(project: project, current_user: user, params: { allow_duplicate: false })) .and_call_original expect(MergeRequests::CreatePipelineWorker).not_to receive(:perform_async) @@ -86,7 +100,7 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl it 'passes :allow_duplicate as true' do expect(MergeRequests::CreatePipelineService) .to receive(:new) - .with(hash_including(project: project, current_user: project.first_owner, params: { allow_duplicate: true })) + .with(hash_including(project: project, current_user: user, params: { allow_duplicate: true })) .and_call_original expect(MergeRequests::CreatePipelineWorker).not_to receive(:perform_async) @@ -100,7 +114,7 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl expect(MergeRequests::CreatePipelineService).not_to receive(:new) expect(MergeRequests::CreatePipelineWorker) .to receive(:perform_async) - .with(project.id, project.first_owner.id, merge_request.id, { "allow_duplicate" => false }) + .with(project.id, user.id, merge_request.id, { "allow_duplicate" => false }) .and_call_original Sidekiq::Testing.fake! do @@ -113,7 +127,7 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl expect(MergeRequests::CreatePipelineService).not_to receive(:new) expect(MergeRequests::CreatePipelineWorker) .to receive(:perform_async) - .with(project.id, project.first_owner.id, merge_request.id, { "allow_duplicate" => true }) + .with(project.id, user.id, merge_request.id, { "allow_duplicate" => true }) .and_call_original Sidekiq::Testing.fake! do diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index c4de56f39dd..7705278f30d 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -28,7 +28,6 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state, f before do project.add_maintainer(user) project.add_developer(user2) - allow(service).to receive(:execute_hooks) end it 'creates an MR' do @@ -39,8 +38,10 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state, f expect(merge_request.merge_params['force_remove_source_branch']).to eq('1') end - it 'executes hooks with default action' do - expect(service).to have_received(:execute_hooks).with(merge_request) + it 'does not execute hooks' do + expect(project).not_to receive(:execute_hooks) + + service.execute end it 'refreshes the number of open merge requests', :use_clean_rails_memory_store_caching do diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 2704458ca4d..48d5935f22f 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -715,10 +715,15 @@ RSpec.describe Projects::TransferService, feature_category: :projects do project.design_repository end + def clear_design_repo_memoization + project.design_management_repository.clear_memoization(:repository) + project.clear_memoization(:design_repository) + end + it 'does not create a design repository' do expect(subject.execute(group)).to be true - project.clear_memoization(:design_repository) + clear_design_repo_memoization expect(design_repository.exists?).to be false end @@ -734,7 +739,7 @@ RSpec.describe Projects::TransferService, feature_category: :projects do it 'moves the repository' do expect(subject.execute(group)).to be true - project.clear_memoization(:design_repository) + clear_design_repo_memoization expect(design_repository).to have_attributes( disk_path: new_full_path, @@ -746,7 +751,7 @@ RSpec.describe Projects::TransferService, feature_category: :projects do allow(subject).to receive(:execute_system_hooks).and_raise('foo') expect { subject.execute(group) }.to raise_error('foo') - project.clear_memoization(:design_repository) + clear_design_repo_memoization expect(design_repository).to have_attributes( disk_path: old_full_path, @@ -763,7 +768,7 @@ RSpec.describe Projects::TransferService, feature_category: :projects do expect(subject.execute(group)).to be true - project.clear_memoization(:design_repository) + clear_design_repo_memoization expect(design_repository).to have_attributes( disk_path: old_disk_path, @@ -777,7 +782,7 @@ RSpec.describe Projects::TransferService, feature_category: :projects do allow(subject).to receive(:execute_system_hooks).and_raise('foo') expect { subject.execute(group) }.to raise_error('foo') - project.clear_memoization(:design_repository) + clear_design_repo_memoization expect(design_repository).to have_attributes( disk_path: old_disk_path, diff --git a/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb index 025f0d5c7ea..c2898513424 100644 --- a/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb @@ -15,7 +15,7 @@ RSpec.shared_examples 'a repo type' do describe '#repository_for' do it 'finds the repository for the repo type' do - expect(described_class.repository_for(expected_container)).to eq(expected_repository) + expect(described_class.repository_for(expected_repository_resolver)).to eq(expected_repository) end it 'returns nil when container is nil' do diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb index 05c2b0e7930..8a99f69c079 100644 --- a/spec/workers/delete_user_worker_spec.rb +++ b/spec/workers/delete_user_worker_spec.rb @@ -21,4 +21,54 @@ RSpec.describe DeleteUserWorker, feature_category: :user_management do described_class.new.perform(current_user.id, user.id, { "test" => "test" }) end + + shared_examples 'does nothing' do + it "does not instantiate a DeleteUserWorker" do + expect(Users::DestroyService).not_to receive(:new) + + perform + end + end + + context 'when user is banned' do + subject(:perform) { described_class.new.perform(current_user.id, user.id) } + + before do + user.ban + end + + it_behaves_like 'does nothing' + + context 'when delay_delete_own_user feature flag is disabled' do + before do + stub_feature_flags(delay_delete_own_user: false) + end + + it "proceeds with deletion" do + expect_next_instance_of(Users::DestroyService) do |service| + expect(service).to receive(:execute).with(user, {}) + end + + perform + end + end + end + + context 'when user to delete does not exist' do + subject(:perform) { described_class.new.perform(current_user.id, non_existing_record_id) } + + it_behaves_like 'does nothing' + end + + context 'when current user does not exist' do + subject(:perform) { described_class.new.perform(non_existing_record_id, user.id) } + + it_behaves_like 'does nothing' + end + + context 'when user to delete and current user do not exist' do + subject(:perform) { described_class.new.perform(non_existing_record_id, non_existing_record_id) } + + it_behaves_like 'does nothing' + end end diff --git a/yarn.lock b/yarn.lock index c46ffb26473..589f4fe050b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12878,10 +12878,10 @@ webpack-dev-middleware@^5.3.1: range-parser "^1.2.1" schema-utils "^4.0.0" -webpack-dev-server@4.13.2: - version "4.13.2" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.13.2.tgz#d97445481d78691efe6d9a3b230833d802fc31f9" - integrity sha512-5i6TrGBRxG4vnfDpB6qSQGfnB6skGBXNL5/542w2uRGLimX6qeE5BQMLrzIC3JYV/xlGOv+s+hTleI9AZKUQNw== +webpack-dev-server@4.13.3: + version "4.13.3" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.13.3.tgz#9feb740b8b56b886260bae1360286818a221bae8" + integrity sha512-KqqzrzMRSRy5ePz10VhjyL27K2dxqwXQLP5rAKwRJBPUahe7Z2bBWzHw37jeb8GCPKxZRO79ZdQUAPesMh/Nug== dependencies: "@types/bonjour" "^3.5.9" "@types/connect-history-api-fallback" "^1.3.5" |