Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-05-02 21:19:21 +00:00
parent b9ce0fe1e6
commit 90693cc231
93 changed files with 757 additions and 529 deletions

View File

@ -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'

View File

@ -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"},

View File

@ -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)

View File

@ -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);
}

View File

@ -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 = [
{

View File

@ -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>

View File

@ -13,6 +13,7 @@ import typeDefs from './typedefs.graphql';
export const apolloProvider = (endpoint) => {
const defaultClient = createDefaultClient(resolvers(endpoint), {
typeDefs,
useGet: true,
});
const { cache } = defaultClient;

View File

@ -94,6 +94,7 @@ export const initPage = async () => {
router,
provide: {
projectPath: dataSet.projectFullPath,
graphqlEtagKey: dataSet.graphqlEtagPath,
},
render(createElement) {
return createElement('router-view');

View File

@ -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',
},
};
};

View File

@ -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,
};

View File

@ -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" />

View File

@ -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;
},

View File

@ -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>

View File

@ -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'"

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 git_repo
project ? GitRepository.new(project) : nil
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 repo_type
Gitlab::GlRepository::DESIGN
end
strong_memoize_attr :git_repo
end
end

View File

@ -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)

View File

@ -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

View File

@ -29,3 +29,5 @@ module Ci
end
end
end
Ci::JobTokenScope::AddProjectService.prepend_mod_with('Ci::JobTokenScope::AddProjectService')

View File

@ -31,3 +31,5 @@ module Ci
end
end
end
Ci::JobTokenScope::RemoveProjectService.prepend_mod_with('Ci::JobTokenScope::RemoveProjectService')

View File

@ -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)

View File

@ -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)

View File

@ -1,4 +1,5 @@
- page_title _("Appearance")
- add_page_specific_style 'page_bundles/settings'
- @force_desktop_expanded_sidebar = true
= render 'form'

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -3,3 +3,4 @@
# Custom URL definitions for the Community Edition.
draw 'directs/milestone'
draw 'directs/subscription_portal'

View File

@ -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

View File

@ -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/ },

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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"

View File

@ -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 = {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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)

View File

@ -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

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
FactoryBot.define do
factory :design_management_repository, class: 'DesignManagement::Repository' do
project
end
end

View File

@ -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

View File

@ -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"
}
]
}

View File

@ -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,
);
});

View File

@ -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,
);
});

View File

@ -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([[]]);
});
},
);
});

View File

@ -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', () => {

View File

@ -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',
},
});
});
});

View File

@ -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');
});
});

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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.&#x000A;The maximum number of parameters is configurable.&#x000A;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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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" }
]
}
)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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') }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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"