Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b9ce0fe1e6
commit
90693cc231
2
Gemfile
2
Gemfile
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import typeDefs from './typedefs.graphql';
|
|||
export const apolloProvider = (endpoint) => {
|
||||
const defaultClient = createDefaultClient(resolvers(endpoint), {
|
||||
typeDefs,
|
||||
useGet: true,
|
||||
});
|
||||
const { cache } = defaultClient;
|
||||
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ export const initPage = async () => {
|
|||
router,
|
||||
provide: {
|
||||
projectPath: dataSet.projectFullPath,
|
||||
graphqlEtagKey: dataSet.graphqlEtagPath,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('router-view');
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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'"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -29,3 +29,5 @@ module Ci
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
Ci::JobTokenScope::AddProjectService.prepend_mod_with('Ci::JobTokenScope::AddProjectService')
|
||||
|
|
|
|||
|
|
@ -31,3 +31,5 @@ module Ci
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
Ci::JobTokenScope::RemoveProjectService.prepend_mod_with('Ci::JobTokenScope::RemoveProjectService')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
- page_title _("Appearance")
|
||||
- add_page_specific_style 'page_bundles/settings'
|
||||
- @force_desktop_expanded_sidebar = true
|
||||
|
||||
= render 'form'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -3,3 +3,4 @@
|
|||
# Custom URL definitions for the Community Edition.
|
||||
|
||||
draw 'directs/milestone'
|
||||
draw 'directs/subscription_portal'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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/ },
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :design_management_repository, class: 'DesignManagement::Repository' do
|
||||
project
|
||||
end
|
||||
end
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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([[]]);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
@ -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',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
]
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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') }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue