Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-05-13 18:07:45 +00:00
parent 2a3be6ad50
commit 647f13ebef
95 changed files with 902 additions and 456 deletions

View File

@ -1680,6 +1680,8 @@
when: never
- <<: *if-security-merge-request
when: never
- <<: *if-merge-request-targeting-stable-branch
when: never
- !reference [.frontend:rules:jest, rules]
################

View File

@ -1932,7 +1932,6 @@ Layout/LineLength:
- 'lib/api/rubygem_packages.rb'
- 'lib/api/settings.rb'
- 'lib/api/snippet_repository_storage_moves.rb'
- 'lib/api/snippets.rb'
- 'lib/api/submodules.rb'
- 'lib/api/suggestions.rb'
- 'lib/api/tags.rb'

View File

@ -244,7 +244,7 @@ export default {
placement="bottom-end"
class="submit-review-dropdown"
:class="{ 'submit-review-dropdown-animated': shouldAnimateReviewButton }"
data-testid="submit-review-dropdown"
data-testid="review-drawer-toggle"
fluid-width
@beforeClose="onBeforeClose"
@shown="setDropdownVisible(true)"

View File

@ -20,7 +20,7 @@ export default {
</script>
<template>
<div v-if="draftsCount > 0 || isReviewer">
<div v-if="draftsCount > 0 || isReviewer" data-testid="review-drawer-toggle">
<gl-button variant="confirm" data-testid="review-drawer-toggle" @click="setDrawerOpened(true)">
{{ __('Your review') }}
<drafts-count

View File

@ -61,6 +61,6 @@ export default {
v-on="$listeners"
>
<file-row-stats v-if="showFileRowStats" :file="file" class="gl-mr-2" />
<changed-file-icon :file="file" :size="16" :show-tooltip="true" />
<changed-file-icon :file="file" :size="16" :show-tooltip="true" :as-button="false" />
</file-row>
</template>

View File

@ -230,12 +230,26 @@ export default {
</script>
<template>
<div class="tree-list-holder gl-flex gl-flex-col" data-testid="file-tree-container">
<section
class="tree-list-holder gl-flex gl-flex-col"
data-testid="file-tree-container"
aria-labelledby="tree-list-title"
>
<div class="gl-mb-3 gl-flex gl-items-center">
<h5 class="gl-my-0 gl-inline-block">{{ __('Files') }}</h5>
<gl-badge v-if="totalFilesCount != null" class="gl-ml-2" data-testid="file-count">{{
totalFilesCount
}}</gl-badge>
<h2
id="tree-list-title"
class="gl-my-0 gl-inline-block gl-text-base"
:aria-label="__('File browser')"
>
{{ __('Files') }}
</h2>
<gl-badge
v-if="totalFilesCount != null"
class="gl-ml-2"
data-testid="file-count"
aria-hidden="true"
>{{ totalFilesCount }}</gl-badge
>
<gl-button-group class="gl-ml-auto">
<gl-button
v-gl-tooltip.hover
@ -257,7 +271,6 @@ export default {
/>
</gl-button-group>
</div>
<label for="diff-tree-search" class="sr-only">{{ $options.searchPlaceholder }}</label>
<gl-search-box-by-type
id="diff-tree-search"
v-model="search"
@ -267,7 +280,11 @@ export default {
:clear-button-title="__('Clear search')"
class="gl-mb-3"
/>
<div :class="{ 'tree-list-blobs': !renderTreeList || search }" class="mr-tree-list">
<nav
:class="{ 'tree-list-blobs': !renderTreeList || search }"
class="mr-tree-list"
:aria-label="__('File tree')"
>
<recycle-scroller
v-if="treeList.length"
ref="scroller"
@ -286,7 +303,7 @@ export default {
:style="{ '--level': item.level }"
:class="{ 'tree-list-parent': item.level > 0 }"
:tabindex="item.loading ? -1 : 0"
class="gl-relative !gl-m-1"
class="gl-relative"
:data-file-row="item.fileHash"
@toggleTreeOpen="$emit('toggleFolder', $event)"
@clickFile="!item.loading && $emit('clickFile', $event)"
@ -299,8 +316,8 @@ export default {
<p v-else class="prepend-top-20 append-bottom-20 text-center">
{{ s__('MergeRequest|No files found') }}
</p>
</div>
</div>
</nav>
</section>
</template>
<style>

View File

@ -7,7 +7,6 @@ fragment Group on Group {
id
}
webUrl
organizationEditPath
descriptionHtml
avatarUrl
descendantGroupsCount

View File

@ -1,4 +1,4 @@
#import "~/organizations/shared/graphql/fragments/group.fragment.graphql"
#import "~/graphql_shared/fragments/group.fragment.graphql"
query getMemberYourWorkGroups($search: String, $sort: String, $parentId: Int, $page: Int) {
groups(search: $search, sort: $sort, parentId: $parentId, page: $page) @client {

View File

@ -36,7 +36,4 @@ export const formatGroupForGraphQLResolver = (group) => ({
projectsCount: group.project_count,
children: group.children?.length ? group.children.map(formatGroupForGraphQLResolver) : [],
childrenCount: group.subgroup_count,
// Properties below are hard coded for now until API has been
// updated to support these fields.
organizationEditPath: '',
});

View File

@ -1,5 +1,5 @@
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
#import "~/organizations/shared/graphql/fragments/group.fragment.graphql"
#import "~/graphql_shared/fragments/group.fragment.graphql"
query getOrganizationGroups(
$id: OrganizationsOrganizationID!
@ -22,6 +22,7 @@ query getOrganizationGroups(
) {
nodes {
...Group
organizationEditPath
}
pageInfo {
...PageInfo

View File

@ -297,13 +297,6 @@ export default {
:show-cleanup-policy-link="config.showCleanupPolicyLink"
>
<template #commands>
<cli-commands
v-if="showCommands"
:docker-build-command="dockerBuildCommand"
:docker-push-command="dockerPushCommand"
:docker-login-command="dockerLoginCommand"
class="!gl-w-auto"
/>
<gl-button
v-if="config.showContainerRegistrySettings"
v-gl-tooltip="$options.i18n.SETTINGS_TEXT"
@ -312,6 +305,13 @@ export default {
:href="config.settingsPath"
:aria-label="$options.i18n.SETTINGS_TEXT"
/>
<cli-commands
v-if="showCommands"
:docker-build-command="dockerBuildCommand"
:docker-push-command="dockerPushCommand"
:docker-login-command="dockerLoginCommand"
class="!gl-w-auto"
/>
</template>
</registry-header>
<persisted-search

View File

@ -51,7 +51,7 @@ export default {
settingBlockDescription: s__(
'ContainerRegistry|When a container repository is protected, only users with specific roles can push and delete container images. This helps prevent unauthorized modifications.',
),
protectionRuleDeletionConfirmModal: {
deletionConfirmModal: {
title: s__('ContainerRegistry|Delete container repository protection rule?'),
descriptionWarning: s__(
'ContainerRegistry|You are about to delete the container repository protection rule for %{repositoryPathPattern}.',
@ -162,7 +162,7 @@ export default {
last: PAGINATION_DEFAULT_PER_PAGE,
};
},
showProtectionRuleDeletionConfirmModal(protectionRule) {
showDeletionConfirmModal(protectionRule) {
this.protectionRuleMutationItem = protectionRule;
},
clearAlertMessage() {
@ -172,10 +172,10 @@ export default {
this.protectionRuleMutationItem = null;
this.protectionRuleMutationInProgress = false;
},
isProtectionRuleMinimumAccessLevelForPushFormSelectDisabled(item) {
isMinimumAccessLevelForPushDisabled(item) {
return this.isProtectionRuleMutationInProgress(item);
},
isProtectionRuleDeleteButtonDisabled(item) {
isDeleteActionDisabled(item) {
return this.isProtectionRuleMutationInProgress(item);
},
isProtectionRuleMutationInProgress(item) {
@ -207,12 +207,12 @@ export default {
this.resetProtectionRuleMutation();
});
},
updateProtectionRuleMinimumAccessLevelForPush(protectionRule) {
updateMinimumAccessLevelForPush(protectionRule) {
this.updateProtectionRule(protectionRule, {
minimumAccessLevelForPush: protectionRule.minimumAccessLevelForPush || null,
});
},
updateProtectionRuleMinimumAccessLevelForDelete(protectionRule) {
updateMinimumAccessLevelForDelete(protectionRule) {
this.updateProtectionRule(protectionRule, {
minimumAccessLevelForDelete: protectionRule.minimumAccessLevelForDelete || null,
});
@ -334,9 +334,9 @@ export default {
required
:aria-label="$options.i18n.minimumAccessLevelForPush"
:options="containerRepositoryMinimumAccessLevelOptions"
:disabled="isProtectionRuleMinimumAccessLevelForPushFormSelectDisabled(item)"
:disabled="isMinimumAccessLevelForPushDisabled(item)"
data-testid="minimum-access-level-for-push-select"
@change="updateProtectionRuleMinimumAccessLevelForPush(item)"
@change="updateMinimumAccessLevelForPush(item)"
/>
</template>
@ -348,8 +348,8 @@ export default {
:aria-label="$options.i18n.minimumAccessLevelForDelete"
:options="containerRepositoryMinimumAccessLevelOptions"
data-testid="minimum-access-level-for-delete-select"
:disabled="isProtectionRuleMinimumAccessLevelForPushFormSelectDisabled(item)"
@change="updateProtectionRuleMinimumAccessLevelForDelete(item)"
:disabled="isMinimumAccessLevelForPushDisabled(item)"
@change="updateMinimumAccessLevelForDelete(item)"
/>
</template>
@ -361,9 +361,9 @@ export default {
icon="remove"
:title="__('Delete')"
:aria-label="__('Delete')"
:disabled="isProtectionRuleDeleteButtonDisabled(item)"
:disabled="isDeleteActionDisabled(item)"
data-testid="delete-btn"
@click="showProtectionRuleDeletionConfirmModal(item)"
@click="showDeletionConfirmModal(item)"
/>
</template>
</gl-table>
@ -386,19 +386,19 @@ export default {
v-if="protectionRuleMutationItem"
:modal-id="$options.modal.id"
size="sm"
:title="$options.i18n.protectionRuleDeletionConfirmModal.title"
:title="$options.i18n.deletionConfirmModal.title"
:action-primary="$options.modalActionPrimary"
:action-cancel="$options.modalActionCancel"
@primary="deleteProtectionRule(protectionRuleMutationItem)"
>
<p>
<gl-sprintf :message="$options.i18n.protectionRuleDeletionConfirmModal.descriptionWarning">
<gl-sprintf :message="$options.i18n.deletionConfirmModal.descriptionWarning">
<template #repositoryPathPattern>
<strong>{{ protectionRuleMutationItem.repositoryPathPattern }}</strong>
</template>
</gl-sprintf>
</p>
<p>{{ $options.i18n.protectionRuleDeletionConfirmModal.descriptionConsequence }}</p>
<p>{{ $options.i18n.deletionConfirmModal.descriptionConsequence }}</p>
</gl-modal>
</div>
</template>

View File

@ -201,7 +201,7 @@ export default {
this.protectionRuleMutationItem = null;
this.protectionRuleMutationInProgress = false;
},
showProtectionRuleDeletionConfirmModal(protectionRule) {
showDeletionConfirmModal(protectionRule) {
this.protectionRuleMutationItem = protectionRule;
this.showModal = true;
},
@ -233,7 +233,7 @@ export default {
deleteIconButton: __('Delete'),
editIconButton: __('Edit'),
title: s__('ContainerRegistry|Protected container image tags'),
protectionRuleDeletionConfirmModal: {
deletionConfirmModal: {
title: s__('ContainerRegistry|Delete protection rule'),
description: s__(
'ContainerRegistry|Are you sure you want to delete the protected container tags rule %{tagNamePattern}?',
@ -359,7 +359,7 @@ export default {
icon="remove"
:title="$options.i18n.deleteIconButton"
:aria-label="$options.i18n.deleteIconButton"
@click="showProtectionRuleDeletionConfirmModal(item)"
@click="showDeletionConfirmModal(item)"
/>
</div>
</template>
@ -387,13 +387,13 @@ export default {
v-model="showModal"
:modal-id="$options.modal.id"
size="sm"
:title="$options.i18n.protectionRuleDeletionConfirmModal.title"
:title="$options.i18n.deletionConfirmModal.title"
:action-primary="$options.modalActionPrimary"
:action-cancel="$options.modalActionCancel"
@primary="deleteProtectionRule(protectionRuleMutationItem)"
>
<p>
<gl-sprintf :message="$options.i18n.protectionRuleDeletionConfirmModal.description">
<gl-sprintf :message="$options.i18n.deletionConfirmModal.description">
<template #tagNamePattern>
<strong>{{ mutationItemTagNamePattern }}</strong>
</template>

View File

@ -12,7 +12,7 @@ import {
GlSprintf,
} from '@gitlab/ui';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import packagesProtectionRuleQuery from '~/packages_and_registries/settings/project/graphql/queries/get_packages_protection_rules.query.graphql';
import getPackagesProtectionRuleQuery from '~/packages_and_registries/settings/project/graphql/queries/get_packages_protection_rules.query.graphql';
import { getPackageTypeLabel } from '~/packages_and_registries/package_registry/utils';
import deletePackagesProtectionRuleMutation from '~/packages_and_registries/settings/project/graphql/mutations/delete_packages_protection_rule.mutation.graphql';
import PackagesProtectionRuleForm from '~/packages_and_registries/settings/project/components/packages_protection_rule_form.vue';
@ -55,8 +55,7 @@ export default {
settingBlockDescription: s__(
'PackageRegistry|When a package is protected, only certain user roles can push, update, and delete the protected package, which helps to avoid tampering with the package.',
),
createProtectionRuleText: s__('PackageRegistry|Add protection rule'),
protectionRuleDeletionConfirmModal: {
deletionConfirmModal: {
title: s__('PackageRegistry|Delete package protection rule?'),
descriptionWarning: s__(
'PackageRegistry|You are about to delete the package protection rule for %{packageNamePattern}.',
@ -68,23 +67,43 @@ export default {
minimumAccessLevelForPush: I18N_MINIMUM_ACCESS_LEVEL_FOR_PUSH,
minimumAccessLevelForDelete: I18N_MINIMUM_ACCESS_LEVEL_FOR_DELETE,
},
apollo: {
protectionRulesQueryPayload: {
query: getPackagesProtectionRuleQuery,
context: {
batchKey: 'PackageRegistryProjectSettings',
},
variables() {
return {
projectPath: this.projectPath,
...this.protectionRulesQueryPaginationParams,
};
},
update(data) {
return data.project?.packagesProtectionRules ?? this.protectionRulesQueryPayload;
},
error(e) {
this.alertErrorMessage = e.message;
},
},
},
data() {
return {
packageProtectionRules: [],
packageProtectionRulesQueryPayload: { nodes: [], pageInfo: {} },
packageProtectionRulesQueryPaginationParams: { first: PAGINATION_DEFAULT_PER_PAGE },
protectionRuleMutationInProgress: false,
protectionRuleMutationItem: null,
protectionRules: [],
protectionRulesQueryPayload: { nodes: [], pageInfo: {} },
protectionRulesQueryPaginationParams: { first: PAGINATION_DEFAULT_PER_PAGE },
mutationInProgress: false,
mutationItem: null,
alertErrorMessage: '',
showDrawer: false,
};
},
computed: {
containsTableItems() {
return this.packageProtectionRulesQueryResult.length > 0;
return this.protectionRulesQueryResult.length > 0;
},
drawerTitle() {
return this.protectionRuleMutationItem
return this.mutationItem
? s__('PackageRegistry|Edit protection rule')
: s__('PackageRegistry|Add protection rule');
},
@ -94,70 +113,50 @@ export default {
: this.$options.fields.filter((field) => field.key !== 'minimumAccessLevelForDelete');
},
tableItems() {
return this.packageProtectionRulesQueryResult.map((packagesProtectionRule) => {
return this.protectionRulesQueryResult.map((protectionRule) => {
return {
id: packagesProtectionRule.id,
minimumAccessLevelForDelete: packagesProtectionRule.minimumAccessLevelForDelete,
minimumAccessLevelForPush: packagesProtectionRule.minimumAccessLevelForPush,
packageNamePattern: packagesProtectionRule.packageNamePattern,
packageType: packagesProtectionRule.packageType,
id: protectionRule.id,
minimumAccessLevelForDelete: protectionRule.minimumAccessLevelForDelete,
minimumAccessLevelForPush: protectionRule.minimumAccessLevelForPush,
packageNamePattern: protectionRule.packageNamePattern,
packageType: protectionRule.packageType,
};
});
},
packageProtectionRulesQueryPageInfo() {
return this.packageProtectionRulesQueryPayload.pageInfo;
protectionRulesQueryPageInfo() {
return this.protectionRulesQueryPayload.pageInfo;
},
packageProtectionRulesQueryResult() {
return this.packageProtectionRulesQueryPayload.nodes;
protectionRulesQueryResult() {
return this.protectionRulesQueryPayload.nodes;
},
isLoadingPackageProtectionRules() {
return this.$apollo.queries.packageProtectionRulesQueryPayload.loading;
isLoadingProtectionRules() {
return this.$apollo.queries.protectionRulesQueryPayload.loading;
},
showTopLevelLoadingIcon() {
return this.isLoadingPackageProtectionRules && !this.containsTableItems;
return this.isLoadingProtectionRules && !this.containsTableItems;
},
toastMessage() {
return this.protectionRuleMutationItem
return this.mutationItem
? s__('PackageRegistry|Package protection rule updated.')
: s__('PackageRegistry|Package protection rule created.');
},
},
apollo: {
packageProtectionRulesQueryPayload: {
query: packagesProtectionRuleQuery,
context: {
batchKey: 'PackageRegistryProjectSettings',
},
variables() {
return {
projectPath: this.projectPath,
...this.packageProtectionRulesQueryPaginationParams,
};
},
update(data) {
return data.project?.packagesProtectionRules ?? this.packageProtectionRulesQueryPayload;
},
error(e) {
this.alertErrorMessage = e.message;
},
},
},
methods: {
closeDrawer() {
this.showDrawer = false;
},
refetchProtectionRules() {
this.$apollo.queries.packageProtectionRulesQueryPayload.refetch();
this.$apollo.queries.protectionRulesQueryPayload.refetch();
},
onNextPage() {
this.packageProtectionRulesQueryPaginationParams = {
after: this.packageProtectionRulesQueryPageInfo.endCursor,
this.protectionRulesQueryPaginationParams = {
after: this.protectionRulesQueryPageInfo.endCursor,
first: PAGINATION_DEFAULT_PER_PAGE,
};
},
onPrevPage() {
this.packageProtectionRulesQueryPaginationParams = {
before: this.packageProtectionRulesQueryPageInfo.startCursor,
this.protectionRulesQueryPaginationParams = {
before: this.protectionRulesQueryPageInfo.startCursor,
last: PAGINATION_DEFAULT_PER_PAGE,
};
},
@ -167,20 +166,20 @@ export default {
this.refetchProtectionRules();
},
openEditFormDrawer(item) {
this.protectionRuleMutationItem = item;
this.mutationItem = item;
this.showDrawer = true;
},
openNewFormDrawer() {
this.protectionRuleMutationItem = null;
this.mutationItem = null;
this.showDrawer = true;
},
showProtectionRuleDeletionConfirmModal(protectionRule) {
this.protectionRuleMutationItem = protectionRule;
showDeletionConfirmModal(protectionRule) {
this.mutationItem = protectionRule;
},
deleteProtectionRule(protectionRule) {
this.clearAlertMessage();
this.protectionRuleMutationInProgress = true;
this.mutationInProgress = true;
return this.$apollo
.mutate({
@ -207,14 +206,14 @@ export default {
this.alertErrorMessage = '';
},
resetProtectionRuleMutation() {
this.protectionRuleMutationItem = null;
this.protectionRuleMutationInProgress = false;
this.mutationItem = null;
this.mutationInProgress = false;
},
isProtectionRuleDeleteButtonDisabled(item) {
return this.isProtectionRuleMutationInProgress(item);
isDeleteButtonDisabled(item) {
return this.isMutationInProgress(item);
},
isProtectionRuleMutationInProgress(item) {
return this.protectionRuleMutationItem === item && this.protectionRuleMutationInProgress;
isMutationInProgress(item) {
return this.mutationItem === item && this.mutationInProgress;
},
},
fields: [
@ -268,7 +267,7 @@ export default {
ref="packagesCrud"
:title="$options.i18n.settingBlockTitle"
:description="$options.i18n.settingBlockDescription"
:toggle-text="$options.i18n.createProtectionRuleText"
:toggle-text="s__('PackageRegistry|Add protection rule')"
@showForm="openNewFormDrawer"
>
<template #default>
@ -289,7 +288,7 @@ export default {
:fields="fields"
stacked="md"
:aria-label="$options.i18n.settingBlockTitle"
:busy="isLoadingPackageProtectionRules"
:busy="isLoadingProtectionRules"
>
<template #table-busy>
<gl-loading-icon size="sm" class="gl-my-5" />
@ -334,8 +333,8 @@ export default {
:title="$options.i18n.delete"
:aria-label="$options.i18n.delete"
data-testid="delete-rule-btn"
:disabled="isProtectionRuleDeleteButtonDisabled(item)"
@click="showProtectionRuleDeletionConfirmModal(item)"
:disabled="isDeleteButtonDisabled(item)"
@click="showDeletionConfirmModal(item)"
/>
</div>
</template>
@ -351,7 +350,7 @@ export default {
</template>
<template #default>
<packages-protection-rule-form
:rule="protectionRuleMutationItem"
:rule="mutationItem"
@cancel="closeDrawer"
@submit="handleSubmit"
/>
@ -361,7 +360,7 @@ export default {
<template #pagination>
<gl-keyset-pagination
v-bind="packageProtectionRulesQueryPageInfo"
v-bind="protectionRulesQueryPageInfo"
@prev="onPrevPage"
@next="onNextPage"
/>
@ -369,22 +368,22 @@ export default {
</crud-component>
<gl-modal
v-if="protectionRuleMutationItem"
v-if="mutationItem"
:modal-id="$options.modal.id"
size="sm"
:title="$options.i18n.protectionRuleDeletionConfirmModal.title"
:title="$options.i18n.deletionConfirmModal.title"
:action-primary="$options.modalActionPrimary"
:action-cancel="$options.modalActionCancel"
@primary="deleteProtectionRule(protectionRuleMutationItem)"
@primary="deleteProtectionRule(mutationItem)"
>
<p>
<gl-sprintf :message="$options.i18n.protectionRuleDeletionConfirmModal.descriptionWarning">
<gl-sprintf :message="$options.i18n.deletionConfirmModal.descriptionWarning">
<template #packageNamePattern>
<strong>{{ protectionRuleMutationItem.packageNamePattern }}</strong>
<strong>{{ mutationItem.packageNamePattern }}</strong>
</template>
</gl-sprintf>
</p>
<p>{{ $options.i18n.protectionRuleDeletionConfirmModal.descriptionConsequence }}</p>
<p>{{ $options.i18n.deletionConfirmModal.descriptionConsequence }}</p>
</gl-modal>
</div>
</template>

View File

@ -6,7 +6,7 @@ query getBlobSearchQuery(
$chunkCount: Int
$regex: Boolean
$includeArchived: Boolean
$includeForked: Boolean
$excludeForks: Boolean
) {
blobSearch(
search: $search
@ -16,7 +16,7 @@ query getBlobSearchQuery(
chunkCount: $chunkCount
regex: $regex
includeArchived: $includeArchived
includeForked: $includeForked
excludeForks: $excludeForks
) {
fileCount
files {

View File

@ -5,7 +5,7 @@ query getBlobSearchCountQuery(
$chunkCount: Int
$regex: Boolean
$includeArchived: Boolean
$includeForked: Boolean
$excludeForks: Boolean
) {
blobSearch(
search: $search
@ -14,7 +14,7 @@ query getBlobSearchCountQuery(
chunkCount: $chunkCount
regex: $regex
includeArchived: $includeArchived
includeForked: $includeForked
excludeForks: $excludeForks
) {
matchCount
}

View File

@ -6,6 +6,7 @@ import getBlobSearchQuery from '~/search/graphql/blob_search_zoekt.query.graphql
import { ERROR_POLICY_NONE } from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import { logError } from '~/lib/logger';
import { EXCLUDE_FORKS_FILTER_PARAM } from '~/search/sidebar/constants';
import { DEFAULT_FETCH_CHUNKS } from '../constants';
import { RECEIVE_NAVIGATION_COUNT } from '../../store/mutation_types';
import EmptyResult from './result_empty.vue';
@ -47,7 +48,7 @@ export default {
chunkCount: DEFAULT_FETCH_CHUNKS,
regex: parseBoolean(this.query?.regex),
includeArchived: parseBoolean(this.query?.include_archived),
includeForked: parseBoolean(this.query?.include_forked),
excludeForks: parseBoolean(this.query?.[EXCLUDE_FORKS_FILTER_PARAM]),
};
if (this.query?.group_id) {

View File

@ -7,7 +7,7 @@ import { InternalEvents } from '~/tracking';
import { parseBoolean } from '~/lib/utils/common_utils';
import {
EVENT_CLICK_ZOEKT_INCLUDE_FORKS_ON_SEARCH_RESULTS_PAGE,
INCLUDE_FORKED_FILTER_PARAM,
EXCLUDE_FORKS_FILTER_PARAM,
} from '~/search/sidebar/constants';
const trackingMixin = InternalEvents.mixin();
@ -23,21 +23,21 @@ export default {
},
mixins: [trackingMixin],
i18n: {
TOOLTIP: s__('GlobalSearch|Include search results from forked projects'),
TOOLTIP: s__('GlobalSearch|Exclude search results from forked projects'),
HEADER_LABEL: s__('GlobalSearch|Forks'),
CHECKBOX_LABEL: s__('GlobalSearch|Include forks'),
CHECKBOX_LABEL: s__('GlobalSearch|Exclude forks'),
},
computed: {
...mapState(['urlQuery']),
selectedFilter: {
get() {
return [parseBoolean(this.urlQuery?.[INCLUDE_FORKED_FILTER_PARAM])];
return [parseBoolean(this.urlQuery?.[EXCLUDE_FORKS_FILTER_PARAM])];
},
set(value) {
const includeForked = [...value].pop() ?? false;
const excludeForks = [...value].pop() ?? false;
this.setQuery({
key: INCLUDE_FORKED_FILTER_PARAM,
value: includeForked?.toString(),
key: EXCLUDE_FORKS_FILTER_PARAM,
value: excludeForks?.toString(),
});
},
},

View File

@ -70,7 +70,7 @@ export const LANGUAGE_MAX_ITEM_LENGTH = 100;
export const INCLUDE_ARCHIVED_FILTER_PARAM = 'include_archived';
export const CONFIDENTAL_FILTER_PARAM = 'confidential';
export const LABEL_FILTER_PARAM = 'label_name';
export const INCLUDE_FORKED_FILTER_PARAM = 'include_forked';
export const EXCLUDE_FORKS_FILTER_PARAM = 'exclude_forks';
export const LANGUAGE_FILTER_PARAM = 'language';
export const SOURCE_BRANCH_PARAM = 'source_branch';
export const NOT_SOURCE_BRANCH_PARAM = 'not[source_branch]';

View File

@ -3,7 +3,7 @@ import {
CONFIDENTAL_FILTER_PARAM,
INCLUDE_ARCHIVED_FILTER_PARAM,
LABEL_FILTER_PARAM,
INCLUDE_FORKED_FILTER_PARAM,
EXCLUDE_FORKS_FILTER_PARAM,
LANGUAGE_FILTER_PARAM,
SOURCE_BRANCH_PARAM,
NOT_SOURCE_BRANCH_PARAM,
@ -26,7 +26,7 @@ export const SIDEBAR_PARAMS = [
LANGUAGE_FILTER_PARAM,
LABEL_FILTER_PARAM,
INCLUDE_ARCHIVED_FILTER_PARAM,
INCLUDE_FORKED_FILTER_PARAM,
EXCLUDE_FORKS_FILTER_PARAM,
SOURCE_BRANCH_PARAM,
NOT_SOURCE_BRANCH_PARAM,
AUTHOR_PARAM,

View File

@ -22,7 +22,7 @@ export default {
support: __('Support'),
docs: __('GitLab documentation'),
plans: __('Compare GitLab plans'),
forum: __('Community forum'),
forum: __('GitLab community forum'),
contribute: __('Contribute to GitLab'),
feedback: __('Provide feedback'),
shortcuts: __('Keyboard shortcuts'),

View File

@ -36,6 +36,11 @@ export default {
required: false,
default: true,
},
asButton: {
type: Boolean,
default: true,
required: false,
},
},
computed: {
changedIcon() {
@ -79,7 +84,7 @@ export default {
<template>
<gl-button
v-if="showIcon"
v-if="showIcon && asButton"
v-gl-tooltip.right
category="tertiary"
size="small"
@ -90,6 +95,15 @@ export default {
>
<gl-icon :name="changedIcon" :size="size" :class="changedIconClass" />
</gl-button>
<span
v-else-if="showIcon"
v-gl-tooltip.right="tooltipTitle"
:class="{ 'ml-auto': isCentered }"
:aria-label="tooltipTitle"
class="file-changed-icon"
>
<gl-icon :name="changedIcon" :size="size" :class="changedIconClass" />
</span>
</template>
<style>

View File

@ -124,49 +124,51 @@ export default {
<template>
<file-header v-if="file.isHeader" :path="file.path" />
<div
<button
v-else
:class="fileClass"
:title="textForTitle"
:data-level="level"
class="file-row"
role="button"
:aria-expanded="file.type === 'tree' ? file.opened.toString() : undefined"
@click="clickFile"
@mouseleave="$emit('mouseleave', $event)"
>
<div class="file-row-name-container">
<span
ref="textOutput"
class="file-row-name"
:title="file.name"
:data-qa-file-name="file.name"
data-testid="file-row-name-container"
:class="[fileClasses, { 'str-truncated': !truncateMiddle, 'gl-min-w-0': truncateMiddle }]"
>
<gl-icon
v-if="file.linked"
v-gl-tooltip="
__('This file was linked in the page URL and will appear as the first one in the list')
"
name="link"
:size="16"
/>
<file-icon
class="file-row-icon"
:class="{ 'gl-text-subtle': file.type === 'tree' }"
:file-name="file.name"
:loading="file.loading"
:folder="isTree"
:opened="file.opened"
:size="16"
:submodule="file.submodule"
/>
<gl-truncate v-if="truncateMiddle" :text="file.name" position="middle" class="gl-pr-7" />
<template v-else>{{ file.name }}</template>
</span>
<slot></slot>
</div>
</div>
<span
ref="textOutput"
class="file-row-name"
:title="file.name"
:data-qa-file-name="file.name"
data-testid="file-row-name-container"
:class="[fileClasses, { 'str-truncated': !truncateMiddle, 'gl-min-w-0': truncateMiddle }]"
>
<gl-icon
v-if="file.linked"
v-gl-tooltip="
__('This file was linked in the page URL and will appear as the first one in the list')
"
name="link"
:size="16"
/>
<file-icon
class="gl-mr-2"
:class="{ 'gl-text-subtle': file.type === 'tree' }"
:file-name="file.name"
:loading="file.loading"
:folder="isTree"
:opened="file.opened"
:size="16"
:submodule="file.submodule"
/>
<gl-truncate
v-if="truncateMiddle"
:text="file.name"
position="middle"
class="gl-items-center gl-pr-7"
/>
<template v-else>{{ file.name }}</template>
</span>
<slot></slot>
</button>
</template>
<style>
@ -180,6 +182,7 @@ export default {
border-radius: 3px;
text-align: left;
cursor: pointer;
color: unset;
}
.file-row-name-container {
@ -190,18 +193,13 @@ export default {
}
.file-row-name {
display: inline-block;
display: flex;
align-items: center;
flex: 1;
max-width: inherit;
height: 19px;
line-height: 16px;
line-height: 1rem;
text-overflow: ellipsis;
white-space: nowrap;
margin-left: calc(var(--level) * var(--file-row-level-padding, 16px));
}
.file-row-name .file-row-icon {
margin-right: 2px;
vertical-align: middle;
}
</style>

View File

@ -451,9 +451,6 @@ export default {
showWorkItemTree() {
return this.findWidget(WIDGET_TYPE_HIERARCHY) && this.allowedChildTypes?.length > 0;
},
showWorkItemVulnerabilities() {
return this.glFeatures.workItemRelatedVulnerabilities;
},
titleClassHeader() {
return {
'sm:!gl-hidden gl-mt-3': this.shouldShowAncestors,
@ -1219,7 +1216,6 @@ export default {
/>
<work-item-vulnerabilities
v-if="showWorkItemVulnerabilities"
:work-item-iid="iid"
:work-item-full-path="workItemFullPath"
data-testid="work-item-vulnerabilities"

View File

@ -7,10 +7,10 @@
padding: $gl-padding-8;
.issue {
background-color: var(--white, $white);
background-color: var(--gl-background-color-section);
margin-bottom: $gl-padding-8;
@apply gl-rounded-base;
border: 1px solid var(--gl-border-color-default);
border: 1px solid var(--gl-border-color-section);
box-shadow: 0 1px 2px $issue-boards-card-shadow;
}
}

View File

@ -300,11 +300,14 @@ $diff-file-header-top: 11px;
.tree-list-holder {
height: 100%;
}
.file-row {
margin-left: 0;
margin-right: 0;
}
.tree-list-holder .file-row {
width: calc(100% - #{$gl-spacing-scale-1} * 2);
margin: 0 0 0 $gl-spacing-scale-1;
border: 0;
min-width: 0;
background: transparent;
}
.tree-list-gutter {

View File

@ -116,10 +116,16 @@ module ProjectsHelper
end
def remove_project_message(project)
_(
"You are going to delete %{project_full_name}. Deleted projects " \
"CANNOT be restored! Are you ABSOLUTELY sure?"
) % { project_full_name: project.full_name }
if project.delayed_deletion_ready?
_("Deleting a project places it into a read-only state until %{date}, " \
"at which point the project will be permanently deleted. Are you ABSOLUTELY sure?"
) % { date: permanent_deletion_date_formatted(Date.current) }
else
_(
"You are going to delete %{project_full_name}. Deleted projects " \
"CANNOT be restored! Are you ABSOLUTELY sure?"
) % { project_full_name: project.full_name }
end
end
def link_to_namespace_change_doc

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Packages
module Conan
module PackageFileable
extend ActiveSupport::Concern
included do
has_many :file_metadata, inverse_of: name.demodulize.underscore.to_sym,
class_name: 'Packages::Conan::FileMetadatum'
has_many :package_files, through: :file_metadata
def orphan?
package_files.empty?
end
end
end
end
end

View File

@ -4,6 +4,7 @@ module Packages
module Conan
class PackageReference < ApplicationRecord
include ShaAttribute
include PackageFileable
REFERENCE_LENGTH_MAX = 40
MAX_INFO_SIZE = 20_000
@ -17,7 +18,6 @@ module Packages
belongs_to :project
has_many :package_revisions, inverse_of: :package_reference, class_name: 'Packages::Conan::PackageRevision'
has_many :file_metadata, inverse_of: :package_reference, class_name: 'Packages::Conan::FileMetadatum'
validates :package, :project, presence: true
validates :reference, presence: true, format: { with: Gitlab::Regex.conan_package_reference_regex },

View File

@ -4,6 +4,7 @@ module Packages
module Conan
class PackageRevision < ApplicationRecord
include ShaAttribute
include PackageFileable
sha_attribute :revision
@ -12,8 +13,6 @@ module Packages
inverse_of: :package_revisions
belongs_to :project
has_many :file_metadata, inverse_of: :package_revision, class_name: 'Packages::Conan::FileMetadatum'
validates :package, :package_reference, :project, presence: true
validates :revision, presence: true, format: { with: ::Gitlab::Regex.conan_revision_regex_v2 }
validates :revision, uniqueness: { scope: [:package_id, :package_reference_id] }, on: %i[create update]

View File

@ -4,6 +4,7 @@ module Packages
module Conan
class RecipeRevision < ApplicationRecord
include ShaAttribute
include PackageFileable
sha_attribute :revision
@ -12,7 +13,6 @@ module Packages
has_many :conan_package_references, inverse_of: :recipe_revision,
class_name: 'Packages::Conan::PackageReference'
has_many :file_metadata, inverse_of: :recipe_revision, class_name: 'Packages::Conan::FileMetadatum'
validates :package, :project, presence: true
validates :revision, presence: true, format: { with: ::Gitlab::Regex.conan_revision_regex_v2 }

View File

@ -475,6 +475,7 @@ class User < ApplicationRecord
delegate :pronouns, :pronouns=, to: :user_detail, allow_nil: true
delegate :pronunciation, :pronunciation=, to: :user_detail, allow_nil: true
delegate :bluesky, :bluesky=, to: :user_detail, allow_nil: true
delegate :orcid, :orcid=, to: :user_detail, allow_nil: true
delegate :mastodon, :mastodon=, to: :user_detail, allow_nil: true
delegate :linkedin, :linkedin=, to: :user_detail, allow_nil: true
delegate :twitter, :twitter=, to: :user_detail, allow_nil: true

View File

@ -37,6 +37,15 @@ class UserDetail < ApplicationRecord
\z # end of string
/x
ORCID_VALIDATION_REGEX = /
\A # beginning of string
( #
[0-9]{4}- # 4 digits spaced by dash
){3} # 3 times
[0-9]{4} # end with 4 digits
\z # end of string
/x
validates :discord, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
validate :discord_format
validates :linkedin, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
@ -47,6 +56,8 @@ class UserDetail < ApplicationRecord
message: proc { s_('Profiles|must contain only a bluesky did:plc identifier.') } }
validates :mastodon, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
validate :mastodon_format
validates :orcid, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
validate :orcid_format
validates :organization, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
validates :skype, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
validates :twitter, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
@ -57,7 +68,7 @@ class UserDetail < ApplicationRecord
before_save :prevent_nil_fields
def sanitize_attrs
%i[bluesky discord linkedin mastodon skype twitter website_url].each do |attr|
%i[bluesky discord linkedin mastodon orcid skype twitter website_url].each do |attr|
value = self[attr]
self[attr] = Sanitize.clean(value) if value.present?
end
@ -77,6 +88,7 @@ class UserDetail < ApplicationRecord
self.location = '' if location.nil?
self.mastodon = '' if mastodon.nil?
self.organization = '' if organization.nil?
self.orcid = '' if orcid.nil?
self.skype = '' if skype.nil?
self.twitter = '' if twitter.nil?
self.website_url = '' if website_url.nil?
@ -102,4 +114,10 @@ def mastodon_format
errors.add(:mastodon, _('must contain only a mastodon handle.'))
end
def orcid_format
return if orcid.blank? || orcid =~ UserDetail::ORCID_VALIDATION_REGEX
errors.add(:orcid, _('must contain only a orcid ID.'))
end
UserDetail.prepend_mod_with('UserDetail')

View File

@ -6,7 +6,7 @@
= link_to _("Explore"), explore_root_path
= link_to _("Help"), help_path
= link_to _("About GitLab"), ApplicationHelper.promo_url
= link_to _("Community forum"), ApplicationHelper.community_forum, target: '_blank', class: 'text-nowrap', rel: 'noopener noreferrer'
= link_to _("GitLab community forum"), ApplicationHelper.community_forum, target: '_blank', class: 'text-nowrap', rel: 'noopener noreferrer'
- if one_trust_enabled?
= render Pajamas::ButtonComponent.new(category: :tertiary, size: :small, button_options: { class: 'ot-sdk-show-settings' }) do
= _("Cookie Preferences")

View File

@ -10,6 +10,8 @@ module Packages
def perform_work
return unless artifact
before_destroy
begin
artifact.transaction do
log_metadata(artifact)
@ -48,6 +50,10 @@ module Packages
raise NotImplementedError
end
def before_destroy
# no op
end
def after_destroy
# no op
end

View File

@ -32,20 +32,10 @@ module Gitlab
private
# Necessary temporarily as new version might process jobs enqueued in old version
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/520048
def ensure_correct_work_item_type(attributes)
return attributes unless attributes.key?('correct_work_item_type_id')
work_item_type_id = attributes['correct_work_item_type_id']
attributes.except('correct_work_item_type_id').merge('work_item_type_id' => work_item_type_id)
end
def create_issue(issue_attributes, project_id)
label_ids = issue_attributes.delete('label_ids')
assignee_ids = issue_attributes.delete('assignee_ids')
issue_id = insert_and_return_id(ensure_correct_work_item_type(issue_attributes), Issue)
issue_id = insert_and_return_id(issue_attributes, Issue)
label_issue(project_id, issue_id, label_ids)
assign_issue(project_id, issue_id, assignee_ids)

View File

@ -13,20 +13,30 @@ module Packages
worker_resource_boundary :unknown
idempotent!
delegate :package, :conan_file_metadatum, to: :artifact, private: true
delegate :recipe_revision, :package_reference, :package_revision, to: :conan_file_metadatum, private: true
def max_running_jobs
::Gitlab::CurrentSettings.packages_cleanup_package_file_worker_capacity
end
private
def before_destroy
# Load the metadatum into the memory for subsequent operations, since the metadatum is deleted
# by CASCADE delete when package file is deleted.
conan_file_metadatum if package.conan?
end
def after_destroy
pkg = artifact.package
# Ml::ModelVersion need the package to be able to upload files later
# Issue https://gitlab.com/gitlab-org/gitlab/-/issues/461322
return if pkg.ml_model?
return if package.ml_model?
pkg.transaction do
pkg.destroy if model.for_package_ids(pkg.id).empty?
cleanup_conan_objects if package.conan?
package.transaction do
package.destroy if model.for_package_ids(package.id).empty?
end
end
@ -38,6 +48,16 @@ module Packages
model.next_pending_destruction(order_by: :id)
end
def cleanup_conan_objects
return unless conan_file_metadatum
# return early as destroy will trigger the cascading delete
return if recipe_revision&.orphan? && recipe_revision.destroy
return if package_reference&.orphan? && package_reference.destroy
package_revision.destroy if package_revision&.orphan?
end
def log_metadata(package_file)
log_extra_metadata_on_done(:package_file_id, package_file.id)
log_extra_metadata_on_done(:package_id, package_file.package_id)

View File

@ -2,8 +2,8 @@
name: improved_review_experience
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/525841
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/185795
rollout_issue_url:
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/535461
milestone: '17.11'
group: group::code review
type: wip
type: beta
default_enabled: false

View File

@ -10,3 +10,5 @@ gitlab_schema: gitlab_ci
sharding_key:
project_id: projects
table_size: over_limit
removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/191033
removed_in_milestone: '18.1'

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddOrcidToUser < Gitlab::Database::Migration[2.3]
USER_DETAILS_FIELD_LIMIT = 256
disable_ddl_transaction!
milestone '18.0'
def up
with_lock_retries do
add_column :user_details, :orcid, :text, default: '', null: false, if_not_exists: true
end
add_text_limit :user_details, :orcid, USER_DETAILS_FIELD_LIMIT
end
def down
with_lock_retries do
remove_column :user_details, :orcid
end
end
end

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
class MoveCiBuildsMetadataToDynamicSchema < Gitlab::Database::Migration[2.3]
include Gitlab::Database::MigrationHelpers::WraparoundAutovacuum
milestone '18.1'
DYNAMIC_SCHEMA = Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA
TABLE_NAME = :ci_builds_metadata
def up
return unless can_execute_on?(TABLE_NAME)
connection.execute(<<~SQL)
ALTER TABLE IF EXISTS #{TABLE_NAME} SET SCHEMA #{DYNAMIC_SCHEMA};
SQL
end
def down
return unless can_execute_on?(TABLE_NAME)
table_identifier = "#{DYNAMIC_SCHEMA}.#{TABLE_NAME}"
if table_exists?(table_identifier)
connection.execute(<<~SQL)
ALTER TABLE IF EXISTS #{table_identifier} SET SCHEMA #{connection.current_schema};
SQL
else # In tests we set the database from structure.sql, so the table doesn't exist
connection.execute(<<~SQL)
DROP TABLE IF EXISTS #{DYNAMIC_SCHEMA}.#{TABLE_NAME}_100;
CREATE TABLE IF NOT EXISTS #{TABLE_NAME} PARTITION OF p_#{TABLE_NAME} FOR VALUES IN (100);
SQL
end
end
end

View File

@ -0,0 +1 @@
d80822eca404b199f56ef3659fdc09c5c3452dc10e5339762c708ac4e099a5b3

View File

@ -0,0 +1 @@
39935f20dc0a333502460214553dc4dd35be6016a17df17e52f0f379cf0a88d3

View File

@ -10901,25 +10901,6 @@ CREATE SEQUENCE ci_builds_metadata_id_seq
ALTER SEQUENCE ci_builds_metadata_id_seq OWNED BY p_ci_builds_metadata.id;
CREATE TABLE ci_builds_metadata (
project_id bigint NOT NULL,
timeout integer,
timeout_source integer DEFAULT 1 NOT NULL,
interruptible boolean,
config_options jsonb,
config_variables jsonb,
has_exposed_artifacts boolean,
environment_auto_stop_in character varying(255),
expanded_environment_name character varying(255),
secrets jsonb DEFAULT '{}'::jsonb NOT NULL,
build_id bigint NOT NULL,
id bigint DEFAULT nextval('ci_builds_metadata_id_seq'::regclass) NOT NULL,
id_tokens jsonb DEFAULT '{}'::jsonb NOT NULL,
partition_id bigint NOT NULL,
debug_trace_enabled boolean DEFAULT false NOT NULL,
exit_code smallint
);
CREATE TABLE ci_builds_runner_session (
id bigint NOT NULL,
url character varying NOT NULL,
@ -24075,6 +24056,7 @@ CREATE TABLE user_details (
onboarding_status jsonb DEFAULT '{}'::jsonb NOT NULL,
bluesky text DEFAULT ''::text NOT NULL,
bot_namespace_id bigint,
orcid text DEFAULT ''::text NOT NULL,
CONSTRAINT check_18a53381cd CHECK ((char_length(bluesky) <= 256)),
CONSTRAINT check_245664af82 CHECK ((char_length(webauthn_xid) <= 100)),
CONSTRAINT check_444573ee52 CHECK ((char_length(skype) <= 500)),
@ -24084,6 +24066,7 @@ CREATE TABLE user_details (
CONSTRAINT check_7d6489f8f3 CHECK ((char_length(linkedin) <= 500)),
CONSTRAINT check_7fe2044093 CHECK ((char_length(website_url) <= 500)),
CONSTRAINT check_8a7fcf8a60 CHECK ((char_length(location) <= 500)),
CONSTRAINT check_99b0365865 CHECK ((char_length(orcid) <= 256)),
CONSTRAINT check_a73b398c60 CHECK ((char_length(phone) <= 50)),
CONSTRAINT check_eeeaf8d4f0 CHECK ((char_length(pronouns) <= 50)),
CONSTRAINT check_f1a8a05b9a CHECK ((char_length(mastodon) <= 500)),
@ -26600,8 +26583,6 @@ ALTER TABLE ONLY uploads_9ba88c4165 ATTACH PARTITION bulk_import_export_upload_u
ALTER TABLE ONLY p_ci_builds ATTACH PARTITION ci_builds FOR VALUES IN ('100');
ALTER TABLE ONLY p_ci_builds_metadata ATTACH PARTITION ci_builds_metadata FOR VALUES IN ('100');
ALTER TABLE ONLY p_ci_job_artifacts ATTACH PARTITION ci_job_artifacts FOR VALUES IN ('100', '101');
ALTER TABLE ONLY p_ci_pipelines ATTACH PARTITION ci_pipelines FOR VALUES IN ('100', '101', '102');
@ -29115,12 +29096,6 @@ ALTER TABLE ONLY ci_build_report_results
ALTER TABLE ONLY ci_build_trace_chunks
ADD CONSTRAINT ci_build_trace_chunks_pkey PRIMARY KEY (id);
ALTER TABLE ONLY p_ci_builds_metadata
ADD CONSTRAINT p_ci_builds_metadata_pkey PRIMARY KEY (id, partition_id);
ALTER TABLE ONLY ci_builds_metadata
ADD CONSTRAINT ci_builds_metadata_pkey PRIMARY KEY (id, partition_id);
ALTER TABLE ONLY p_ci_builds
ADD CONSTRAINT p_ci_builds_pkey PRIMARY KEY (id, partition_id);
@ -30279,6 +30254,9 @@ ALTER TABLE ONLY p_ci_build_trace_metadata
ALTER TABLE ONLY p_ci_builds_execution_configs
ADD CONSTRAINT p_ci_builds_execution_configs_pkey PRIMARY KEY (id, partition_id);
ALTER TABLE ONLY p_ci_builds_metadata
ADD CONSTRAINT p_ci_builds_metadata_pkey PRIMARY KEY (id, partition_id);
ALTER TABLE ONLY p_ci_finished_build_ch_sync_events
ADD CONSTRAINT p_ci_finished_build_ch_sync_events_pkey PRIMARY KEY (build_id, partition);
@ -34124,22 +34102,6 @@ CREATE INDEX index_ci_build_trace_chunks_on_partition_id_build_id ON ci_build_tr
CREATE INDEX index_ci_build_trace_chunks_on_project_id ON ci_build_trace_chunks USING btree (project_id);
CREATE INDEX p_ci_builds_metadata_build_id_idx ON ONLY p_ci_builds_metadata USING btree (build_id) WHERE (has_exposed_artifacts IS TRUE);
CREATE INDEX index_ci_builds_metadata_on_build_id_and_has_exposed_artifacts ON ci_builds_metadata USING btree (build_id) WHERE (has_exposed_artifacts IS TRUE);
CREATE INDEX p_ci_builds_metadata_build_id_id_idx ON ONLY p_ci_builds_metadata USING btree (build_id) INCLUDE (id) WHERE (interruptible = true);
CREATE INDEX index_ci_builds_metadata_on_build_id_and_id_and_interruptible ON ci_builds_metadata USING btree (build_id) INCLUDE (id) WHERE (interruptible = true);
CREATE UNIQUE INDEX p_ci_builds_metadata_build_id_partition_id_idx ON ONLY p_ci_builds_metadata USING btree (build_id, partition_id);
CREATE UNIQUE INDEX index_ci_builds_metadata_on_build_id_partition_id_unique ON ci_builds_metadata USING btree (build_id, partition_id);
CREATE INDEX p_ci_builds_metadata_project_id_idx ON ONLY p_ci_builds_metadata USING btree (project_id);
CREATE INDEX index_ci_builds_metadata_on_project_id ON ci_builds_metadata USING btree (project_id);
CREATE INDEX p_ci_builds_auto_canceled_by_id_idx ON ONLY p_ci_builds USING btree (auto_canceled_by_id) WHERE (auto_canceled_by_id IS NOT NULL);
CREATE INDEX index_ci_builds_on_auto_canceled_by_id ON ci_builds USING btree (auto_canceled_by_id) WHERE (auto_canceled_by_id IS NOT NULL);
@ -38302,6 +38264,14 @@ CREATE INDEX organization_detail_uploads_uploaded_by_user_id_idx ON organization
CREATE INDEX organization_detail_uploads_uploader_path_idx ON organization_detail_uploads USING btree (uploader, path);
CREATE INDEX p_ci_builds_metadata_build_id_id_idx ON ONLY p_ci_builds_metadata USING btree (build_id) INCLUDE (id) WHERE (interruptible = true);
CREATE INDEX p_ci_builds_metadata_build_id_idx ON ONLY p_ci_builds_metadata USING btree (build_id) WHERE (has_exposed_artifacts IS TRUE);
CREATE UNIQUE INDEX p_ci_builds_metadata_build_id_partition_id_idx ON ONLY p_ci_builds_metadata USING btree (build_id, partition_id);
CREATE INDEX p_ci_builds_metadata_project_id_idx ON ONLY p_ci_builds_metadata USING btree (project_id);
CREATE INDEX p_ci_builds_scheduled_at_idx ON ONLY p_ci_builds USING btree (scheduled_at) WHERE ((scheduled_at IS NOT NULL) AND ((type)::text = 'Ci::Build'::text) AND ((status)::text = 'scheduled'::text));
CREATE UNIQUE INDEX p_ci_builds_token_encrypted_partition_id_idx ON ONLY p_ci_builds USING btree (token_encrypted, partition_id) WHERE (token_encrypted IS NOT NULL);
@ -40502,8 +40472,6 @@ ALTER INDEX index_uploads_9ba88c4165_on_uploader_and_path ATTACH PARTITION bulk_
ALTER INDEX p_ci_builds_status_created_at_project_id_idx ATTACH PARTITION ci_builds_gitlab_monitor_metrics;
ALTER INDEX p_ci_builds_metadata_pkey ATTACH PARTITION ci_builds_metadata_pkey;
ALTER INDEX p_ci_builds_pkey ATTACH PARTITION ci_builds_pkey;
ALTER INDEX p_ci_job_artifacts_pkey ATTACH PARTITION ci_job_artifacts_pkey;
@ -40670,14 +40638,6 @@ ALTER INDEX tmp_p_ci_builds_trigger_request_id_idx ATTACH PARTITION index_437b18
ALTER INDEX index_ci_runner_machines_on_executor_type ATTACH PARTITION index_aa3b4fe8c6;
ALTER INDEX p_ci_builds_metadata_build_id_idx ATTACH PARTITION index_ci_builds_metadata_on_build_id_and_has_exposed_artifacts;
ALTER INDEX p_ci_builds_metadata_build_id_id_idx ATTACH PARTITION index_ci_builds_metadata_on_build_id_and_id_and_interruptible;
ALTER INDEX p_ci_builds_metadata_build_id_partition_id_idx ATTACH PARTITION index_ci_builds_metadata_on_build_id_partition_id_unique;
ALTER INDEX p_ci_builds_metadata_project_id_idx ATTACH PARTITION index_ci_builds_metadata_on_project_id;
ALTER INDEX p_ci_builds_auto_canceled_by_id_idx ATTACH PARTITION index_ci_builds_on_auto_canceled_by_id;
ALTER INDEX p_ci_builds_commit_id_stage_idx_created_at_idx ATTACH PARTITION index_ci_builds_on_commit_id_and_stage_idx_and_created_at;

View File

@ -2,13 +2,14 @@
stage: Tenant Scale
group: Geo
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
gitlab_dedicated: yes
title: Secondary runners
---
{{< details >}}
- Tier: Premium, Ultimate
- Offering: GitLab Self-Managed
- Offering: GitLab Self-Managed, GitLab Dedicated
{{< /details >}}
@ -29,6 +30,12 @@ The jobs that start during the first stage of a pipeline almost always have thei
## Use secondary runners with a Location Aware public URL (Unified URL)
{{< details >}}
- Offering: GitLab Self-Managed
{{< /details >}}
Using [Location-Aware DNS](_index.md#configure-location-aware-dns), with the feature flag enabled works with no extra configuration. After you install and register a runner in the same location as a secondary site, it automatically talks to the closest site, and only proxies to the primary if the secondary is out of date.
## Use secondary runners with separate URLs
@ -44,6 +51,12 @@ When executing [a planned failover](../disaster_recovery/planned_failover.md), s
### With Location Aware public URL
{{< details >}}
- Offering: GitLab Self-Managed
{{< /details >}}
When using [Location-Aware DNS](_index.md#configure-location-aware-dns), all runners automatically connect to the closest Geo site.
When failing over to a new primary:

View File

@ -2,14 +2,15 @@
stage: Create
group: Source Code
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Configure a Diagrams.net integration for GitLab Self-Managed.
description: Configure a Diagrams.net integration for GitLab.
gitlab_dedicated: yes
title: Diagrams.net
---
{{< details >}}
- Tier: Free, Premium, Ultimate
- Offering: GitLab Self-Managed
- Offering: GitLab Self-Managed, GitLab Dedicated
{{< /details >}}
@ -25,7 +26,7 @@ The diagram editor is available in both the plain text editor and the rich text
On GitLab.com, this integration is enabled for all SaaS users and does not require any additional configuration.
On GitLab Self-Managed, you can choose to integrate with the free [diagrams.net](https://www.drawio.com/)
On GitLab Self-Managed and GitLab Dedicated, you can choose to integrate with the free [diagrams.net](https://www.drawio.com/)
website, or host your own diagrams.net site in offline environments.
To set up the integration, you must:

View File

@ -2,13 +2,14 @@
stage: Plan
group: Project Management
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
gitlab_dedicated: yes
title: Kroki
---
{{< details >}}
- Tier: Free, Premium, Ultimate
- Offering: GitLab Self-Managed
- Offering: GitLab Self-Managed, GitLab Dedicated
{{< /details >}}

View File

@ -2,13 +2,14 @@
stage: Systems
group: Distribution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
gitlab_dedicated: yes
title: Polling interval multiplier
---
{{< details >}}
- Tier: Free, Premium, Ultimate
- Offering: GitLab Self-Managed
- Offering: GitLab Self-Managed, GitLab Dedicated
{{< /details >}}

View File

@ -2,13 +2,14 @@
stage: Software Supply Chain Security
group: Authorization
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
gitlab_dedicated: yes
title: Review spam logs
---
{{< details >}}
- Tier: Free, Premium, Ultimate
- Offering: GitLab Self-Managed
- Offering: GitLab Self-Managed, GitLab Dedicated
{{< /details >}}

View File

@ -2,14 +2,15 @@
stage: Create
group: Source Code
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Define limits for deprecated APIs on GitLab Self-Managed.
description: Define limits for deprecated APIs on GitLab.
gitlab_dedicated: yes
title: Deprecated API rate limits
---
{{< details >}}
- Tier: Free, Premium, Ultimate
- Offering: GitLab Self-Managed
- Offering: GitLab Self-Managed, GitLab Dedicated
{{< /details >}}

View File

@ -28236,6 +28236,7 @@ four standard [pagination arguments](#pagination-arguments):
| ---- | ---- | ----------- |
| <a id="groupdependenciescomponentids"></a>`componentIds` | [`[SbomComponentID!]`](#sbomcomponentid) | Filter dependencies by component IDs. |
| <a id="groupdependenciescomponentnames"></a>`componentNames` | [`[String!]`](#string) | Filter dependencies by component names. |
| <a id="groupdependenciescomponentversions"></a>`componentVersions` | [`[String!]`](#string) | Filter dependencies by component versions. |
| <a id="groupdependenciespackagemanagers"></a>`packageManagers` | [`[PackageManager!]`](#packagemanager) | Filter dependencies by package managers. |
| <a id="groupdependenciessort"></a>`sort` | [`DependencySort`](#dependencysort) | Sort dependencies by given criteria. |
| <a id="groupdependenciessourcetypes"></a>`sourceTypes` | [`[SbomSourceType!]`](#sbomsourcetype) | Filter dependencies by source type. |
@ -28261,6 +28262,7 @@ four standard [pagination arguments](#pagination-arguments):
| ---- | ---- | ----------- |
| <a id="groupdependencyaggregationscomponentids"></a>`componentIds` | [`[SbomComponentID!]`](#sbomcomponentid) | Filter dependencies by component IDs. |
| <a id="groupdependencyaggregationscomponentnames"></a>`componentNames` | [`[String!]`](#string) | Filter dependencies by component names. |
| <a id="groupdependencyaggregationscomponentversions"></a>`componentVersions` | [`[String!]`](#string) | Filter dependencies by component versions. |
| <a id="groupdependencyaggregationspackagemanagers"></a>`packageManagers` | [`[PackageManager!]`](#packagemanager) | Filter dependencies by package managers. |
| <a id="groupdependencyaggregationsprojectcountmax"></a>`projectCountMax` | [`Int`](#int) | Filter dependencies by maximum project count. |
| <a id="groupdependencyaggregationsprojectcountmin"></a>`projectCountMin` | [`Int`](#int) | Filter dependencies by minimum project count. |
@ -35874,6 +35876,7 @@ four standard [pagination arguments](#pagination-arguments):
| ---- | ---- | ----------- |
| <a id="projectdependenciescomponentids"></a>`componentIds` | [`[SbomComponentID!]`](#sbomcomponentid) | Filter dependencies by component IDs. |
| <a id="projectdependenciescomponentnames"></a>`componentNames` | [`[String!]`](#string) | Filter dependencies by component names. |
| <a id="projectdependenciescomponentversions"></a>`componentVersions` | [`[String!]`](#string) | Filter dependencies by component versions. |
| <a id="projectdependenciespackagemanagers"></a>`packageManagers` | [`[PackageManager!]`](#packagemanager) | Filter dependencies by package managers. |
| <a id="projectdependenciessort"></a>`sort` | [`DependencySort`](#dependencysort) | Sort dependencies by given criteria. |
| <a id="projectdependenciessourcetypes"></a>`sourceTypes` | [`[SbomSourceType!]`](#sbomsourcetype) | Filter dependencies by source type. |

View File

@ -2,13 +2,14 @@
stage: Fulfillment
group: Utilization
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
gitlab_dedicated: yes
title: License API
---
{{< details >}}
- Tier: Free, Premium, Ultimate
- Offering: GitLab Self-Managed
- Offering: GitLab Self-Managed, GitLab Dedicated
{{< /details >}}

View File

@ -2,13 +2,14 @@
stage: Systems
group: Distribution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
gitlab_dedicated: yes
title: Sidekiq Metrics API
---
{{< details >}}
- Tier: Free, Premium, Ultimate
- Offering: GitLab Self-Managed
- Offering: GitLab Self-Managed, GitLab Dedicated
{{< /details >}}

View File

@ -23,7 +23,7 @@ You can incorporate [Docker](https://www.docker.com) into your CI/CD workflow in
or in the GitLab container registry. Your job then runs in a container that's based on the image.
The container has all the Node dependencies you need to build your app.
- **Use [Docker](using_docker_build.md) or [kaniko](using_kaniko.md) to build Docker images.**
- **Use [Docker](using_docker_build.md) to build Docker images.**
You can create CI/CD jobs to build Docker images and publish
them to a container registry.

View File

@ -748,11 +748,8 @@ and [using the OverlayFS storage driver](https://docs.docker.com/storage/storage
## Docker alternatives
To build Docker images without enabling privileged mode on the runner, you can
use one of these alternatives:
- [`kaniko`](using_kaniko.md).
- [`buildah`](#buildah-example).
To build Docker images without enabling privileged mode on the runner,
use [`buildah`](#buildah-example).
### Buildah example
@ -987,7 +984,7 @@ To resolve this issue:
image:
name: docker:19.03
variables:
DOCKER_HOST: tcp://localhost:2375
DOCKER_HOST: tcp://localhost:2375
DOCKER_TLS_CERTDIR: ""
CA_CERTIFICATE: "$CA_CERTIFICATE"
services:
@ -999,7 +996,7 @@ To resolve this issue:
echo "$CA_CERTIFICATE" > /usr/local/share/ca-certificates/custom-ca.crt && \
update-ca-certificates && \
dockerd-entrypoint.sh || exit
script:
script:
- docker info
- docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD $DOCKER_REGISTRY
- docker build -t "${DOCKER_REGISTRY}/my-app:${CI_COMMIT_REF_NAME}" .

View File

@ -3,17 +3,18 @@ stage: Fulfillment
group: Subscription Management
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Seat usage, compute minutes, storage limits, renewal info.
gitlab_dedicated: yes
title: Troubleshooting GitLab subscription
---
{{< details >}}
- Tier: Premium, Ultimate
- Offering: GitLab.com, GitLab Self-Managed
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
{{< /details >}}
When you purchase or use subscriptions for GitLab.com or GitLab Self-Managed, you might encounter the following issues.
When you purchase or use subscriptions for GitLab, you might encounter the following issues.
## Credit card declined
@ -93,7 +94,7 @@ Ensure that you have the Owner role for that namespace, and review the [transfer
## Subscription data fails to synchronize
On GitLab Self-Managed, your subscription data might fail to synchronize.
On GitLab Self-Managed or GitLab Dedicated, your subscription data might fail to synchronize.
This issue can occur when network traffic between your GitLab instance and certain
IP addresses is not allowed.

View File

@ -356,16 +356,10 @@ For each selected vulnerability:
- [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/13216) in GitLab 17.9 [with a flag](../../../administration/feature_flags.md) named `enhanced_vulnerability_bulk_actions`. Disabled by default.
- [Enabled on GitLab.com, GitLab Self-Managed, and GitLab Dedicated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/190213) in GitLab 18.0.
- [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/515204) in GitLab 18.1. Feature flag `enhanced_vulnerability_bulk_actions` removed.
{{< /history >}}
{{< alert type="flag" >}}
The availability of this feature is controlled by a feature flag.
For more information, see the history.
{{< /alert >}}
You can link one or more vulnerabilities to existing issues in the vulnerability report.
Prerequisites:

View File

@ -4,4 +4,4 @@ source "https://rubygems.org"
gemspec
gem 'activerecord', '~> 7.0.8' # rubocop:disable Gemfile/MissingFeatureCategory
gem 'activerecord', '~> 7.1.5.1' # rubocop:disable Gemfile/MissingFeatureCategory

View File

@ -7,19 +7,33 @@ PATH
GEM
remote: https://rubygems.org/
specs:
activemodel (7.0.8.7)
activesupport (= 7.0.8.7)
activerecord (7.0.8.7)
activemodel (= 7.0.8.7)
activesupport (= 7.0.8.7)
activesupport (7.0.8.7)
activemodel (7.1.5.1)
activesupport (= 7.1.5.1)
activerecord (7.1.5.1)
activemodel (= 7.1.5.1)
activesupport (= 7.1.5.1)
timeout (>= 0.4.0)
activesupport (7.1.5.1)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
mutex_m
securerandom (>= 0.3)
tzinfo (~> 2.0)
ast (2.4.2)
base64 (0.2.0)
benchmark (0.4.0)
bigdecimal (3.1.9)
concurrent-ruby (1.2.2)
connection_pool (2.5.3)
diff-lcs (1.5.0)
drb (2.2.1)
gitlab-styles (10.1.0)
rubocop (~> 1.50.2)
rubocop-graphql (~> 0.18)
@ -29,8 +43,10 @@ GEM
i18n (1.13.0)
concurrent-ruby (~> 1.0)
json (2.6.3)
logger (1.7.0)
mini_portile2 (2.8.2)
minitest (5.18.0)
mutex_m (0.3.0)
parallel (1.23.0)
parser (3.2.2.3)
ast (~> 2.4.1)
@ -83,8 +99,10 @@ GEM
rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22)
ruby-progressbar (1.13.0)
securerandom (0.4.1)
sqlite3 (1.6.3)
mini_portile2 (~> 2.8.0)
timeout (0.4.3)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.4.2)
@ -93,7 +111,7 @@ PLATFORMS
ruby
DEPENDENCIES
activerecord (~> 7.0.8)
activerecord (~> 7.1.5.1)
activerecord-gitlab!
gitlab-styles (~> 10.1.0)
rspec (~> 3.12)
@ -102,18 +120,25 @@ DEPENDENCIES
sqlite3 (~> 1.6)
CHECKSUMS
activemodel (7.0.8.7) sha256=f13b04bb055c1e85b965ce40b0a2e671b8d97835083597bc7fbc04cde0f40a83
activerecord (7.0.8.7) sha256=f94fc8510e58a18e462c5ee8862c9be75e2bfad0688e8d022b86a6e05df2a45a
activemodel (7.1.5.1) sha256=74727466854a7fbdfe8f2702ca3112b23877500d4926bf7e02e921ad542191f1
activerecord (7.1.5.1) sha256=f40ad1609bf33b9ba5bdc4e16d80a77b1517153234ceb413d31d635d7b91f1e3
activerecord-gitlab (0.2.0)
activesupport (7.0.8.7) sha256=df4702375de924aae81709c831605317c5417f0bd9e502a0373ff84a067204ff
activesupport (7.1.5.1) sha256=9f0c482e473b9868cb3dfe3e9db549a3bd2302c02e4f595a5caac144a8c7cfb8
ast (2.4.2) sha256=1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12
base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507
benchmark (0.4.0) sha256=0f12f8c495545e3710c3e4f0480f63f06b4c842cc94cec7f33a956f5180e874a
bigdecimal (3.1.9) sha256=2ffc742031521ad69c2dfc815a98e426a230a3d22aeac1995826a75dabfad8cc
concurrent-ruby (1.2.2) sha256=3879119b8b75e3b62616acc256c64a134d0b0a7a9a3fcba5a233025bcde22c4f
connection_pool (2.5.3) sha256=cfd74a82b9b094d1ce30c4f1a346da23ee19dc8a062a16a85f58eab1ced4305b
diff-lcs (1.5.0) sha256=49b934001c8c6aedb37ba19daec5c634da27b318a7a3c654ae979d6ba1929b67
drb (2.2.1) sha256=e9d472bf785f558b96b25358bae115646da0dbfd45107ad858b0bc0d935cb340
gitlab-styles (10.1.0) sha256=f42745f5397d042fe24cf2d0eb56c995b37f9f43d8fb79b834d197a1cafdc84a
i18n (1.13.0) sha256=1d24cacd941be578faa7fc5d537d573a3e76e2822ce7dffc0c71c41ba91e63fa
json (2.6.3) sha256=86aaea16adf346a2b22743d88f8dcceeb1038843989ab93cda44b5176c845459
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
mini_portile2 (2.8.2) sha256=46b2d244cc6ff01a89bf61274690c09fdbdca47a84ae9eac39039e81231aee7c
minitest (5.18.0) sha256=06f43aa0692ce3acf19cb5bc539ad2c6095ca3d2c7e5fbafc58a7d847e898745
mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751
parallel (1.23.0) sha256=27154713ad6ef32fa3dcb7788a721d6c07bca77e72443b4c6080a14145288c49
parser (3.2.2.3) sha256=10685f358ab36ffea2252dc4952e5b8fad3a297a8152a85f59adc982747b91eb
racc (1.7.1) sha256=af64124836fdd3c00e830703d7f873ea5deabde923f37006a39f5a5e0da16387
@ -135,7 +160,9 @@ CHECKSUMS
rubocop-rails (2.20.2) sha256=d20cbd613900fa22bcf85a7fba78ab68b21fc4f90b1e73c97284d40674332417
rubocop-rspec (2.22.0) sha256=2d7493222c81c78ad304ddd81aaf64b3543bcfac6d3d8706c220331921753a03
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
sqlite3 (1.6.3) sha256=67b476378889b15c93f9b78d39f6d92636dda414194d570d3a1b27514a9e2541
timeout (0.4.3) sha256=9509f079b2b55fe4236d79633bd75e34c1c1e7e3fb4b56cb5fda61f80a0fe30e
tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
unicode-display_width (2.4.2) sha256=6a10205d1a19ca790c4e53064ba93f09d9eb234bf6bd135d9deb6001c21428be

View File

@ -4,4 +4,4 @@ source "https://rubygems.org"
gemspec
gem 'activerecord', '~> 7.1' # rubocop:disable Gemfile/MissingFeatureCategory
gem 'activerecord', '~> 7.2' # rubocop:disable Gemfile/MissingFeatureCategory

View File

@ -7,24 +7,27 @@ PATH
GEM
remote: https://rubygems.org/
specs:
activemodel (7.1.3.4)
activesupport (= 7.1.3.4)
activerecord (7.1.3.4)
activemodel (= 7.1.3.4)
activesupport (= 7.1.3.4)
activemodel (7.2.2.1)
activesupport (= 7.2.2.1)
activerecord (7.2.2.1)
activemodel (= 7.2.2.1)
activesupport (= 7.2.2.1)
timeout (>= 0.4.0)
activesupport (7.1.3.4)
activesupport (7.2.2.1)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
mutex_m
tzinfo (~> 2.0)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
ast (2.4.2)
base64 (0.2.0)
benchmark (0.4.0)
bigdecimal (3.1.8)
concurrent-ruby (1.3.3)
connection_pool (2.4.1)
@ -39,8 +42,8 @@ GEM
i18n (1.14.5)
concurrent-ruby (~> 1.0)
json (2.7.2)
logger (1.7.0)
minitest (5.24.1)
mutex_m (0.2.0)
parallel (1.25.1)
parser (3.3.4.0)
ast (~> 2.4.1)
@ -98,6 +101,7 @@ GEM
rubocop-rspec_rails (2.29.0)
rubocop (~> 1.40)
ruby-progressbar (1.13.0)
securerandom (0.4.1)
sqlite3 (1.7.3-aarch64-linux)
sqlite3 (1.7.3-arm-linux)
sqlite3 (1.7.3-arm64-darwin)
@ -105,7 +109,7 @@ GEM
sqlite3 (1.7.3-x86_64-darwin)
sqlite3 (1.7.3-x86_64-linux)
strscan (3.1.0)
timeout (0.4.1)
timeout (0.4.3)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0)
@ -119,7 +123,7 @@ PLATFORMS
x86_64-linux
DEPENDENCIES
activerecord (~> 7.1)
activerecord (~> 7.2)
activerecord-gitlab!
gitlab-styles (~> 10.1.0)
rspec (~> 3.12)

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
if ::ActiveRecord::VERSION::STRING >= "7.2"
if ::ActiveRecord::VERSION::STRING >= "7.3"
raise 'New version of active-record detected, please remove or update this patch'
end
@ -9,7 +9,7 @@ module ActiveRecord
module GitlabPatches
module Partitioning
module Base
if ::ActiveRecord::VERSION::STRING <= "7.1"
if ::ActiveRecord::VERSION::STRING <= "7.2"
def _query_constraints_hash
if self.class.query_constraints_list.nil?
{ @primary_key => id_in_database }
@ -28,7 +28,7 @@ module ActiveRecord
@query_constraints_list = columns_list.map(&:to_s)
end
if ::ActiveRecord::VERSION::STRING <= "7.1"
if ::ActiveRecord::VERSION::STRING <= "7.2"
def query_constraints_list # :nodoc:
@query_constraints_list ||= if base_class? || primary_key != base_class.primary_key
primary_key if primary_key.is_a?(Array)

View File

@ -69,7 +69,11 @@ module API
filter_params = declared_params(include_missing: false).merge(only_personal: true)
present paginate(find_snippets(user: nil, params: filter_params)), with: Entities::PersonalSnippet, current_user: current_user
present(
paginate(find_snippets(user: nil, params: filter_params)),
with: Entities::PersonalSnippet,
current_user: current_user
)
end
desc 'List all snippets current_user has access to' do
@ -141,7 +145,11 @@ module API
authorize! :create_snippet
attrs = process_create_params(declared_params(include_missing: false))
service_response = ::Snippets::CreateService.new(project: nil, current_user: current_user, params: attrs).execute
service_response = ::Snippets::CreateService.new(
project: nil,
current_user: current_user,
params: attrs
).execute
snippet = service_response.payload[:snippet]
if service_response.success?
@ -188,7 +196,12 @@ module API
validate_params_for_multiple_files(snippet)
attrs = process_update_params(declared_params(include_missing: false))
service_response = ::Snippets::UpdateService.new(project: nil, current_user: current_user, params: attrs, perform_spam_check: true).execute(snippet)
service_response = ::Snippets::UpdateService.new(
project: nil,
current_user: current_user,
params: attrs,
perform_spam_check: true
).execute(snippet)
snippet = service_response.payload[:snippet]

View File

@ -4,7 +4,7 @@ module Gitlab
module Memory
class Watchdog
class Configurator
DEFAULT_PUMA_WORKER_RSS_LIMIT_MB = 1200
DEFAULT_PUMA_WORKER_RSS_LIMIT_MB = 1500
DEFAULT_SLEEP_INTERVAL_S = 60
DEFAULT_SIDEKIQ_SLEEP_INTERVAL_S = 3
MIN_SIDEKIQ_SLEEP_INTERVAL_S = 2

View File

@ -15136,9 +15136,6 @@ msgstr ""
msgid "Commit|containing commit"
msgstr ""
msgid "Community forum"
msgstr ""
msgid "Company"
msgstr ""
@ -26501,6 +26498,9 @@ msgstr ""
msgid "File added."
msgstr ""
msgid "File browser"
msgstr ""
msgid "File changed and moved."
msgstr ""
@ -26558,6 +26558,9 @@ msgstr ""
msgid "File too large. Secure files must be less than %{limit} MB."
msgstr ""
msgid "File tree"
msgstr ""
msgid "File upload error."
msgstr ""
@ -28166,6 +28169,9 @@ msgstr ""
msgid "GitLab commit"
msgstr ""
msgid "GitLab community forum"
msgstr ""
msgid "GitLab container registry API not supported"
msgstr ""
@ -28706,6 +28712,12 @@ msgstr ""
msgid "GlobalSearch|Epics"
msgstr ""
msgid "GlobalSearch|Exclude forks"
msgstr ""
msgid "GlobalSearch|Exclude search results from forked projects"
msgstr ""
msgid "GlobalSearch|Explore"
msgstr ""
@ -28757,15 +28769,9 @@ msgstr ""
msgid "GlobalSearch|Include archived"
msgstr ""
msgid "GlobalSearch|Include forks"
msgstr ""
msgid "GlobalSearch|Include search results from archived projects"
msgstr ""
msgid "GlobalSearch|Include search results from forked projects"
msgstr ""
msgid "GlobalSearch|Incremental indexing queue length"
msgstr ""
@ -73184,6 +73190,9 @@ msgstr ""
msgid "must contain only a mastodon handle."
msgstr ""
msgid "must contain only a orcid ID."
msgstr ""
msgid "must have a repository"
msgstr ""

View File

@ -20,6 +20,14 @@ module QA
element 'submit-review-button'
end
view 'app/assets/javascripts/batch_comments/components/review_drawer.vue' do
element 'submit-review-button'
end
view 'app/assets/javascripts/batch_comments/components/submit_review_button.vue' do
element 'review-drawer-toggle'
end
view 'app/assets/javascripts/diffs/components/compare_dropdown_layout.vue' do
element 'version-dropdown-content'
end
@ -185,11 +193,7 @@ module QA
end
end
within_element('review-bar-content') do
click_element('review-preview-dropdown')
end
click_element('submit-review-dropdown')
all_elements('review-drawer-toggle', minimum: 1).first.click
click_element('submit-review-button')
# After clicking the button, wait for the review bar to disappear

View File

@ -3,17 +3,14 @@
module QA
RSpec.describe 'Create' do
# admin required to check if feature flag is enabled
describe 'Batch comments in merge request', :smoke, :requires_admin, product_group: :code_review do
describe 'Batch comments in merge request', :smoke, product_group: :code_review do
let(:project) { create(:project, name: 'project-with-merge-request') }
let(:merge_request) do
create(:merge_request, title: 'This is a merge request', description: 'Great feature', project: project)
end
it 'user submits a non-diff review',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347777',
feature_flag: { name: :improved_review_experience } do
skip('improved_review_experience FF is WIP') if Runtime::Feature.enabled?('improved_review_experience')
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347777' do
Flow::Login.sign_in
merge_request.visit!
@ -30,10 +27,7 @@ module QA
end
it 'user submits a diff review',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347778',
feature_flag: { name: :improved_review_experience } do
skip('improved_review_experience FF is WIP') if Runtime::Feature.enabled?('improved_review_experience')
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347778' do
Flow::Login.sign_in
merge_request.visit!

View File

@ -2,9 +2,9 @@
require 'spec_helper'
RSpec.describe Admin::IntegrationsController, feature_category: :integrations do
RSpec.describe Admin::IntegrationsController, :with_current_organization, feature_category: :integrations do
let_it_be(:admin) { create(:admin) }
let_it_be(:organization) { create(:organization, :default) }
let_it_be(:organization) { current_organization }
before do
stub_feature_flags(remove_monitor_metrics: false)
@ -45,14 +45,12 @@ RSpec.describe Admin::IntegrationsController, feature_category: :integrations do
describe '#update' do
include JiraIntegrationHelpers
let_it_be(:organization) { create(:organization) }
let(:integration) { create(:jira_integration, :instance) }
let(:integration_name) { integration.class.to_param }
before do
stub_jira_integration_test
allow(PropagateIntegrationWorker).to receive(:perform_async)
allow(Current).to receive(:organization).and_return(organization)
put :update, params: { id: integration_name, service: params }
end

View File

@ -65,7 +65,6 @@ RSpec.describe 'Database schema',
chat_teams: %w[team_id],
ci_builds: %w[project_id runner_id user_id erased_by_id trigger_request_id partition_id
auto_canceled_by_partition_id execution_config_id upstream_pipeline_partition_id],
ci_builds_metadata: %w[partition_id project_id build_id],
ci_build_needs: %w[project_id],
ci_build_pending_states: %w[project_id],
ci_build_trace_chunks: %w[project_id],

View File

@ -13,8 +13,6 @@ RSpec.describe 'Merge request > Batch comments', :js, feature_category: :code_re
end
before do
stub_feature_flags(improved_review_experience: false)
project.add_maintainer(user)
sign_in(user)
@ -27,21 +25,22 @@ RSpec.describe 'Merge request > Batch comments', :js, feature_category: :code_re
expect(find('.draft-note')).to have_content('Line is wrong')
expect(page).to have_selector('[data-testid="review_bar_component"]')
expect(find('[data-testid="review_bar_component"] .gl-badge')).to have_content('1')
expect(first('[data-testid="review-drawer-toggle"] .gl-badge')).to have_content('1')
end
it 'publishes review' do
write_diff_comment
page.within('.review-bar-content') do
click_button 'Finish review'
click_button 'Submit review'
page.within '.merge-request-tabs-holder' do
click_button 'Your review'
end
click_button 'Submit review'
wait_for_requests
find_by_scrolling("[id='#{sample_compare.changes[0][:line_code]}']")
expect(page).not_to have_selector('.draft-note', text: 'Line is wrong')
expect(page).to have_selector('.note:not(.draft-note)', text: 'Line is wrong')
@ -150,7 +149,9 @@ RSpec.describe 'Merge request > Batch comments', :js, feature_category: :code_re
expect(page).to have_selector('.draft-note', text: 'Its a draft comment')
click_button('Pending comments')
page.within '.merge-request-tabs-holder' do
click_button 'Your review'
end
expect(page).to have_text('2 pending comments')
end
@ -160,7 +161,9 @@ RSpec.describe 'Merge request > Batch comments', :js, feature_category: :code_re
expect(page).to have_selector('.note:not(.draft-note)', text: 'Its a regular comment')
click_button('Pending comments')
page.within '.merge-request-tabs-holder' do
click_button 'Your review'
end
expect(page).to have_text('1 pending comment')
end
@ -180,7 +183,7 @@ RSpec.describe 'Merge request > Batch comments', :js, feature_category: :code_re
expect(find('.new .draft-note')).to have_content('Line is wrong')
expect(find('.old .draft-note')).to have_content('Another wrong line')
expect(find('.review-bar-content .gl-badge')).to have_content('2')
expect(first('[data-testid="review-drawer-toggle"] .gl-badge')).to have_content('2')
end
end
@ -206,11 +209,12 @@ RSpec.describe 'Merge request > Batch comments', :js, feature_category: :code_re
write_reply_to_discussion(resolve: true)
page.within('.review-bar-content') do
click_button 'Finish review'
click_button 'Submit review'
page.within '.merge-request-tabs-holder' do
click_button 'Your review'
end
click_button 'Submit review'
wait_for_requests
page.within(first('.discussions-counter')) do
@ -249,11 +253,12 @@ RSpec.describe 'Merge request > Batch comments', :js, feature_category: :code_re
write_reply_to_discussion(button_text: 'Start a review', unresolve: true)
page.within('.review-bar-content') do
click_button 'Finish review'
click_button 'Submit review'
page.within '.merge-request-tabs-holder' do
click_button 'Your review'
end
click_button 'Submit review'
wait_for_requests
page.within(first('.discussions-counter')) do

View File

@ -12,20 +12,19 @@ RSpec.describe 'Merge request > User sees merge request file tree sidebar', :js,
let(:sidebar_scroller) { sidebar.find('.vue-recycle-scroller') }
before do
stub_feature_flags(improved_review_experience: false)
sign_in(user)
visit diffs_project_merge_request_path(project, merge_request)
wait_for_requests
end
it 'sees file tree sidebar' do
expect(page).to have_selector('.file-row[role=button]')
expect(page).to have_selector('[data-testid="file-tree-container"]')
end
shared_examples 'last entry clickable' do
specify do
sidebar_scroller.execute_script('this.scrollBy(0,99999)')
button = find_all('.file-row[role=button]').last
button = find_all('[data-testid="file-tree-container"] nav button').last
title = button.find('[data-testid=file-row-name-container]')[:title]
expect(button.obscured?).to be_falsy
button.click
@ -35,26 +34,6 @@ RSpec.describe 'Merge request > User sees merge request file tree sidebar', :js,
it_behaves_like 'last entry clickable'
context 'when has started a review' do
before do
add_diff_line_draft_comment('foo', find('.line_holder', match: :first))
# wait for review bar to appear
find_by_testid('review_bar_component')
# wait for sidebar to adjust
sleep(1)
end
it_behaves_like 'last entry clickable'
context 'when scrolled into full view' do
before do
sidebar.execute_script("this.scrollIntoView({ block: 'end' })")
end
it_behaves_like 'last entry clickable'
end
end
context 'when viewing using file-by-file mode' do
let(:user) { create(:user, view_diffs_file_by_file: true) }

View File

@ -48,6 +48,7 @@ describe('Diff File Row component', () => {
file: {},
size: 16,
showTooltip: true,
asButton: false,
}),
);
});

View File

@ -170,14 +170,14 @@ describe('Diffs tree list component', () => {
return treeEntries;
};
describe('default', () => {
beforeEach(() => {
createComponent();
});
it('renders empty text', () => {
createComponent();
expect(wrapper.text()).toContain('No files found');
});
it('renders empty text', () => {
expect(wrapper.text()).toContain('No files found');
});
it('renders title', () => {
createComponent();
expect(wrapper.find('h2').text()).toContain('Files');
});
it('renders file count', () => {

View File

@ -64,7 +64,6 @@ describe('your work groups resolver', () => {
fullName: 'frontend-fixtures-group',
parent: { id: null },
webUrl: mockGroup.web_url,
organizationEditPath: '',
descriptionHtml: '',
avatarUrl: null,
descendantGroupsCount: 1,

View File

@ -51,7 +51,7 @@ describe('ForksFilter', () => {
it('wraps the label element with a tooltip', () => {
const tooltip = getBinding(findCheckboxFilterLabel().element, 'gl-tooltip');
expect(tooltip).toBeDefined();
expect(tooltip.value).toBe('Include search results from forked projects');
expect(tooltip.value).toBe('Exclude search results from forked projects');
});
});
@ -71,19 +71,19 @@ describe('ForksFilter', () => {
it('wraps the label element with a tooltip', () => {
const tooltip = getBinding(findCheckboxFilterLabel().element, 'gl-tooltip');
expect(tooltip).toBeDefined();
expect(tooltip.value).toBe('Include search results from forked projects');
expect(tooltip.value).toBe('Exclude search results from forked projects');
});
});
describe.each`
include_forked | checkboxState
${'true'} | ${'true'}
${'sdfsdf'} | ${'false'}
${''} | ${'false'}
${'false'} | ${'false'}
`('selectedFilter', ({ include_forked, checkboxState }) => {
exclude_forks | checkboxState
${'true'} | ${'true'}
${'sdfsdf'} | ${'false'}
${''} | ${'false'}
${'false'} | ${'false'}
`('selectedFilter', ({ exclude_forks, checkboxState }) => {
beforeEach(() => {
createComponent({ urlQuery: { include_forked } });
createComponent({ urlQuery: { exclude_forks } });
});
it('renders the component', () => {
@ -101,7 +101,7 @@ describe('ForksFilter', () => {
findCheckboxFilter().vm.$emit('input', selectedFilter);
expect(defaultActions.setQuery).toHaveBeenCalledWith(expect.any(Object), {
key: 'include_forked',
key: 'exclude_forks',
value: 'false',
});
expect(selectedFilter).toEqual([false]);

View File

@ -108,4 +108,12 @@ describe('Changed file icon', () => {
expect(findIconName()).toEqual(iconName);
});
it('can be rendered as span', () => {
factory({
asButton: false,
});
expect(wrapper.element.tagName).toBe('SPAN');
});
});

View File

@ -34,6 +34,14 @@ describe('File row component', () => {
expect(name.text().trim()).toEqual(fileName);
});
it('renders as button', () => {
createComponent({
file: file('t4'),
level: 0,
});
expect(wrapper.find('button').exists()).toBe(true);
});
it('renders the full path as title', () => {
const filePath = 'path/to/file/with a very long folder name/';
const fileName = 'foo.txt';

View File

@ -965,6 +965,34 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do
end
end
describe '#remove_project_message' do
subject(:message) { helper.remove_project_message(project) }
before do
allow(project).to receive(:delayed_deletion_ready?).and_return(enabled)
end
context 'when project has delayed deletion enabled' do
let(:enabled) { true }
specify do
deletion_date = helper.permanent_deletion_date_formatted(Date.current)
expect(message).to eq "Deleting a project places it into a read-only state until #{deletion_date}, " \
"at which point the project will be permanently deleted. Are you ABSOLUTELY sure?"
end
end
context 'when project has delayed deletion disabled' do
let(:enabled) { false }
specify do
expect(message).to eq "You are going to delete #{project.full_name}. Deleted projects CANNOT be " \
"restored! Are you ABSOLUTELY sure?"
end
end
end
describe '#project_permissions_panel_data' do
subject { helper.project_permissions_panel_data(project) }

View File

@ -12,9 +12,14 @@ RSpec.describe Gitlab::Database::PartitionHelpers, feature_category: :database d
describe "#partition?" do
subject(:is_partitioned) { model.partition?(table_name) }
let(:table_name) { 'ci_builds_metadata' }
context "when a partition table exist" do
let(:table_name) { '_test_ci_builds_metadata' }
before do
model.connection.create_table("#{table_name}_p", options: 'PARTITION BY LIST (id)')
model.connection.execute("CREATE TABLE #{table_name} PARTITION OF #{table_name}_p FOR VALUES IN (1)")
end
context 'when the view postgres_partitions exists' do
it 'calls the view', :aggregate_failures do
expect(Gitlab::Database::PostgresPartition).to receive(:partition_exists?).with(table_name).and_call_original

View File

@ -151,7 +151,14 @@ RSpec.describe Gitlab::Database::PostgresPartition, type: :model, feature_catego
subject { described_class.partition_exists?(table_name) }
context 'when the partition exists' do
let(:table_name) { "ci_builds_metadata" }
let(:table_name) { "_test_partition_02" }
before do
ActiveRecord::Base.connection.execute(<<~SQL)
CREATE TABLE #{table_name} PARTITION OF public._test_partitioned_table
FOR VALUES FROM ('2020-02-01') to ('2020-03-01');
SQL
end
it { is_expected.to be_truthy }
end
@ -167,7 +174,14 @@ RSpec.describe Gitlab::Database::PostgresPartition, type: :model, feature_catego
subject { described_class.legacy_partition_exists?(table_name) }
context 'when the partition exists' do
let(:table_name) { "ci_builds_metadata" }
let(:table_name) { "_test_partition_02" }
before do
ActiveRecord::Base.connection.execute(<<~SQL)
CREATE TABLE #{table_name} PARTITION OF public._test_partitioned_table
FOR VALUES FROM ('2020-02-01') to ('2020-03-01');
SQL
end
it { is_expected.to be_truthy }
end

View File

@ -80,7 +80,6 @@ RSpec.describe 'new tables missing sharding_key', feature_category: :cell do
# 2. It does not yet have a foreign key as the index is still being backfilled
let(:allowed_to_be_missing_foreign_key) do
[
'ci_builds_metadata.project_id',
'ci_deleted_objects.project_id', # LFK already present on p_ci_builds and cascade delete all ci resources
'ci_job_artifacts.project_id',
'ci_namespace_monthly_usages.namespace_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/321400

View File

@ -20,12 +20,7 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer, :clean_gitlab_re
approvals_before_merge: 1)
end
let_it_be(:issue) do
# TODO: .reload can be removed after the migration https://gitlab.com/gitlab-org/gitlab/-/issues/497857
create(:issue,
assignees: [user],
project: exportable).reload
end
let_it_be(:issue) { create(:issue, assignees: [user], project: exportable) }
let(:exportable_path) { 'project' }
let(:logger) { Gitlab::Export::Logger.build }
@ -367,8 +362,7 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer, :clean_gitlab_re
describe 'with inaccessible associations' do
let_it_be(:milestone) { create(:milestone, project: exportable) }
# TODO: .reload can be removed after the migration https://gitlab.com/gitlab-org/gitlab/-/issues/497857
let_it_be(:issue) { create(:issue, assignees: [user], project: exportable, milestone: milestone).reload }
let_it_be(:issue) { create(:issue, assignees: [user], project: exportable, milestone: milestone) }
let_it_be(:label1) { create(:label, project: exportable) }
let_it_be(:label2) { create(:label, project: exportable) }
let_it_be(:link1) { create(:label_link, label: label1, target: issue) }

View File

@ -0,0 +1,66 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe MoveCiBuildsMetadataToDynamicSchema, :migration, feature_category: :continuous_integration do
let(:migration) { described_class.new }
let(:table_name) { described_class::TABLE_NAME }
let(:identifier) { "#{described_class::DYNAMIC_SCHEMA}.#{table_name}" }
describe '#up' do
it 'moves the table into the dynamic schema' do
expect(table_exists?(table_name)).to be_truthy
migration.up
expect(table_exists?(table_name)).to be_falsey
expect(table_exists?(identifier)).to be_truthy
end
end
describe '#down' do
context 'when the partition exists in the dynamic schema' do
before do
migration.up
ApplicationRecord.connection.execute(<<~SQL)
DROP TABLE IF EXISTS #{identifier}_100;
CREATE TABLE IF NOT EXISTS #{identifier} PARTITION OF p_#{table_name} FOR VALUES IN (100);
SQL
end
it 'moves the table into the current schema' do
expect(table_exists?(identifier)).to be_truthy
migration.down
expect(table_exists?(table_name)).to be_truthy
end
end
context 'when the partition does not exist in the dynamic schema' do
before do
migration.up
ApplicationRecord.connection.execute(<<~SQL)
DROP TABLE IF EXISTS #{identifier};
CREATE TABLE IF NOT EXISTS #{identifier}_100 PARTITION OF p_#{table_name} FOR VALUES IN (100);
SQL
end
it 'creates the table into the current schema' do
expect(table_exists?(identifier)).to be_falsey
expect(table_exists?("#{identifier}_100")).to be_truthy
migration.down
expect(table_exists?(table_name)).to be_truthy
end
end
end
def table_exists?(name)
ApplicationRecord.connection.table_exists?(name)
end
end

View File

@ -0,0 +1,39 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Packages::Conan::PackageFileable, type: :model, feature_category: :package_registry do
let_it_be(:instance) { build(:conan_recipe_revision) }
describe 'associations' do
subject { instance }
it 'has many file_metadata' do
is_expected.to have_many(:file_metadata)
end
it 'has many package_files through file_metadata' do
is_expected.to have_many(:package_files).through(:file_metadata)
end
end
describe '#orphan?' do
subject { instance.orphan? }
context 'when package_files is empty' do
it 'returns true' do
is_expected.to be_truthy
end
end
context 'when package_files is not empty' do
let_it_be(:package_file) do
create(:conan_package_file, :conan_recipe_file, package: instance.package, conan_recipe_revision: instance)
end
it 'returns false' do
is_expected.to be_falsey
end
end
end
end

View File

@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Packages::Conan::PackageReference, type: :model, feature_category: :package_registry do
it { is_expected.to be_a(Packages::Conan::PackageFileable) }
describe 'associations' do
it 'belongs to package' do
is_expected.to belong_to(:package).class_name('Packages::Conan::Package').inverse_of(:conan_package_references)

View File

@ -5,6 +5,8 @@ require 'spec_helper'
RSpec.describe Packages::Conan::PackageRevision, type: :model, feature_category: :package_registry do
using RSpec::Parameterized::TableSyntax
it { is_expected.to be_a(Packages::Conan::PackageFileable) }
describe 'associations' do
it 'belongs to package' do
is_expected.to belong_to(:package).class_name('Packages::Conan::Package').inverse_of(:conan_package_revisions)

View File

@ -5,6 +5,8 @@ require 'spec_helper'
RSpec.describe Packages::Conan::RecipeRevision, type: :model, feature_category: :package_registry do
using RSpec::Parameterized::TableSyntax
it { is_expected.to be_a(Packages::Conan::PackageFileable) }
describe 'associations' do
it 'belongs to package' do
is_expected.to belong_to(:package).class_name('Packages::Conan::Package').inverse_of(:conan_recipe_revisions)

View File

@ -385,6 +385,44 @@ RSpec.describe UserDetail, feature_category: :system_access do
end
end
describe '#orcid' do
context 'when orcid is set' do
let_it_be(:user_detail) { create(:user).user_detail }
it 'accepts a valid orcid username' do
user_detail.orcid = '1234-1234-1234-1234'
expect(user_detail).to be_valid
end
context 'when orcid id is wrong' do
it 'throws an error when orcid username format is too long' do
user_detail.orcid = '1234-1234-1234-1234-1234'
expect(user_detail).not_to be_valid
expect(user_detail.errors.full_messages)
.to match_array([_('Orcid must contain only a orcid ID.')])
end
it 'throws an error when orcid username format is too short' do
user_detail.orcid = '1234-1234'
expect(user_detail).not_to be_valid
expect(user_detail.errors.full_messages)
.to match_array([_('Orcid must contain only a orcid ID.')])
end
it 'throws an error when orcid username format is letters' do
user_detail.orcid = 'abcd-abcd-abcd-abcd'
expect(user_detail).not_to be_valid
expect(user_detail.errors.full_messages)
.to match_array([_('Orcid must contain only a orcid ID.')])
end
end
end
end
describe '#location' do
it { is_expected.to validate_length_of(:location).is_at_most(500) }
end
@ -423,6 +461,7 @@ RSpec.describe UserDetail, feature_category: :system_access do
linkedin: 'linkedin',
location: 'location',
bluesky: 'did:plc:ewvi7nxzyoun6zhxrhs64oiz',
orcid: '1234-1234-1234-1234',
mastodon: '@robin@example.com',
organization: 'organization',
skype: 'skype',
@ -447,6 +486,7 @@ RSpec.describe UserDetail, feature_category: :system_access do
it_behaves_like 'prevents `nil` value', :linkedin
it_behaves_like 'prevents `nil` value', :location
it_behaves_like 'prevents `nil` value', :bluesky
it_behaves_like 'prevents `nil` value', :orcid
it_behaves_like 'prevents `nil` value', :mastodon
it_behaves_like 'prevents `nil` value', :organization
it_behaves_like 'prevents `nil` value', :skype

View File

@ -150,6 +150,9 @@ RSpec.describe User, feature_category: :user_profile do
it { is_expected.to delegate_method(:skype).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:skype=).to(:user_detail).with_arguments(:args).allow_nil }
it { is_expected.to delegate_method(:orcid).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:orcid=).to(:user_detail).with_arguments(:args).allow_nil }
it { is_expected.to delegate_method(:website_url).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:website_url=).to(:user_detail).with_arguments(:args).allow_nil }

View File

@ -21,8 +21,7 @@ RSpec.shared_examples 'permission level for issue mutation is correctly verified
)
end
# TODO: .reload can be removed after the migration https://gitlab.com/gitlab-org/gitlab/-/issues/497857
let(:expected) { issue_attributes(issue.reload) }
let(:expected) { issue_attributes(issue) }
shared_examples_for 'when the user does not have access to the resource' do |raise_for_assigned_and_author|
before do

View File

@ -24,7 +24,7 @@ RSpec.describe 'devise/shared/_footer', feature_category: :system_access do
end
it { is_expected.to have_link(_('About GitLab'), href: ApplicationHelper.promo_url) }
it { is_expected.to have_link(_('Community forum'), href: ApplicationHelper.community_forum) }
it { is_expected.to have_link(_('GitLab community forum'), href: ApplicationHelper.community_forum) }
context 'when one trust is enabled' do
before do

View File

@ -115,27 +115,6 @@ RSpec.describe Gitlab::JiraImport::ImportIssueWorker, feature_category: :importe
expect(issue.work_item_type_id).to eq(issue_type.id)
end
context 'when legacy correct_work_item_type_id was part of the attributes (backward compatibility)' do
let(:issue_type) { ::WorkItems::Type.default_issue_type }
# At the moment, both id and correct_id columns have the same value in the work_item_types table
let(:correct_work_item_type_id) { issue_type.id }
let(:issue_attrs) do
Gitlab::JiraImport::IssueSerializer.new(
project,
jira_issue,
user.id,
issue_type,
params
).execute.except(:work_item_type_id).merge(correct_work_item_type_id: correct_work_item_type_id)
end
it 'creates an issue with the correct type' do
issue = Issue.last
expect(issue.work_item_type_id).to eq(issue_type.id)
end
end
context 'when assignee_ids is nil' do
let(:assignee_ids) { nil }

View File

@ -107,6 +107,108 @@ RSpec.describe Packages::CleanupPackageFileWorker, type: :worker, feature_catego
.and change { Packages::Package.count }.by(0)
end
end
context 'with a Conan package file' do
let_it_be(:package) { create(:conan_package, without_package_files: true) }
let_it_be(:package_file) { create(:conan_package_file, :conan_package_info, :pending_destruction, package: package) }
let_it_be(:package_file_2) { create(:conan_package_file, package: package) }
let_it_be(:recipe_revision) { package.conan_recipe_revisions.first }
let_it_be(:package_reference) { package.conan_package_references.first }
let_it_be(:package_revision) { package.conan_package_revisions.first }
context 'when deleting recipe revision' do
context 'when the recipe revision is orphan but there are other recipe revisions' do
let_it_be(:recipe_revision2) { create(:conan_recipe_revision, package: package) }
let_it_be(:other_metadata) do
create(:conan_file_metadatum, recipe_revision: recipe_revision2, package_file: package_file_2)
end
it 'deletes the recipe revision and its dependent objects' do
expect { subject }.to change { Packages::PackageFile.count }.by(-1)
.and change { Packages::Conan::RecipeRevision.count }.by(-1)
.and change { Packages::Conan::PackageReference.count }.by(-1)
.and change { Packages::Conan::PackageRevision.count }.by(-1)
.and not_change { Packages::Conan::Package.count }
end
end
context 'when the recipe revision is not orphan' do
let_it_be(:other_metadata) do
create(:conan_file_metadatum, recipe_revision: recipe_revision, package_file: package_file_2)
end
it 'does not delete the recipe revision' do
expect { subject }.to change { Packages::PackageFile.count }.by(-1)
.and not_change { Packages::Conan::RecipeRevision.count }
end
end
end
context 'when deleting package reference' do
context 'when the package reference is orphan but there are other package references' do
let_it_be(:package_reference2) { create(:conan_package_reference, package: package, recipe_revision: recipe_revision) }
let_it_be(:package_revision2) { create(:conan_package_revision, package: package, package_reference: package_reference2) }
let_it_be(:other_metadata) do
create(:conan_file_metadatum, recipe_revision: recipe_revision, package_reference: package_reference2,
package_revision: package_revision2, package_file: package_file_2, conan_file_type: 'package_file')
end
it 'deletes the package reference and its dependent package revision' do
expect { subject }.to change { Packages::PackageFile.count }.by(-1)
.and change { Packages::Conan::PackageReference.count }.by(-1)
.and change { Packages::Conan::PackageRevision.count }.by(-1)
.and not_change { Packages::Conan::RecipeRevision.count }
.and not_change { Packages::Conan::Package.count }
end
end
context 'when the package reference is still referenced by other package files' do
let_it_be(:other_metadata) do
create(:conan_file_metadatum,
conan_file_type: 'package_file',
recipe_revision: recipe_revision,
package_reference: package_reference,
package_revision: create(:conan_package_revision, package: package, package_reference: package_reference),
package_file: package_file_2)
end
it 'does not delete the package reference' do
expect { subject }.to change { Packages::PackageFile.count }.by(-1)
.and not_change { Packages::Conan::PackageReference.count }
end
end
end
context 'when deleting package revision' do
context 'when the package revision is orphan but there are other package revisions' do
let_it_be(:package_revision2) { create(:conan_package_revision, package: package) }
let_it_be(:other_metadata) do
create(:conan_file_metadatum, recipe_revision: recipe_revision, package_reference: package_reference,
package_revision: package_revision2, conan_file_type: 'package_file', package_file: package_file_2)
end
it 'deletes the package revision' do
expect { subject }.to change { Packages::PackageFile.count }.by(-1)
.and change { Packages::Conan::PackageRevision.count }.by(-1)
.and not_change { Packages::Conan::RecipeRevision.count }
.and not_change { Packages::Conan::PackageReference.count }
.and not_change { Packages::Conan::Package.count }
end
end
context 'when the package revision is still referenced by other package files' do
let_it_be(:other_metadata) do
create(:conan_file_metadatum, conan_file_type: 'package_file', recipe_revision: recipe_revision,
package_reference: package_reference, package_revision: package_revision, package_file: package_file_2)
end
it 'does not delete the package revision' do
expect { subject }.to change { Packages::PackageFile.count }.by(-1)
.and not_change { Packages::Conan::PackageRevision.count }
end
end
end
end
end
describe '#max_running_jobs' do

View File

@ -25,7 +25,7 @@ require (
github.com/jpillora/backoff v1.0.0
github.com/mitchellh/copystructure v1.2.0
github.com/prometheus/client_golang v1.21.1
github.com/redis/go-redis/v9 v9.7.3
github.com/redis/go-redis/v9 v9.8.0
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
github.com/sirupsen/logrus v1.9.3
github.com/sony/gobreaker/v2 v2.1.0

View File

@ -544,8 +544,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/prometheus v0.54.0 h1:6+VmEkohHcofl3W5LyRlhw1Lfm575w/aX6ZFyVAmzM0=
github.com/prometheus/prometheus v0.54.0/go.mod h1:xlLByHhk2g3ycakQGrMaU8K7OySZx98BzeCR99991NY=
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo=
github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=