Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-11-05 18:18:22 +00:00
parent b17372c8e7
commit f1a3f078ff
127 changed files with 1341 additions and 1017 deletions

View File

@ -215,7 +215,6 @@ Lint/AssignmentInCondition:
- 'rubocop/cop/rspec/httparty_basic_auth.rb'
- 'scripts/lint-docs-blueprints.rb'
- 'spec/factories/ci/processable.rb'
- 'spec/lib/gitlab/background_migration/backfill_root_storage_statistics_fork_storage_sizes_spec.rb'
- 'spec/models/packages/go/module_version_spec.rb'
- 'spec/models/project_feature_spec.rb'
- 'spec/requests/api/go_proxy_spec.rb'

View File

@ -1 +1 @@
55dfaaba43c8513b6233dc4444fac4552ef10b6d
a3b447448698d44d0c93138fa68c1877c5bab70e

View File

@ -1 +1 @@
6226391494a57203a37df10f489ec6caa8261a7b
fb36514b67173b5a16a386256a85203a0d14a76c

View File

@ -493,7 +493,7 @@ end
group :development, :test do
gem 'deprecation_toolkit', '~> 1.5.1', require: false # rubocop:todo Gemfile/MissingFeatureCategory
gem 'bullet', '~> 7.1.2' # rubocop:todo Gemfile/MissingFeatureCategory
gem 'parser', '= 3.3.5.1', feature_category: :shared
gem 'parser', '= 3.3.6.0', feature_category: :shared
gem 'pry-byebug' # rubocop:todo Gemfile/MissingFeatureCategory
gem 'pry-rails', '~> 0.3.9' # rubocop:todo Gemfile/MissingFeatureCategory
gem 'pry-shell', '~> 0.6.4' # rubocop:todo Gemfile/MissingFeatureCategory

View File

@ -499,7 +499,7 @@
{"name":"pact-support","version":"1.20.0","platform":"ruby","checksum":"41c343a3124fb379684b9ad9f1a0766c5fa18d3b78d433a52e5552d8b9475871"},
{"name":"paper_trail","version":"15.1.0","platform":"ruby","checksum":"0dbccd97e9d26c54aaea256d2566eaa040a3535601f49a62d79187e77d9ba9f9"},
{"name":"parallel","version":"1.24.0","platform":"ruby","checksum":"5bf38efb9b37865f8e93d7a762727f8c5fc5deb19949f4040c76481d5eee9397"},
{"name":"parser","version":"3.3.5.1","platform":"ruby","checksum":"b74c4403b8dd2ada9f72f9eae3b57f85ee82541ab4251736d39c25eede136c65"},
{"name":"parser","version":"3.3.6.0","platform":"ruby","checksum":"25d4e67cc4f0f7cab9a2ae1f38e2005b6904d2ea13c34734511d0faad038bc3b"},
{"name":"parslet","version":"1.8.2","platform":"ruby","checksum":"08d1ab3721cd3f175bfbee8788b2ddff71f92038f2d69bd65454c22bb9fbd98a"},
{"name":"pastel","version":"0.8.0","platform":"ruby","checksum":"481da9fb7d2f6e6b1a08faf11fa10363172dc40fd47848f096ae21209f805a75"},
{"name":"peek","version":"1.1.0","platform":"ruby","checksum":"d6501ead8cde46d8d8ed0d59eb6f0ba713d0a41c11a2c4a81447b2dce37b3ecc"},
@ -586,7 +586,7 @@
{"name":"rest-client","version":"2.1.0","platform":"x86-mswin32","checksum":"a35a3bb8d16ca39d110a946a2c805267f98ce07a0ae890e4512a45eadea47a6e"},
{"name":"retriable","version":"3.1.2","platform":"ruby","checksum":"0a5a5d0ca4ba61a76fb31a17ab8f7f80281beb040c329d34dfc137a1398688e0"},
{"name":"reverse_markdown","version":"1.4.0","platform":"ruby","checksum":"a3305da1509ac8388fa84a28745621113e121383402a2e8e9350ba649034e870"},
{"name":"rexml","version":"3.3.8","platform":"ruby","checksum":"f68fd96345330a1062896d0d0401324b0dae0ee20f784791b0843db96b212167"},
{"name":"rexml","version":"3.3.9","platform":"ruby","checksum":"d71875b85299f341edf47d44df0212e7658cbdf35aeb69cefdb63f57af3137c9"},
{"name":"rinku","version":"2.0.0","platform":"ruby","checksum":"3e695aaf9f24baba3af45823b5c427b58a624582132f18482320e2737f9f8a85"},
{"name":"rotp","version":"6.3.0","platform":"ruby","checksum":"75d40087e65ed0d8022c33055a6306c1c400d1c12261932533b5d6cbcd868854"},
{"name":"rouge","version":"4.4.0","platform":"ruby","checksum":"7a6d6d951e3202e4ce3926838625fa6edeb35680e6d1e3817f53c14212220b64"},

View File

@ -217,7 +217,7 @@ PATH
PATH
remote: vendor/gems/sidekiq-reliable-fetch
specs:
gitlab-sidekiq-fetcher (0.11.0)
gitlab-sidekiq-fetcher (0.12.0)
json (>= 2.5)
sidekiq (~> 7.0)
@ -1404,7 +1404,7 @@ GEM
activerecord (>= 6.1)
request_store (~> 1.4)
parallel (1.24.0)
parser (3.3.5.1)
parser (3.3.6.0)
ast (~> 2.4.1)
racc
parslet (1.8.2)
@ -1568,7 +1568,7 @@ GEM
retriable (3.1.2)
reverse_markdown (1.4.0)
nokogiri
rexml (3.3.8)
rexml (3.3.9)
rinku (2.0.0)
rotp (6.3.0)
rouge (4.4.0)
@ -2215,7 +2215,7 @@ DEPENDENCIES
pact (~> 1.64)
paper_trail (~> 15.0)
parallel (~> 1.19)
parser (= 3.3.5.1)
parser (= 3.3.6.0)
parslet (~> 1.8)
peek (~> 1.1)
pg (~> 1.5.6)

View File

@ -504,7 +504,7 @@
{"name":"pact-support","version":"1.20.0","platform":"ruby","checksum":"41c343a3124fb379684b9ad9f1a0766c5fa18d3b78d433a52e5552d8b9475871"},
{"name":"paper_trail","version":"15.1.0","platform":"ruby","checksum":"0dbccd97e9d26c54aaea256d2566eaa040a3535601f49a62d79187e77d9ba9f9"},
{"name":"parallel","version":"1.24.0","platform":"ruby","checksum":"5bf38efb9b37865f8e93d7a762727f8c5fc5deb19949f4040c76481d5eee9397"},
{"name":"parser","version":"3.3.5.1","platform":"ruby","checksum":"b74c4403b8dd2ada9f72f9eae3b57f85ee82541ab4251736d39c25eede136c65"},
{"name":"parser","version":"3.3.6.0","platform":"ruby","checksum":"25d4e67cc4f0f7cab9a2ae1f38e2005b6904d2ea13c34734511d0faad038bc3b"},
{"name":"parslet","version":"1.8.2","platform":"ruby","checksum":"08d1ab3721cd3f175bfbee8788b2ddff71f92038f2d69bd65454c22bb9fbd98a"},
{"name":"pastel","version":"0.8.0","platform":"ruby","checksum":"481da9fb7d2f6e6b1a08faf11fa10363172dc40fd47848f096ae21209f805a75"},
{"name":"peek","version":"1.1.0","platform":"ruby","checksum":"d6501ead8cde46d8d8ed0d59eb6f0ba713d0a41c11a2c4a81447b2dce37b3ecc"},
@ -599,7 +599,7 @@
{"name":"rest-client","version":"2.1.0","platform":"x86-mswin32","checksum":"a35a3bb8d16ca39d110a946a2c805267f98ce07a0ae890e4512a45eadea47a6e"},
{"name":"retriable","version":"3.1.2","platform":"ruby","checksum":"0a5a5d0ca4ba61a76fb31a17ab8f7f80281beb040c329d34dfc137a1398688e0"},
{"name":"reverse_markdown","version":"1.4.0","platform":"ruby","checksum":"a3305da1509ac8388fa84a28745621113e121383402a2e8e9350ba649034e870"},
{"name":"rexml","version":"3.3.8","platform":"ruby","checksum":"f68fd96345330a1062896d0d0401324b0dae0ee20f784791b0843db96b212167"},
{"name":"rexml","version":"3.3.9","platform":"ruby","checksum":"d71875b85299f341edf47d44df0212e7658cbdf35aeb69cefdb63f57af3137c9"},
{"name":"rinku","version":"2.0.0","platform":"ruby","checksum":"3e695aaf9f24baba3af45823b5c427b58a624582132f18482320e2737f9f8a85"},
{"name":"rotp","version":"6.3.0","platform":"ruby","checksum":"75d40087e65ed0d8022c33055a6306c1c400d1c12261932533b5d6cbcd868854"},
{"name":"rouge","version":"4.4.0","platform":"ruby","checksum":"7a6d6d951e3202e4ce3926838625fa6edeb35680e6d1e3817f53c14212220b64"},

View File

@ -217,7 +217,7 @@ PATH
PATH
remote: vendor/gems/sidekiq-reliable-fetch
specs:
gitlab-sidekiq-fetcher (0.11.0)
gitlab-sidekiq-fetcher (0.12.0)
json (>= 2.5)
sidekiq (~> 7.0)
@ -1419,7 +1419,7 @@ GEM
activerecord (>= 6.1)
request_store (~> 1.4)
parallel (1.24.0)
parser (3.3.5.1)
parser (3.3.6.0)
ast (~> 2.4.1)
racc
parslet (1.8.2)
@ -1594,7 +1594,7 @@ GEM
retriable (3.1.2)
reverse_markdown (1.4.0)
nokogiri
rexml (3.3.8)
rexml (3.3.9)
rinku (2.0.0)
rotp (6.3.0)
rouge (4.4.0)
@ -2242,7 +2242,7 @@ DEPENDENCIES
pact (~> 1.64)
paper_trail (~> 15.0)
parallel (~> 1.19)
parser (= 3.3.5.1)
parser (= 3.3.6.0)
parslet (~> 1.8)
peek (~> 1.1)
pg (~> 1.5.6)

View File

@ -170,7 +170,7 @@ export default {
<gl-loading-icon v-if="isLoading" class="gl-m-3" size="sm" />
<p
v-else-if="filteredArtifacts.length === 0"
class="gl-m-0 gl-px-4 gl-py-3 gl-text-gray-600"
class="gl-m-0 gl-px-4 gl-py-3 gl-text-subtle"
data-testid="artifacts-empty-message"
>
{{ $options.i18n.emptyArtifactsMessage }}

View File

@ -116,7 +116,7 @@ export default {
:selected-project="selectedProject"
@select="selectProject"
/>
<p class="gl-mb-0 gl-mt-1 gl-text-gray-600">
<p class="gl-mb-0 gl-mt-1 gl-text-subtle">
<template v-if="projects.length">
{{ $options.i18n.privateForkSelected }}
</template>

View File

@ -111,7 +111,7 @@ export default {
/>
</li>
</ul>
<p v-else class="multi-file-commit-list form-text gl-text-center gl-text-gray-600">
<p v-else class="multi-file-commit-list form-text gl-text-center gl-text-subtle">
{{ emptyStateText }}
</p>
<gl-modal

View File

@ -497,7 +497,7 @@ export default class CreateMergeRequestDropdown {
removeMessage(target) {
const { input, message } = this.getTargetData(target);
const inputClasses = ['gl-field-error-outline', 'gl-field-success-outline'];
const messageClasses = ['gl-text-gray-600', 'gl-text-red-500', 'gl-text-green-500'];
const messageClasses = ['gl-text-subtle', 'gl-text-red-500', 'gl-text-green-500'];
inputClasses.forEach((cssClass) => input.classList.remove(cssClass));
messageClasses.forEach((cssClass) => message.classList.remove(cssClass));
@ -530,7 +530,7 @@ export default class CreateMergeRequestDropdown {
const text = target === INPUT_TARGET_BRANCH ? __('branch name') : __('source');
this.removeMessage(target);
message.classList.add('gl-text-gray-600');
message.classList.add('gl-text-subtle');
message.textContent = sprintf(__('Checking %{text} availability…'), { text });
message.style.display = 'inline-block';
}

View File

@ -329,6 +329,9 @@ export const filtersMap = {
[OPERATOR_IS]: {
[NORMAL_FILTER]: 'target_branches[]',
},
[OPERATOR_NOT]: {
[NORMAL_FILTER]: 'not[target_branches][]',
},
},
},
[TOKEN_TYPE_TYPE]: {

View File

@ -34,7 +34,7 @@ export default {
{{ group.full_name }}
</span>
<div v-if="group.description">
<p class="!gl-mt-2 gl-mb-0 gl-text-gray-600" v-text="group.description"></p>
<p class="!gl-mt-2 gl-mb-0 gl-text-subtle" v-text="group.description"></p>
</div>
</div>
</div>

View File

@ -326,7 +326,6 @@ export default {
title: TOKEN_TITLE_MILESTONE,
icon: 'milestone',
token: MilestoneToken,
operators: OPERATORS_IS,
recentSuggestionsStorageKey: `${this.fullPath}-merge-requests-recent-tokens-milestone`,
shouldSkipSort: true,
fullPath: this.fullPath,
@ -339,7 +338,6 @@ export default {
title: TOKEN_TITLE_TARGET_BRANCH,
icon: 'arrow-right',
token: BranchToken,
operators: OPERATORS_IS,
fullPath: this.fullPath,
isProject: true,
fetchBranches: this.fetchTargetBranches,

View File

@ -29,7 +29,7 @@ export default {
data-testid="discussion-filter-container"
>
<div
class="gl-float-left -gl-mt-1 gl-ml-2 gl-flex gl-h-6 gl-w-6 gl-items-center gl-justify-center gl-rounded-full gl-bg-gray-50 gl-text-gray-600"
class="gl-float-left -gl-mt-1 gl-ml-2 gl-flex gl-h-6 gl-w-6 gl-items-center gl-justify-center gl-rounded-full gl-bg-gray-50 gl-text-subtle"
>
<gl-icon name="comment" />
</div>

View File

@ -1,9 +1,13 @@
<script>
import { GlAlert, GlButton } from '@gitlab/ui';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import { setUrlParams } from '~/lib/utils/url_utility';
import { s__, sprintf } from '~/locale';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { InternalEvents } from '~/tracking';
import { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
import { ERRORED_PACKAGE_TEXT } from '~/packages_and_registries/package_registry/constants';
import DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue';
import getPackageErrorsCountQuery from '~/packages_and_registries/package_registry/graphql/queries/get_package_errors_count.query.graphql';
export default {
name: 'PackageErrorsCount',
@ -13,19 +17,37 @@ export default {
DeleteModal,
},
mixins: [InternalEvents.mixin()],
props: {
inject: ['isGroupPage', 'fullPath'],
apollo: {
errorPackages: {
type: Array,
required: false,
default: () => [],
query: getPackageErrorsCountQuery,
variables() {
return {
fullPath: this.fullPath,
isGroupPage: this.isGroupPage,
};
},
update(data) {
if (!data[this.graphqlResource]) {
this.reportToSentry();
}
return data[this.graphqlResource]?.packages ?? {};
},
error(error) {
this.reportToSentry(error);
},
},
},
data() {
return {
errorPackages: {},
itemsToBeDeleted: [],
};
},
computed: {
errorPackagesCount() {
return this.errorPackages.count;
},
errorTitleAlert() {
if (this.singleErrorPackage) {
return sprintf(s__('PackageRegistry|There was an error publishing %{packageName}'), {
@ -33,12 +55,14 @@ export default {
});
}
return sprintf(s__('PackageRegistry|There was an error publishing %{count} packages'), {
count: this.errorPackages.length,
count: this.errorPackagesCount,
});
},
errorMessageBodyAlert() {
if (this.singleErrorPackage) {
return this.singleErrorPackage.statusMessage || this.$options.i18n.errorMessageBodyAlert;
return sprintf(this.$options.i18n.errorMessageBodyAlert, {
message: this.singleErrorPackage.statusMessage || ERRORED_PACKAGE_TEXT,
});
}
return sprintf(
@ -46,29 +70,30 @@ export default {
'PackageRegistry|Failed to publish %{count} packages. Delete these packages and try again.',
),
{
count: this.errorPackages.length,
count: this.errorPackagesCount,
},
);
},
graphqlResource() {
return this.isGroupPage ? WORKSPACE_GROUP : WORKSPACE_PROJECT;
},
singleErrorPackage() {
if (this.errorPackages.length === 1) {
const [errorPackage] = this.errorPackages;
if (this.errorPackagesCount === 1) {
const [errorPackage] = this.errorPackages.nodes;
return errorPackage;
}
return null;
},
showErrorPackageAlert() {
return this.errorPackages.length > 0;
return this.errorPackagesCount > 0;
},
errorPackagesHref() {
// For reactivity we depend on showErrorPackageAlert so we update accordingly
if (!this.showErrorPackageAlert) {
if (this.singleErrorPackage) {
return '';
}
const pageParams = { after: null, before: null };
return mergeUrlParams({ status: 'error', ...pageParams }, window.location.href);
return setUrlParams({ status: 'error' }, window.location.href, true);
},
},
methods: {
@ -79,11 +104,12 @@ export default {
this.itemsToBeDeleted = [this.singleErrorPackage];
this.$refs.deletePackagesModal.show();
},
reportToSentry(error) {
Sentry.captureException(error);
},
},
i18n: {
errorMessageBodyAlert: s__(
'PackageRegistry|There was a timeout and the package was not published. Delete this package and try again.',
),
errorMessageBodyAlert: s__('PackageRegistry|%{message}. Delete this package and try again.'),
},
};
</script>
@ -92,7 +118,7 @@ export default {
<div>
<gl-alert
v-if="showErrorPackageAlert"
class="gl-mt-5"
class="gl-mb-5 gl-mt-2"
variant="danger"
:title="errorTitleAlert"
>

View File

@ -0,0 +1,26 @@
query getPackageErrorsCount($fullPath: ID!, $isGroupPage: Boolean!) {
project(fullPath: $fullPath) @skip(if: $isGroupPage) {
id
packages(status: ERROR, sort: CREATED_DESC, last: 1) {
count
nodes {
id
name
statusMessage
version
}
}
}
group(fullPath: $fullPath) @include(if: $isGroupPage) {
id
packages(status: ERROR, sort: CREATED_DESC, last: 1) {
count
nodes {
id
name
statusMessage
version
}
}
}
}

View File

@ -92,9 +92,6 @@ export default {
},
},
computed: {
errorPackages() {
return this.packages?.nodes?.filter((pkg) => pkg.status === PACKAGE_ERROR_STATUS) ?? [];
},
packages() {
return this.packagesResource?.packages ?? {};
},
@ -150,9 +147,9 @@ export default {
isLoading() {
return this.$apollo.queries.packagesResource.loading || this.isDeleteInProgress;
},
isNotFilteredByErrorStatus() {
showPackageErrorsCount() {
const packageStatus = this.filters?.packageStatus?.toUpperCase();
return this.errorPackages.length > 0 && packageStatus !== PACKAGE_ERROR_STATUS;
return this.packagesCount > 0 && packageStatus !== PACKAGE_ERROR_STATUS;
},
refetchQueriesData() {
return [
@ -216,7 +213,6 @@ export default {
/>
</template>
</package-title>
<package-search @update="handleSearchUpdate" />
<delete-packages
:refetch-queries="refetchQueriesData"
@ -226,11 +222,8 @@ export default {
>
<template #default="{ deletePackages }">
<div>
<package-errors-count
v-if="isNotFilteredByErrorStatus"
:error-packages="errorPackages"
@confirm-delete="deletePackages"
/>
<package-errors-count v-if="showPackageErrorsCount" @confirm-delete="deletePackages" />
<package-search @update="handleSearchUpdate" />
<package-list
:group-settings="groupSettings"
:list="packages.nodes"

View File

@ -287,7 +287,7 @@ export default {
:disabled="loading"
required
/>
<p v-if="showHint" class="form-text gl-text-gray-600" data-testid="hint">
<p v-if="showHint" class="form-text gl-text-subtle" data-testid="hint">
{{ $options.i18n.COMMIT_MESSAGE_HINT }}
</p>
</gl-form-group>

View File

@ -253,13 +253,13 @@ export default {
v-safe-html:[$options.safeHtmlConfig]="commitData.titleHtml"
:href="commitData.commitPath"
:title="commitData.message"
class="str-truncated-100 tree-commit-link gl-text-gray-600"
class="str-truncated-100 tree-commit-link gl-text-subtle"
/>
<gl-intersection-observer @appear="rowAppeared" @disappear="rowDisappeared">
<gl-skeleton-loader v-if="showSkeletonLoader" :lines="1" />
</gl-intersection-observer>
</td>
<td class="tree-time-ago text-right cursor-default gl-text-gray-600">
<td class="tree-time-ago text-right cursor-default gl-text-subtle">
<gl-intersection-observer @appear="rowAppeared" @disappear="rowDisappeared">
<timeago-tooltip v-if="commitData" :time="commitData.committedDate" />
</gl-intersection-observer>

View File

@ -125,7 +125,7 @@ export default {
</script>
<template>
<div class="gl-inline gl-text-gray-600">
<div class="gl-inline gl-text-subtle">
<div v-if="isBasicSearch" data-testid="basic">
<div v-if="isFallBacktoBasicSearch" :data-testid="`${searchTypeAvailableTestId}-disabled`">
<gl-sprintf :message="disabledMessage">

View File

@ -268,7 +268,7 @@ export default {
<span
v-if="!securityTrainingEnabled"
data-testid="unavailable-text"
class="gl-text-gray-600"
class="gl-text-subtle"
>
{{ $options.i18n.unavailableText }}
</span>

View File

@ -8,6 +8,7 @@ import Tracking from '~/tracking';
import {
INSTRUMENT_TAB_LABELS,
INSTRUMENT_TODO_FILTER_CHANGE,
INSTRUMENT_TODO_ITEM_CLICK,
STATUS_BY_TAB,
} from '~/todos/constants';
import getTodosQuery from './queries/get_todos.query.graphql';
@ -165,6 +166,9 @@ export default {
onClick: async () => {
hide();
await this.$apollo.mutate({ mutation, variables: { todoId } });
this.track(INSTRUMENT_TODO_ITEM_CLICK, {
label: markedAsDone ? 'undo_mark_done' : 'undo_mark_pending',
});
this.updateAllQueries(false);
},
},

View File

@ -152,7 +152,7 @@ export default {
<template v-if="isCostFactored(project)">
<number-to-human-size :value="project.statistics.costFactoredStorageSize" />
<div class="gl-mt-2 gl-text-sm gl-text-gray-600">
<div class="gl-mt-2 gl-text-sm gl-text-subtle">
<gl-sprintf :message="s__('UsageQuotas|(of %{totalStorageSize})')">
<template #totalStorageSize>
<number-to-human-size :value="project.statistics.storageSize" />

View File

@ -781,7 +781,7 @@ export default {
<p v-if="showMergeDetailsHeader" class="gl-mb-2 gl-text-gray-900">
{{ __('Merge details') }}
</p>
<ul class="gl-mb-0 gl-ml-3 gl-pl-4 gl-text-gray-600">
<ul class="gl-mb-0 gl-ml-3 gl-pl-4 gl-text-subtle">
<li v-if="sourceHasDivergedFromTarget" class="gl-leading-normal">
<gl-sprintf :message="$options.i18n.sourceDivergedFromTargetText">
<template #link>

View File

@ -41,7 +41,7 @@ export default {
<gl-icon class="gl-min-w-6" name="key" />
<span class="gl-flex gl-min-w-0 gl-grow gl-flex-col">
<span class="gl-truncate gl-font-bold">{{ title }}</span>
<span class="gl-text-gray-600">@{{ username }}</span>
<span class="gl-text-subtle">@{{ username }}</span>
</span>
<gl-button

View File

@ -56,7 +56,7 @@ export default {
/>
<span class="gl-flex gl-flex-col">
<span class="gl-font-bold">{{ fullName }}</span>
<span class="gl-text-gray-600">@{{ name }}</span>
<span class="gl-text-subtle">@{{ name }}</span>
</span>
</div>

View File

@ -45,7 +45,7 @@ export default {
/>
<span class="gl-flex gl-max-w-30 gl-grow gl-flex-col">
<span class="gl-font-bold">{{ name }}</span>
<span class="gl-text-gray-600">{{ data.nameWithNamespace }}</span>
<span class="gl-text-subtle">{{ data.nameWithNamespace }}</span>
</span>
<gl-button

View File

@ -44,7 +44,7 @@ export default {
<gl-avatar :alt="name" :entity-name="name" :size="32" :src="avatarUrl" fallback-on-error />
<span class="gl-flex gl-grow gl-flex-col">
<span class="gl-font-bold">{{ name }}</span>
<span class="gl-text-gray-600">@{{ username }}</span>
<span class="gl-text-subtle">@{{ username }}</span>
</span>
<gl-button

View File

@ -14,7 +14,7 @@ export default {
<template>
<timeline-entry-item class="note note-wrapper">
<div
class="gl-float-left -gl-mt-1 gl-ml-2 gl-flex gl-h-6 gl-w-6 gl-items-center gl-justify-center gl-rounded-full gl-bg-gray-50 gl-text-gray-600"
class="gl-float-left -gl-mt-1 gl-ml-2 gl-flex gl-h-6 gl-w-6 gl-items-center gl-justify-center gl-rounded-full gl-bg-gray-50 gl-text-subtle"
></div>
<div class="timeline-content">
<div class="note-header"></div>

View File

@ -105,7 +105,7 @@ export default {
return this.getNoteableData.noteableType === 'MergeRequest';
},
iconBgClass() {
return ICON_COLORS[this.note.system_note_icon_name] || 'gl-bg-gray-50 gl-text-gray-600';
return ICON_COLORS[this.note.system_note_icon_name] || 'gl-bg-gray-50 gl-text-subtle';
},
systemNoteIconName() {
let icon = this.note.system_note_icon_name;

View File

@ -1,6 +1,6 @@
<template>
<div
class="gl-float-left -gl-mt-1 gl-ml-2 gl-flex gl-h-6 gl-w-6 gl-items-center gl-justify-center gl-rounded-full gl-bg-gray-50 gl-text-gray-600"
class="gl-float-left -gl-mt-1 gl-ml-2 gl-flex gl-h-6 gl-w-6 gl-items-center gl-justify-center gl-rounded-full gl-bg-gray-50 gl-text-subtle"
>
<slot></slot>
</div>

View File

@ -130,12 +130,12 @@ export default {
<span data-testid="legend-text">{{ legendText }}</span>
</template>
</gl-infinite-scroll>
<div v-if="showNoResultsMessage" class="js-no-results-message gl-ml-3 gl-text-gray-600">
<div v-if="showNoResultsMessage" class="js-no-results-message gl-ml-3 gl-text-subtle">
{{ __('Sorry, no projects matched your search') }}
</div>
<div
v-if="showMinimumSearchQueryMessage"
class="js-minimum-search-query-message gl-ml-3 gl-text-gray-600"
class="js-minimum-search-query-message gl-ml-3 gl-text-subtle"
>
{{ __('Enter at least three characters to search') }}
</div>

View File

@ -20,7 +20,7 @@ export default {
<template>
<timeline-entry-item class="system-note note-wrapper">
<div
class="gl-float-left -gl-mt-1 gl-ml-2 gl-flex gl-h-6 gl-w-6 gl-items-center gl-justify-center gl-rounded-full gl-bg-gray-50 gl-text-gray-600"
class="gl-float-left -gl-mt-1 gl-ml-2 gl-flex gl-h-6 gl-w-6 gl-items-center gl-justify-center gl-rounded-full gl-bg-gray-50 gl-text-subtle"
>
<gl-icon :name="icon" />
</div>

View File

@ -119,7 +119,7 @@ export default {
<gl-avatar :alt="getAuthorName(item.author)" :size="32" :src="getAvatarUrl(item)" />
<span class="gl-flex gl-flex-col">
<span class="gl-font-bold">{{ versionText(item) }}</span>
<span v-if="item.author" class="gl-mt-1 gl-text-gray-600">
<span v-if="item.author" class="gl-mt-1 gl-text-subtle">
<span class="gl-block">{{ getAuthorName(item.author) }}</span>
<time-ago
v-if="item.createdAt"

View File

@ -31,7 +31,7 @@ export default {
<template>
<li class="timeline-entry note note-wrapper discussion-filter-note">
<div
class="gl-float-left -gl-mt-1 gl-ml-2 gl-flex gl-h-6 gl-w-6 gl-items-center gl-justify-center gl-rounded-full gl-bg-gray-50 gl-text-gray-600"
class="gl-float-left -gl-mt-1 gl-ml-2 gl-flex gl-h-6 gl-w-6 gl-items-center gl-justify-center gl-rounded-full gl-bg-gray-50 gl-text-subtle"
>
<gl-icon name="comment" />
</div>

View File

@ -331,7 +331,7 @@ export default {
<div class="gl-font-bold">{{ organizationName }}</div>
<div
v-if="contact.organization.description || contact.organization.defaultRate"
class="gl-text-gray-600"
class="gl-text-subtle"
>
<gl-truncate-text
v-if="contact.organization.description"

View File

@ -103,9 +103,9 @@ module IconsHelper
def boolean_to_icon(value)
if value
sprite_icon('check',
css_class: 'gl-text-green-500') + content_tag(:span, _('Enabled'), class: 'gl-pl-2 gl-text-secondary')
css_class: 'gl-text-green-500') + content_tag(:span, _('Enabled'), class: 'gl-pl-2 gl-text-subtle')
else
content_tag(:span, _('Not enabled'), class: 'gl-text-secondary')
content_tag(:span, _('Not enabled'), class: 'gl-text-subtle')
end
end

View File

@ -231,7 +231,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
title: s_('Terraform|Support for periods (`.`) in Terraform state names might break existing states.'),
class: 'gl-ml-2',
data: { toggle: 'tooltip' }
) { sprite_icon('error', css_class: 'gl-text-gray-600') }
) { sprite_icon('error', css_class: 'gl-text-subtle') }
AnchorData.new(
true,

View File

@ -12,7 +12,7 @@
.form-group
= f.label :members_delete_limit, _('Maximum requests per minute per group / project'), class: 'label-bold'
= f.number_field :members_delete_limit, min: 0, class: 'form-control gl-form-input'
.form-text.gl-text-gray-600
.form-text.gl-text-subtle
= _("Set to 0 to disable the limit.")
= f.submit _('Save changes'), pajamas_button: true

View File

@ -4,7 +4,7 @@
%fieldset
.form-group
= f.label :search_rate_limit, _('Maximum number of requests per minute for an authenticated user'), class: 'label-bold'
.form-text.gl-text-gray-600
.form-text.gl-text-subtle
= _("Set this number to 0 to disable the limit.")
= f.number_field :search_rate_limit, class: 'form-control gl-form-input'

View File

@ -30,7 +30,7 @@
%label.col-form-label{ for: 'personal_access_token' }= _('Personal access token')
= hidden_field_tag(:namespace_id, params[:namespace_id])
= password_field_tag :personal_access_token, '', class: 'form-control gl-form-input', placeholder: _('e.g. %{token}') % { token: '8d3f016698e...' }, data: { testid: 'personal-access-token-field' }
%span.form-text.gl-text-gray-600
%span.form-text.gl-text-subtle
- code_pair = tag_pair(tag.code, :code_start, :code_end)
- github_link_tag_pair = tag_pair(link_to('', 'https://github.com/settings/tokens', target: '_blank', rel: 'noopener noreferrer'), :link_start, :link_end)
= safe_format(s_('GithubImport|Create and provide your GitHub %{link_start}personal access token%{link_end}.'), github_link_tag_pair)

View File

@ -13,6 +13,6 @@
- else
.file-content.code
.nothing-here-block
.gl-text-gray-600.gl-text-sm
.gl-text-subtle.gl-text-sm
- max_file_size_indexed = Gitlab::CurrentSettings.elasticsearch_indexed_file_size_limit_kb.kilobytes
= _('The file could not be displayed because it is empty or larger than the maximum file size indexed (%{size}).') % { size: number_to_human_size(max_file_size_indexed) }

View File

@ -2,7 +2,7 @@
.gl-text-sm.gl-font-semibold.gl-text-secondary
- if label.project_label?
= sprite_icon('project', size: 12, css_class: 'gl-text-gray-600')
= sprite_icon('project', size: 12, css_class: 'gl-text-subtle')
- else
= sprite_icon('group', size: 12, css_class: 'gl-text-gray-600')
= sprite_icon('group', size: 12, css_class: 'gl-text-subtle')
= full_path

View File

@ -12,7 +12,7 @@
note_id: note.id } }
.timeline-entry-inner
- if note.system
.gl-float-left.gl-flex.gl-justify-center.gl-items-center.gl-rounded-full.-gl-mt-1.gl-ml-2.gl-w-6.gl-h-6.gl-bg-gray-50.gl-text-gray-600
.gl-float-left.gl-flex.gl-justify-center.gl-items-center.gl-rounded-full.-gl-mt-1.gl-ml-2.gl-w-6.gl-h-6.gl-bg-gray-50.gl-text-subtle
= icon_for_system_note(note)
- else
.timeline-avatar.gl-float-left

View File

@ -24,7 +24,7 @@ module Database
Gitlab::CurrentSettings.database_max_running_batched_background_migrations
end
# We have to overirde this one, as we want
# We have to override this one, as we want
# arguments passed as is, and not duplicated
def perform_with_capacity(args)
worker = new

View File

@ -1,15 +0,0 @@
- title: "Breaking change to the Maven repository group permissions"
announcement_milestone: "16.6"
removal_milestone: "18.0"
breaking_change: true
reporter: trizzi
stage: Package
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/393933
body: |
The Maven repository exposes an API endpoint at the group level that allows Maven clients to download files from a specific package. The package finder first locates the package within the group, and then finds the file within the package.
However, there is a limitation that affects duplicate package names hosted in different projects. The Maven package finder always returns the most recent package, but the "most recent" filter depends on user permissions. It is possible for a user with different permissions in different projects to download the wrong Maven package.
In GitLab 18.0, the package finder logic will be fixed so that the "most recent" package is the last updated name and version of a package in a group. User permissions will be checked after the most recent package is found.
After the change, download requests for users without correct permissions will be rejected. If your workflow depends on the current bugged behavior, this fix will introduce a breaking change.
The change will be introduced in GitLab 16.6 behind a feature flag. If you are interested in enabling the feature flag for your group, leave a comment in [issue 393933](https://gitlab.com/gitlab-org/gitlab/-/issues/393933).

View File

@ -7,4 +7,4 @@ feature_category: vulnerability_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/164110
milestone: '17.4'
queued_migration_version: 20240827165545
finalized_by: # version of the migration that finalized this BBM
finalized_by: 20241102143754

View File

@ -1,9 +0,0 @@
---
migration_job_name: BackfillRootStorageStatisticsForkStorageSizes
description: Backfill the public_forks_storage_size, internal_forks_storage_size,
and private_forks_storage_size columns on the namespace_root_storage_statistics
table
feature_category: consumables_cost_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120916
milestone: '16.1'
finalized_by: '20231207221036'

View File

@ -8,4 +8,13 @@ description: TODO
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/9d6fe7bfdf9ff3f68ee73baa0e3d0aa7df13c351
milestone: '10.8'
gitlab_schema: gitlab_ci
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/458479
desired_sharding_key:
project_id:
references: projects
backfill_via:
parent:
foreign_key: build_id
table: p_ci_builds
sharding_key: project_id
belongs_to: build
foreign_key_name: fk_89e29fa5ee_p

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class QueueBackfillPartitionCiRunners < Gitlab::Database::Migration[2.2]
include Gitlab::Database::PartitioningMigrationHelpers
milestone '17.6'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_ci
MIGRATION = 'BackfillCiRunnersPartitionedTable'
TABLE_NAME = 'ci_runners'
def up
enqueue_partitioning_data_migration TABLE_NAME, MIGRATION
end
def down
cleanup_partitioning_data_migration TABLE_NAME, MIGRATION
end
end

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
class RetryRevertAddFkFromPartitionedCiRunnerManagersToPartitionedCiRunners < Gitlab::Database::Migration[2.2]
milestone '17.6'
disable_ddl_transaction!
SOURCE_TABLE_NAME = :ci_runner_machines_687967fa8a
TARGET_TABLE_NAME = :ci_runners_e59bb2812d
FK_NAME = :fk_rails_3f92913d27
def up
# Remove the partitioned tables' FK, since it wasn't handled automatically
# by RevertAddFkFromPartitionedCiRunnerManagersToPartitionedCiRunners
Gitlab::Database::PostgresPartitionedTable.each_partition(SOURCE_TABLE_NAME) do |partition|
source = partition.to_s
with_lock_retries do
remove_foreign_key_if_exists(source, partitioned_target_table_name(source),
name: FK_NAME, reverse_lock_order: true)
end
end
end
def down
# no-op
end
private
def partitioned_target_table_name(source)
runner_type = source.match(/(.+?_type).+/)[1]
"#{runner_type}_#{TARGET_TABLE_NAME}"
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class FinalizeBackfillProjectIdToSecurityScans < Gitlab::Database::Migration[2.2]
milestone '17.6'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_sec
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillProjectIdToSecurityScans',
table_name: :security_scans,
column_name: :id,
job_arguments: [],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1 @@
6e8628dc5d1215fd16cd2f1a833c7fcb840f6d8171bcaacbe157716477cf7389

View File

@ -0,0 +1 @@
c2756b6e784e5a01eae712cbd986f98445b3f1f922d1cdcffa1928a2196235fd

View File

@ -0,0 +1 @@
637edf12dd1e216fde975dae1c46b2044dab3c3dd61ff040f12c16207cd0a437

View File

@ -23,9 +23,7 @@ GitLab Dedicated Engineers also don't have direct access to tenant environments,
NOTE:
An instance refers to a GitLab Dedicated deployment, whereas a tenant refers to a customer.
## Configuration changes
### Configure your instance using Switchboard
## Configure your instance using Switchboard
You can use Switchboard to make limited configuration changes to your GitLab Dedicated instance.
@ -48,7 +46,7 @@ To make a configuration change:
For all other instance configurations, submit a support ticket according to the
[configuration change request policy](#configuration-change-request-policy).
#### Applying configuration changes in Switchboard
### Apply configuration changes in Switchboard
You can apply configuration changes made in Switchboard immediately or defer them until your next scheduled weekly [maintenance window](../../administration/dedicated/create_instance.md#maintenance-window).
@ -58,29 +56,37 @@ When you apply changes immediately:
- Changes are applied in the order they're saved.
- You can save multiple changes and apply them in one batch.
After deployment, you'll receive an email notification. Check your spam folder if you don't see it in your main inbox.
After the deployment job is complete, you receive an email notification. Check your spam folder if you do not see a notification in your main inbox.
All users with access to view or edit your tenant in Switchboard receive a notification for each change. For more information, see [Manage Switchboard notification preferences](#manage-notification-preferences).
NOTE:
You will only receive email notifications for changes made by a Switchboard tenant admin. Changes made by a GitLab Operator (for example, a GitLab version update completed during a maintenance window) don't trigger email notifications.
You only receive email notifications for changes made by a Switchboard tenant administrator. Changes made by a GitLab Operator (for example, a GitLab version update completed during a maintenance window) do not trigger email notifications.
### View the configuration change log
## Configuration change log
You can use the configuration change log to track the changes made to your GitLab Dedicated instance, including:
The **Configuration change log** page in Switchboard tracks changes made to your GitLab Dedicated instance.
- Configuration change: Name of the configuration setting that changed.
- User: Email address of the user that made the configuration change. For changes made by a GitLab Operator, this value will appear as `GitLab Operator`.
- IP: IP address of the user that made the configuration change. For changes made by a GitLab Operator, this value will appear as `Unavailable`.
- Status: Whether the configuration change is initiated, in progress, completed, or deferred.
- Start time: Start date and time when the configuration change is initiated, in UTC.
- End time: End date and time when the configuration change is deployed, in UTC.
Each change log entry includes the following details:
| Field | Description |
|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
| Configuration change | Name of the configuration setting that changed. |
| User | Email address of the user that made the configuration change. For changes made by a GitLab Operator, this value appears as `GitLab Operator`. |
| IP | IP address of the user that made the configuration change. For changes made by a GitLab Operator, this value appears as `Unavailable`. |
| Status | Whether the configuration change is initiated, in progress, completed, or deferred. |
| Start time | Start date and time when the configuration change is initiated, in UTC. |
| End time | End date and time when the configuration change is deployed, in UTC. |
Each configuration change has a status:
- Initiated: Configuration change is made in Switchboard, but not yet deployed to the instance.
- In progress: Configuration change is actively being deployed to the instance.
- Complete: Configuration change has been deployed to the instance.
- Delayed: Initial job to deploy a change has failed and the change has not yet been assigned to a new job.
| Status | Description |
|---|---|
| Initiated | Configuration change is made in Switchboard, but not yet deployed to the instance. |
| In progress | Configuration change is actively being deployed to the instance. |
| Complete | Configuration change has been deployed to the instance. |
| Delayed | Initial job to deploy a change has failed and the change has not yet been assigned to a new job. |
### View the configuration change log
To view the configuration change log:
@ -88,11 +94,13 @@ To view the configuration change log:
1. Select your tenant.
1. At the top of the page, select **Configuration change log**.
### Configuration change request policy
Each configuration change appears as an entry in the table. Select **View details** to see more information about each change.
## Configuration change request policy
This policy does not apply to configuration changes made by a GitLab Dedicated instance admin using Switchboard.
Configuration changes requested with a [support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650):
Configuration changes requested with a [support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650) adhere to the following policies:
- Are applied during your environment's weekly four-hour maintenance window.
- Can be requested for options specified during onboarding or for optional features listed on this page.
@ -102,17 +110,17 @@ Configuration changes requested with a [support ticket](https://support.gitlab.c
NOTE:
Even if a change request meets the minimum lead time, it might not be applied during the upcoming maintenance window.
### Bring your own domain (BYOD)
## Bring your own domain (BYOD)
You can use a [custom hostname](../../subscriptions/gitlab_dedicated/index.md#bring-your-own-domain) to access your GitLab Dedicated instance. You can also provide a custom hostname for the bundled container registry and Kubernetes Agent Server (KAS) services.
#### Let's Encrypt certificates
### Let's Encrypt certificates
GitLab Dedicated integrates with [Let's Encrypt](https://letsencrypt.org/), a free, automated, and open source certificate authority. When you use a custom hostname, Let's Encrypt automatically issues and renews SSL/TLS certificates for your domain.
This integration uses the [`http-01` challenge](https://letsencrypt.org/docs/challenge-types/#http-01-challenge) to obtain certificates through Let's Encrypt.
#### Set up DNS records
### Set up DNS records
To use a custom hostname with GitLab Dedicated, you must update your domain's DNS records.
@ -143,17 +151,17 @@ To set up DNS records for a custom hostname with GitLab Dedicated:
1. Save your changes and wait for the DNS changes to propagate.
#### Add your custom hostname
### Add your custom hostname
To add a custom hostname to your existing GitLab Dedicated instance, submit a [support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650).
### SMTP email service
## SMTP email service
You can configure an [SMTP](../../subscriptions/gitlab_dedicated/index.md#email-service) email service for your GitLab Dedicated instance.
To configure an SMTP email service, submit a [support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650) with the credentials and settings for your SMTP server.
### Inbound Private Link
## Inbound Private Link
[AWS Private Link](https://docs.aws.amazon.com/vpc/latest/privatelink/what-is-privatelink.html) allows users and applications in your VPC on AWS to securely connect to the GitLab Dedicated endpoint without network traffic going over the public internet.
@ -170,7 +178,7 @@ To enable the Inbound Private Link:
1. After you create the endpoint, use the instance URL provided to you during onboarding to securely connect to your GitLab Dedicated instance from your VPC, without the traffic going over the public internet.
### Outbound Private Link
## Outbound Private Link
Outbound private links allow your GitLab Dedicated instance and the hosted runners for GitLab Dedicated to securely communicate with services running in your VPC on AWS without exposing any traffic to the public internet.
@ -219,7 +227,7 @@ To enable an Outbound Private Link:
GitLab then configures the tenant instance to create the necessary Endpoint Interfaces based on the service names you provided. Any matching outbound
connections made from the tenant instance are directed through the PrivateLink into your VPC.
#### Private hosted zones
### Private hosted zones
You can use a private hosted zone (PHZ) if:
@ -240,11 +248,11 @@ For example:
If you don't use the Dedicated instance domain, the PHZ name and a PHZ entry in the format `phz-entry.phz-name.com` is still required.
### Custom certificates
## Custom certificates
In some cases, the GitLab Dedicated instance can't reach an internal service you own because it exposes a certificate that can't be validated using a public Certification Authority (CA). In these cases, custom certificates are required.
#### Add a custom certificate with Switchboard
### Add a custom certificate with Switchboard
1. Sign in to [Switchboard](https://console.gitlab-dedicated.com/).
1. At the top of the page, select **Configuration**.
@ -254,21 +262,21 @@ In some cases, the GitLab Dedicated instance can't reach an internal service you
1. Select **Save**.
1. Scroll up to the top of the page and select whether to apply the changes immediately or during the next maintenance window.
#### Add a custom certificate with a Support Request
### Add a custom certificate with a Support Request
If you are unable to use Switchboard to add a custom certificate, you can open a [support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650) and attach your custom public certificate files to request this change..
#### Maximum number of reverse PrivateLink connections
### Maximum number of reverse PrivateLink connections
GitLab Dedicated limits the number of reverse PrivateLink connections to 10.
### IP allowlist
## IP allowlist
GitLab Dedicated allows you to control which IP addresses can access your instance through an IP allowlist. Once the IP allowlist has been enabled, when an IP not on the allowlist tries to access your instance an `HTTP 403 Forbidden` response is returned.
IP addresses that have been added to your IP allowlist can be viewed on the Configuration page in Switchboard. You can add or remove IP addresses from your allowlist with Switchboard.
#### Add an IP to the allowlist with Switchboard
### Add an IP to the allowlist with Switchboard
1. Sign in to [Switchboard](https://console.gitlab-dedicated.com/).
1. At the top of the page, select **Configuration**.
@ -279,11 +287,11 @@ IP addresses that have been added to your IP allowlist can be viewed on the Conf
1. Select **Save**.
1. Scroll up to the top of the page and select whether to apply the changes immediately or during the next maintenance window. After the changes are applied, the IP addresses are added to the IP allowlist for your instance.
#### Add an IP to the allowlist with a Support Request
### Add an IP to the allowlist with a Support Request
If you are unable to use Switchboard to update your IP allowlist, you can open a [support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650) and specify a comma separated list of IP addresses that can access your GitLab Dedicated instance.
#### Enable OpenID Connect for your IP allowlist
### Enable OpenID Connect for your IP allowlist
Using [GitLab as an OpenID Connect identity provider](../../integration/openid_connect_provider.md) requires internet access to the OpenID Connect verification endpoint.
@ -293,7 +301,7 @@ To enable access to the OpenID Connect endpoint while maintaining your IP allowl
The configuration is applied during the next maintenance window.
### SAML
## SAML
You can [configure SAML single sign-on (SSO)](../../integration/saml.md#configure-saml-support-in-gitlab) for your GitLab Dedicated instance. Optionally, you can configure more than one SAML identity provider (IdP).
@ -311,7 +319,7 @@ Prerequisites:
NOTE:
You can only configure one SAML IdP with Switchboard. If you configured a SAML IdP on your GitLab Dedicated instance before the introduction of support for multiple IdPs, you can manage that provider through Switchboard. To configure additional SAML IdPs, [submit a support request](#activate-saml-with-a-support-request).
#### Activate SAML with Switchboard
### Activate SAML with Switchboard
To activate SAML for your GitLab Dedicated instance:
@ -341,7 +349,7 @@ To activate SAML for your GitLab Dedicated instance:
- Check that the SSO button description is displayed on your instance's sign-in page.
- Go to the metadata URL of your instance (`https://INSTANCE-URL/users/auth/saml/metadata`). This page can be used to simplify much of the configuration of the identity provider, and manually validate the settings.
#### Activate SAML with a Support Request
### Activate SAML with a Support Request
If you are unable to use Switchboard to activate or update SAML for your GitLab Dedicated instance, or if you need to configure more than one SAML IdP, then you can open a [support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650):
@ -385,7 +393,7 @@ If you are unable to use Switchboard to activate or update SAML for your GitLab
- Check that the SSO login button description is displayed on your instance's login page.
- Go to the metadata URL of your instance, which is provided by GitLab in the support ticket. This page can be used to simplify much of the configuration of the identity provider, as well as manually validate the settings.
#### Request signing
### Request signing
If [SAML request signing](../../integration/saml.md#sign-saml-authentication-requests-optional) is desired, a certificate must be obtained. This certificate can be self-signed which has the advantage of not having to prove ownership of an arbitrary Common Name (CN) to a public Certificate Authority (CA).
@ -400,13 +408,13 @@ To enable SAML request signing:
Authentication requests from GitLab to your identity provider can now be signed.
#### SAML groups
### SAML groups
With SAML groups you can configure GitLab users based on SAML group membership.
To enable SAML groups, add the [required elements](../../integration/saml.md#configure-users-based-on-saml-group-membership) to your SAML configuration in [Switchboard](#activate-saml-with-switchboard) or to the SAML block you provide in a [support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650).
#### Group sync
### Group sync
With [group sync](../../user/group/saml_sso/group_sync.md), you can sync users across identity provider groups to mapped groups in GitLab.
@ -415,7 +423,7 @@ To enable group sync:
1. Add the [required elements](../../user/group/saml_sso/group_sync.md#configure-saml-group-sync) to your SAML configuration in [Switchboard](#activate-saml-with-switchboard) or to the SAML configuration block you provide in a [support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650).
1. Configure the [Group Links](../../user/group/saml_sso/group_sync.md#configure-saml-group-links).
### Add users to an instance
## Add users to an instance
Administrators can add Switchboard users to their GitLab Dedicated instance. There are two types of users:
@ -432,7 +440,7 @@ To add a new user to your GitLab Dedicated instance:
An invitation to use Switchboard is sent to the user.
#### Manage notification preferences
### Manage notification preferences
You can specify whether you want to receive email notifications from Switchboard. You will only receive notifications after you:
@ -447,13 +455,13 @@ To manage your own email notification preferences:
You will see an alert confirming that your notification preferences have been updated.
### Application logs
## Application logs
GitLab delivers [application logs](../../administration/logs/index.md) to an Amazon S3 bucket in the GitLab tenant account, which can be shared with you.
Logs stored in the S3 bucket are retained indefinitely, until the one year retention policy is fully enforced. GitLab team members can view more information in confidential issue [483](https://gitlab.com/gitlab-com/gl-infra/gitlab-dedicated/team/-/issues/483).
#### Request bucket access
### Request bucket access
To gain read only access to the S3 bucket with your application logs:
@ -462,7 +470,7 @@ To gain read only access to the S3 bucket with your application logs:
GitLab provides the name of the S3 bucket. Your authorized users or roles can then access all objects in the bucket. To verify access, you can use the [AWS CLI](https://aws.amazon.com/cli/).
#### Bucket contents and structure
### Bucket contents and structure
The Amazon S3 bucket contains a combination of infrastructure logs and application logs from the GitLab [log system](../../administration/logs/index.md).

View File

@ -3967,6 +3967,7 @@ Input type: `CustomFieldCreateInput`
| <a id="mutationcustomfieldcreatefieldtype"></a>`fieldType` | [`CustomFieldType!`](#customfieldtype) | Type of custom field. |
| <a id="mutationcustomfieldcreategrouppath"></a>`groupPath` | [`ID!`](#id) | Group path where the custom field is created. |
| <a id="mutationcustomfieldcreatename"></a>`name` | [`String!`](#string) | Name of the custom field. |
| <a id="mutationcustomfieldcreateselectoptions"></a>`selectOptions` | [`[CustomFieldSelectOptionInput!]`](#customfieldselectoptioninput) | Available options for a select field. |
#### Fields
@ -3991,6 +3992,7 @@ Input type: `CustomFieldUpdateInput`
| <a id="mutationcustomfieldupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcustomfieldupdateid"></a>`id` | [`IssuablesCustomFieldID!`](#issuablescustomfieldid) | Global ID of the custom field. |
| <a id="mutationcustomfieldupdatename"></a>`name` | [`String!`](#string) | Name of the custom field. |
| <a id="mutationcustomfieldupdateselectoptions"></a>`selectOptions` | [`[CustomFieldSelectOptionInput!]`](#customfieldselectoptioninput) | Available options for a select field. |
#### Fields
@ -43097,6 +43099,17 @@ Attributes for defining a CI/CD variable.
| <a id="complianceviolationprojectinputmergedbefore"></a>`mergedBefore` | [`Date`](#date) | Merge requests merged before the date (inclusive). |
| <a id="complianceviolationprojectinputtargetbranch"></a>`targetBranch` | [`String`](#string) | Filter compliance violations by target branch. |
### `CustomFieldSelectOptionInput`
Attributes for the custom field select option.
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="customfieldselectoptioninputid"></a>`id` | [`IssuablesCustomFieldSelectOptionID`](#issuablescustomfieldselectoptionid) | Global ID of the custom field select option to update. Creates a new record if not provided. |
| <a id="customfieldselectoptioninputvalue"></a>`value` | [`String!`](#string) | Value of the custom field select option. |
### `DastProfileCadenceInput`
Represents DAST Profile Cadence.

View File

@ -32,7 +32,7 @@ This access can also [be restricted](#limit-job-token-scope-for-public-or-intern
| Feature | Additional details |
|-------------------------------------------------------------------------------------------------------|--------------------|
| [Container registry API](../../api/container_registry.md) | The token is scoped to the container registry of the job's project only. |
| [Container registry](../../user/packages/container_registry/build_and_push_images.md#use-gitlab-cicd) | The `$CI_REGISTRY_PASSWORD` [predefined variable](../variables/predefined_variables.md) is the CI/CD job token. |
| [Container registry](../../user/packages/container_registry/build_and_push_images.md#use-gitlab-cicd) | The `$CI_REGISTRY_PASSWORD` [predefined variable](../variables/predefined_variables.md) is the CI/CD job token. Both are scoped to the container registry of the job's project only. |
| [Deployments API](../../api/deployments.md) | `GET` requests are public by default. |
| [Environments API](../../api/environments.md) | `GET` requests are public by default. |
| [Job artifacts API](../../api/job_artifacts.md#get-job-artifacts) | `GET` requests are public by default. |
@ -69,7 +69,7 @@ jobs.
## Control job token access to your project
You can control which groups or projects can use a job token to authenticate and access your project's resources.
You can control which groups or projects can use a job token to authenticate and access some of your project's resources.
By default, job token access is restricted to only CI/CD jobs that run in pipelines in
your project. To allow another group or project to authenticate with a job token from the other

View File

@ -207,7 +207,7 @@ refresh_service.execute(oldrev, newrev, ref)
See ["Why is it bad style to `rescue Exception => e` in Ruby?"](https://stackoverflow.com/questions/10048173/why-is-it-bad-style-to-rescue-exception-e-in-ruby).
This rule is [enforced automatically by RuboCop](https://gitlab.com/gitlab-org/gitlab-foss/blob/8-4-stable/.rubocop.yml#L911-914)._
This rule is [enforced automatically by RuboCop](https://gitlab.com/gitlab-org/gitlab-foss/blob/8-4-stable/.rubocop.yml#L911-914).
## Do not use inline JavaScript in views

View File

@ -66,7 +66,7 @@ the tiers are no longer mentioned in GitLab documentation:
- [Full code quality reports in the code quality tab](../ci/testing/code_quality.md#pipeline-details-view)
- [Merge request approvals](../user/project/merge_requests/approvals/index.md)
- [Multiple assignees](../user/project/merge_requests/index.md#assign-a-user-to-a-merge-request)
- [Approval rule information for reviewers](../user/project/merge_requests/reviews/index.md#see-how-reviewers-map-to-approval-rules)
- [Approval rule information for reviewers](../user/project/merge_requests/reviews/index.md#request-a-review)
- [Required Approvals](../user/project/merge_requests/approvals/index.md#required-approvals)
- [Code Owners as eligible approvers](../user/project/merge_requests/approvals/rules.md#code-owners-as-eligible-approvers)
- [Approval rules](../user/project/merge_requests/approvals/rules.md) features

View File

@ -196,28 +196,6 @@ We've deprecated the use of `ref` and `sha` in API calls to `GET /projects/:id/c
<div class="deprecation breaking-change" data-milestone="18.0">
### Breaking change to the Maven repository group permissions
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">16.6</span>
- Removal in GitLab <span class="milestone">18.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/393933).
</div>
The Maven repository exposes an API endpoint at the group level that allows Maven clients to download files from a specific package. The package finder first locates the package within the group, and then finds the file within the package.
However, there is a limitation that affects duplicate package names hosted in different projects. The Maven package finder always returns the most recent package, but the "most recent" filter depends on user permissions. It is possible for a user with different permissions in different projects to download the wrong Maven package.
In GitLab 18.0, the package finder logic will be fixed so that the "most recent" package is the last updated name and version of a package in a group. User permissions will be checked after the most recent package is found.
After the change, download requests for users without correct permissions will be rejected. If your workflow depends on the current bugged behavior, this fix will introduce a breaking change.
The change will be introduced in GitLab 16.6 behind a feature flag. If you are interested in enabling the feature flag for your group, leave a comment in [issue 393933](https://gitlab.com/gitlab-org/gitlab/-/issues/393933).
</div>
<div class="deprecation breaking-change" data-milestone="18.0">
### CodeClimate-based Code Quality scanning will be removed
<div class="deprecation-notes">

View File

@ -1,48 +1,14 @@
---
stage: AI-powered
group: AI Model Validation
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
ignore_in_report: true
redirect_to: 'index.md'
remove_date: '2025-02-03'
---
# Suggested Reviewers data usage
<!-- markdownlint-disable -->
DETAILS:
**Tier:** Ultimate
**Offering:** GitLab.com
This document was moved to [another location](index.md).
## How it works
Suggested Reviewers is the first user-facing GitLab machine learning (ML) powered feature. It leverages a project's contribution graph to generate suggestions. This data already exists within GitLab including merge request metadata, source code files, and GitLab user account metadata.
### Enabling the feature
When a Project Maintainer or Owner enables Suggested Reviewers in project settings, GitLab kicks off a data extraction job for the project which leverages the Merge Request API to understand pattern of review including recency, domain experience, and frequency to suggest an appropriate reviewer. If projects do not use the [merge request approval process](../approvals/index.md) or do not have any historical merge request data, Suggested Reviewers cannot suggest reviewers.
This data extraction job can take a few hours to complete (possibly up to a day), which is largely dependent on the size of the project. The process is automated and no action is needed during this process. Once data extraction is complete, you start getting suggestions in merge requests.
### Generating suggestions
Once Suggested Reviewers is enabled and the data extraction is complete, new merge requests or new commits to existing merge requests automatically trigger a Suggested Reviewers ML model inference and generate up to 5 suggested reviewers. These suggestions are contextual to the changes in the merge request. Additional commits to merge requests may change the reviewer suggestions, which are automatically updated in the reviewer dropdown list.
## Progressive enhancement
This feature is designed as a progressive enhancement to the existing GitLab Reviewers functionality. The GitLab Reviewer UI only offers suggestions if the ML engine is able to provide a recommendation. In the event of an issue or model inference failure, the feature gracefully degrades. At no point with the usage of Suggested Reviewers prevent a user from being able to manually set a reviewer.
## Model Accuracy
Organizations use many different processes for code review. Some focus on senior engineers reviewing junior engineer's code, others have hierarchical organizational structure based reviews. Suggested Reviewers is focused on contextual reviewers based on historical merge request activity by users. While we continue evolving the underlying ML model to better serve various code review use cases and processes Suggested Reviewers does not replace the usage of other code review features like Code Owners and [Approval Rules](../approvals/rules.md). Reviewer selection is highly subjective therefore, we do not expect Suggested Reviewers to provide perfect suggestions every time.
Through analysis of beta customer usage, we find that the Suggested Reviewers ML model provides suggestions that are adopted in 60% of cases. We plan to introduce a feedback mechanism into the Suggested Reviewers feature in the future to allow users to flag bad reviewer suggestions to help improve the model. Additionally we plan to offer an opt-in feature in the future which allows the model to use your project's data for training the underlying model.
## Off by default
Suggested Reviewers is off by default and requires a Project Owner or Admin to enable the feature.
## Data privacy
Suggested Reviewers operates completely within the GitLab.com infrastructure providing the same level of [privacy](https://about.gitlab.com/privacy/) and [security](https://about.gitlab.com/security/) of any other feature of GitLab.com.
No new additional data is collected to enable this feature. GitLab infers your merge request against a trained machine learning model. The content of your source code is not used as training data. Your data also never leaves GitLab.com, all training and inference is done within GitLab.com infrastructure.
[Read more about the security of GitLab.com](https://about.gitlab.com/security/faq/)
<!-- This redirect file can be deleted after the global navigation is updated -->
<!-- This redirect file can be deleted after <2025-02-03>. -->
<!-- Redirects that point to other docs in the same project expire in three months. -->
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -52,36 +52,45 @@ of a merge request. Each **Reviewer** shows the status to the right of the user'
## Request a review
To assign a reviewer to a merge request, in a text area in
the merge request, use the `/assign_reviewer @user`
[quick action](../../quick_actions.md#issues-merge-requests-and-epics), or:
> - Enhanced reviewer drawer [introduced](https://gitlab.com/groups/gitlab-org/-/epics/12878) in GitLab 17.5 [with a flag](../../../../administration/feature_flags.md) named `reviewer_assign_drawer`.
> - [Enabled](https://gitlab.com/gitlab-org/gitlab/-/issues/467205) on GitLab.com and self-managed in GitLab 17.5.
When you've finished preparing your changes, it's time to request a review. To assign a reviewer to your merge request,
either use the `/assign_reviewer @user`
[quick action](../../quick_actions.md#issues-merge-requests-and-epics) in any text field, or:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Merge requests** and find your merge request.
1. Select the title of the merge request to view it.
1. On the right sidebar, in the **Reviewers** section, select **Edit**.
1. Search for the user you want to assign, and select the user.
1. On the right sidebar, in the **Reviewers** section:
- To find a specific reviewer by name, select **Edit**.
- In GitLab Premium and Ultimate, to find a reviewer
[who fulfills approval rules](#find-reviewers-who-fulfill-approval-rules), select **Assign** to
open the reviewer drawer.
GitLab adds the merge request to the user's review requests.
### From multiple users
### Find reviewers who fulfill approval rules
DETAILS:
**Tier:** Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
To assign multiple reviewers to a merge request, in a text area in
the merge request, use the `/assign_reviewer @user1 @user2`
[quick action](../../quick_actions.md#issues-merge-requests-and-epics), or:
GitLab Premium and Ultimate help you more quickly find the best reviewers for your merge request.
Use the **Assign reviewers** drawer to filter lists of reviewers. See the Code Owners for the files
changed in your merge request, and the users who satisfy your project's approval rules.
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Merge requests** and find your merge request.
1. Select the title of the merge request to view it.
1. On the right sidebar, in the **Reviewers** section, select **Edit**.
1. From the dropdown list, select all the users you want
to assign to the merge request.
In this example, the merge request requires 3 Code Owner approvals, but has none so far:
To remove a reviewer, clear the user from the same dropdown list.
![The Assign Reviewers drawer for a merge request that requires 3 Code Owner approvals, but has none. It shows one line per Code Owner rule, and one line per approval rule. You can select reviewers for each rule.](img/select_good_reviewers_v17_5.png)
1. To see optional approval rules or Code Owners, select **Optional approval rules** (**{chevron-lg-up}**) to show them.
1. Next to the reviewer type you need, select **Edit**:
- **Code Owners** shows only the Code Owners for that file type.
- **Approval rules** shows only users who fulfill that approval rule.
1. Select your desired reviewers. (GitLab Premium and Ultimate enable you to select multiple reviewers.)
1. Repeat for each required **Code Owner** and **Approval rule** item.
1. When you've selected your reviewers, on the top right, select **Close** (**{close}**) to hide the drawer.
### Re-request a review
@ -196,43 +205,6 @@ another user with permission to merge the merge request can override this check:
![This merge request contains a bypassed check, and should be merged with caution.](img/status_warning_v17_4.png)
### See how reviewers map to approval rules
DETAILS:
**Tier:** Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
When you create a merge request, you want to request reviews from
subject matter experts for the changes you're making. To decrease the number of
review cycles for your merge request, consider requesting reviews from users
listed in the project's approval rules.
When you edit the **Reviewers** field in a merge request, GitLab shows you
the matching [approval rule](../approvals/rules.md) below the name of each reviewer.
[Code Owners](../../codeowners/index.md) display as `Codeowner` without any group detail.
::Tabs
:::TabTitle Create or edit a merge request
1. When you create a new merge request, or edit an existing one, select **Reviewers**.
1. Begin entering the name of your desired reviewer. Users who are Code Owners, or match an approval rule, show more information below the username:
![Reviewer approval rules in new/edit form](img/reviewer_approval_rules_form_v15_9.png)
:::TabTitle Reviewing a merge request
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Merge requests**.
1. Select your merge request.
1. On the right sidebar, next to **Reviewers**, select **Edit**.
1. Begin entering the name of your desired reviewer. Users who are Code Owners,
or who match an approval rule, show more information below the username:
![Reviewer approval rules in sidebar](img/reviewer_approval_rules_sidebar_v15_9.png)
::EndTabs
## Download merge request changes
### As a diff
@ -281,54 +253,6 @@ To download and apply the patch in a one-line CLI command using [`git am`](https
curl "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/000000.patch" | git am
```
## Suggested Reviewers
DETAILS:
**Tier:** Ultimate
**Offering:** GitLab.com
> - [Introduced](https://gitlab.com/groups/gitlab-org/modelops/applied-ml/review-recommender/-/epics/3) in GitLab 15.4 as a [beta](../../../../policy/experiment-beta-support.md#beta) feature [with a flag](../../../../administration/feature_flags.md) named `suggested_reviewers_control`. Disabled by default.
> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/368356) in GitLab 15.6.
> - Beta designation [removed from the UI](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113058) in GitLab 15.10.
> - Feature flag [removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134728) in GitLab 16.6.
GitLab uses machine learning to suggest reviewers for your merge request.
To suggest reviewers, GitLab uses:
- The changes in the merge request
- The project's contribution graph
Suggested Reviewers also integrates with Code Owners, profile status,
and merge request rules. It helps you make a more informed decision when choosing
reviewers who can meet your review criteria.
![A list of reviewers.](img/suggested_reviewers_v16_3.png)
For more information, see [Data usage in Suggested Reviewers](data_usage.md).
### Enable Suggested Reviewers
Enabling Suggested Reviewers triggers GitLab to create the machine learning model your
project uses to generate reviewers. The larger your project, the longer
this process can take. The model is usually ready to generate suggestions
after a few hours.
Prerequisites:
- You have the Owner or Maintainer role for the project.
To do this:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > Merge requests**.
1. Scroll to **Suggested reviewers**, and select **Enable suggested reviewers**.
1. Select **Save changes**.
After you enable the feature, no action is needed. After the model is ready,
recommendations populate the **Reviewer** dropdown list in the right-hand sidebar
of a merge request with new commits.
## Associated features
Merge requests are related to these features:

View File

@ -139,7 +139,6 @@ Configure your project's merge request settings:
- [Merge only if pipeline succeeds](../merge_requests/auto_merge.md).
- [Merge only when all threads are resolved](../merge_requests/index.md#prevent-merge-unless-all-threads-are-resolved).
- [Required associated issue from Jira](../../../integration/jira/issues.md#require-associated-jira-issue-for-merge-requests-to-be-merged).
- [Suggested Reviewers](../merge_requests/reviews/index.md#suggested-reviewers)
- [**Delete source branch when merge request is accepted** option by default](#delete-the-source-branch-on-merge-by-default).
- Configure:
- [Suggested changes commit messages](../merge_requests/reviews/suggestions.md#configure-the-commit-message-for-applied-suggestions).

View File

@ -39,7 +39,7 @@ GEM
rack (3.1.8)
rainbow (3.1.1)
regexp_parser (2.8.1)
rexml (3.3.8)
rexml (3.3.9)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
@ -102,4 +102,4 @@ DEPENDENCIES
sqlite3 (~> 1.6)
BUNDLED WITH
2.5.21
2.5.22

View File

@ -41,7 +41,7 @@ GEM
rainbow (3.1.1)
rake (13.0.6)
regexp_parser (2.8.1)
rexml (3.3.8)
rexml (3.3.9)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
@ -103,4 +103,4 @@ DEPENDENCIES
rubocop-rspec
BUNDLED WITH
2.5.21
2.5.22

View File

@ -37,7 +37,7 @@ GEM
rack (3.1.8)
rainbow (3.1.1)
regexp_parser (2.8.1)
rexml (3.3.8)
rexml (3.3.9)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
@ -97,4 +97,4 @@ DEPENDENCIES
rubocop-rspec (~> 2.22)
BUNDLED WITH
2.5.21
2.5.22

View File

@ -133,7 +133,7 @@ GEM
rainbow (3.1.1)
rake (13.2.1)
regexp_parser (2.9.2)
rexml (3.3.8)
rexml (3.3.9)
rspec (3.13.0)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
@ -209,4 +209,4 @@ DEPENDENCIES
rubocop-rspec (~> 2.27.1)
BUNDLED WITH
2.5.21
2.5.22

View File

@ -205,7 +205,7 @@ GEM
regexp_parser (2.7.0)
request_store (1.5.1)
rack (>= 1.4)
rexml (3.3.8)
rexml (3.3.9)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
@ -300,4 +300,4 @@ DEPENDENCIES
rubocop-rspec (~> 2.22)
BUNDLED WITH
2.5.21
2.5.22

View File

@ -43,7 +43,7 @@ GEM
rack (3.1.8)
rainbow (3.1.1)
regexp_parser (2.8.2)
rexml (3.3.8)
rexml (3.3.9)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
@ -102,4 +102,4 @@ DEPENDENCIES
rspec (~> 3.0)
BUNDLED WITH
2.5.21
2.5.22

View File

@ -74,7 +74,7 @@ GEM
rainbow (3.1.1)
rake (13.1.0)
regexp_parser (2.8.3)
rexml (3.3.8)
rexml (3.3.9)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
@ -161,4 +161,4 @@ DEPENDENCIES
webmock
BUNDLED WITH
2.5.21
2.5.22

View File

@ -125,7 +125,7 @@ GEM
rainbow (3.1.1)
rake (13.0.6)
regexp_parser (2.8.1)
rexml (3.3.8)
rexml (3.3.9)
rouge (4.3.0)
rspec (3.12.0)
rspec-core (~> 3.12.0)
@ -220,4 +220,4 @@ DEPENDENCIES
webrick (~> 1.8)
BUNDLED WITH
2.5.21
2.5.22

View File

@ -94,7 +94,7 @@ GEM
rainbow (3.1.1)
rake (13.0.6)
regexp_parser (2.8.1)
rexml (3.3.8)
rexml (3.3.9)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
@ -185,4 +185,4 @@ DEPENDENCIES
rubocop-rspec (~> 2.22)
BUNDLED WITH
2.5.21
2.5.22

View File

@ -67,7 +67,7 @@ GEM
rack (3.1.8)
rainbow (3.1.1)
regexp_parser (2.8.1)
rexml (3.3.8)
rexml (3.3.9)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
@ -143,4 +143,4 @@ DEPENDENCIES
rubocop-rspec (~> 2.22)
BUNDLED WITH
2.5.21
2.5.22

View File

@ -41,7 +41,7 @@ GEM
regexp_parser (2.7.0)
request_store (1.5.1)
rack (>= 1.4)
rexml (3.3.8)
rexml (3.3.9)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
@ -101,4 +101,4 @@ DEPENDENCIES
rubocop-rspec (~> 2.22)
BUNDLED WITH
2.5.21
2.5.22

View File

@ -54,7 +54,7 @@ GEM
rack (3.1.8)
rainbow (3.1.1)
regexp_parser (2.8.1)
rexml (3.3.8)
rexml (3.3.9)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
@ -136,4 +136,4 @@ DEPENDENCIES
rubocop-rspec (~> 2.22)
BUNDLED WITH
2.5.21
2.5.22

View File

@ -62,7 +62,7 @@ GEM
re2 (2.4.3)
mini_portile2 (~> 2.8.5)
regexp_parser (2.8.2)
rexml (3.3.8)
rexml (3.3.9)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
@ -148,4 +148,4 @@ DEPENDENCIES
rubocop-rspec (~> 2.22)
BUNDLED WITH
2.5.21
2.5.22

View File

@ -108,7 +108,7 @@ GEM
rainbow (3.1.1)
rake (13.0.6)
regexp_parser (2.8.1)
rexml (3.3.8)
rexml (3.3.9)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
@ -201,4 +201,4 @@ DEPENDENCIES
rubocop-rspec (~> 2.22)
BUNDLED WITH
2.5.21
2.5.22

View File

@ -53,7 +53,7 @@ GEM
rainbow (3.1.1)
rake (13.0.6)
regexp_parser (2.8.1)
rexml (3.3.8)
rexml (3.3.9)
rspec (3.11.0)
rspec-core (~> 3.11.0)
rspec-expectations (~> 3.11.0)
@ -136,4 +136,4 @@ DEPENDENCIES
simplecov (~> 0.22.0)
BUNDLED WITH
2.5.21
2.5.22

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Background migration to copy only valid data from ci_runners to its corresponding partitioned table
# rubocop: disable Migration/BatchedMigrationBaseClass -- This is indirectly deriving from the correct base class
class BackfillCiRunnersPartitionedTable < BackfillPartitionedTable
extend ::Gitlab::Utils::Override
private
override :filter_sub_batch_content
def filter_sub_batch_content(relation)
relation.where(runner_type: 1).or(relation.where.not(sharding_key_id: nil))
end
end
# rubocop: enable Migration/BatchedMigrationBaseClass
end
end

View File

@ -9,7 +9,7 @@ module Gitlab
job_arguments :partitioned_table
def perform
validate_paritition_table!
validate_partition_table!
bulk_copy = Gitlab::Database::PartitioningMigrationHelpers::BulkCopy.new(
batch_table,
@ -19,16 +19,15 @@ module Gitlab
)
each_sub_batch do |relation|
sub_start_id, sub_stop_id = relation.pick(Arel.sql("MIN(#{batch_column}), MAX(#{batch_column})"))
bulk_copy.copy_between(sub_start_id, sub_stop_id)
bulk_copy.copy_relation(filter_sub_batch_content(relation))
end
end
private
def validate_paritition_table!
def validate_partition_table!
unless connection.table_exists?(partitioned_table)
raise "exiting backfill migration because partitioned table #{partitioned_table} does not exist. " \
raise "exiting backfill migration because partitioned table '#{partitioned_table}' does not exist. " \
"This could be due to rollback of the migration which created the partitioned table."
end
@ -38,6 +37,10 @@ module Gitlab
end
# rubocop: enable Style/GuardClause
end
def filter_sub_batch_content(relation)
relation
end
end
end
end

View File

@ -1,99 +0,0 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Backfill the following columns on the namespace_root_storage_statistics table:
# - public_forks_storage_size
# - internal_forks_storage_size
# - private_forks_storage_size
class BackfillRootStorageStatisticsForkStorageSizes < BatchedMigrationJob
operation_name :backfill_root_storage_statistics_fork_sizes
feature_category :consumables_cost_management
VISIBILITY_LEVELS_TO_STORAGE_SIZE_COLUMNS = {
0 => :private_forks_storage_size,
10 => :internal_forks_storage_size,
20 => :public_forks_storage_size
}.freeze
def perform
each_sub_batch do |sub_batch|
sub_batch.each do |root_storage_statistics|
next if has_fork_data?(root_storage_statistics)
namespace_id = root_storage_statistics.namespace_id
namespace_type = execute("SELECT type FROM namespaces WHERE id = #{namespace_id}").first&.fetch('type')
next if namespace_type.nil?
sql = if user_namespace?(namespace_type)
user_namespace_sql(namespace_id)
else
group_namespace_sql(namespace_id)
end
stats = execute(sql)
.map { |h| { h['projects_visibility_level'] => h['sum_project_statistics_storage_size'] } }
.reduce({}) { |memo, h| memo.merge(h) }
.transform_keys { |k| VISIBILITY_LEVELS_TO_STORAGE_SIZE_COLUMNS[k] }
root_storage_statistics.update!(stats)
end
end
end
def has_fork_data?(root_storage_statistics)
root_storage_statistics.public_forks_storage_size != 0 ||
root_storage_statistics.internal_forks_storage_size != 0 ||
root_storage_statistics.private_forks_storage_size != 0
end
def user_namespace?(type)
type.nil? || type == 'User' || !(type == 'Group' || type == 'Project')
end
def execute(sql)
::ApplicationRecord.connection.execute(sql)
end
def user_namespace_sql(namespace_id)
<<~SQL
SELECT
SUM("project_statistics"."storage_size") AS sum_project_statistics_storage_size,
"projects"."visibility_level" AS projects_visibility_level
FROM
"projects"
INNER JOIN "project_statistics" ON "project_statistics"."project_id" = "projects"."id"
INNER JOIN "fork_network_members" ON "fork_network_members"."project_id" = "projects"."id"
INNER JOIN "fork_networks" ON "fork_networks"."id" = "fork_network_members"."fork_network_id"
WHERE
"projects"."namespace_id" = #{namespace_id}
AND (fork_networks.root_project_id != projects.id)
GROUP BY "projects"."visibility_level"
SQL
end
def group_namespace_sql(namespace_id)
<<~SQL
SELECT
SUM("project_statistics"."storage_size") AS sum_project_statistics_storage_size,
"projects"."visibility_level" AS projects_visibility_level
FROM
"projects"
INNER JOIN "project_statistics" ON "project_statistics"."project_id" = "projects"."id"
INNER JOIN "fork_network_members" ON "fork_network_members"."project_id" = "projects"."id"
INNER JOIN "fork_networks" ON "fork_networks"."id" = "fork_network_members"."fork_network_id"
WHERE
"projects"."namespace_id" IN (
SELECT namespaces.traversal_ids[array_length(namespaces.traversal_ids, 1)] AS id
FROM "namespaces"
WHERE "namespaces"."type" = 'Group' AND (traversal_ids @> ('{#{namespace_id}}'))
)
AND (fork_networks.root_project_id != projects.id)
GROUP BY "projects"."visibility_level"
SQL
end
end
end
end

View File

@ -16,7 +16,7 @@ module Gitlab
end
unless table_exists?(partitioned_table)
logger.warn "exiting backfill migration because partitioned table #{partitioned_table} does not exist. " \
logger.warn "exiting backfill migration because partitioned table '#{partitioned_table}' does not exist. " \
"This could be due to the migration being rolled back after migration jobs were enqueued in sidekiq"
return
end

View File

@ -27,6 +27,15 @@ module Gitlab
SQL
end
def copy_relation(relation)
connection.execute(<<~SQL)
INSERT INTO #{destination_table} (#{column_listing})
#{relation.select(column_listing).to_sql}
FOR UPDATE
ON CONFLICT (#{conflict_targets}) DO NOTHING
SQL
end
private
def column_listing

View File

@ -221,7 +221,7 @@ module Gitlab
#
# enqueue_partitioning_data_migration :audit_events
#
def enqueue_partitioning_data_migration(table_name)
def enqueue_partitioning_data_migration(table_name, migration = MIGRATION)
assert_table_is_allowed(table_name)
assert_not_in_transaction_block(scope: ERROR_SCOPE)
@ -230,7 +230,7 @@ module Gitlab
primary_key = connection.primary_key(table_name)
queue_batched_background_migration(
MIGRATION,
migration,
table_name,
primary_key,
partitioned_table_name,
@ -249,13 +249,13 @@ module Gitlab
#
# cleanup_partitioning_data_migration :audit_events
#
def cleanup_partitioning_data_migration(table_name)
def cleanup_partitioning_data_migration(table_name, migration = MIGRATION)
assert_table_is_allowed(table_name)
partitioned_table_name = make_partitioned_table_name(table_name)
primary_key = connection.primary_key(table_name)
delete_batched_background_migration(MIGRATION, table_name, primary_key, [partitioned_table_name])
delete_batched_background_migration(migration, table_name, primary_key, [partitioned_table_name])
end
def create_hash_partitions(table_name, number_of_partitions)

View File

@ -13,6 +13,11 @@ module Gitlab
'pipelines_graph',
'continuous_integration'
],
[
%r{\Aprojects/.+/.+/jobs\z},
'jobs_table',
'continuous_integration'
],
[
%r(\Apipelines/sha/\w{#{Gitlab::Git::Commit::MIN_SHA_LENGTH},#{Gitlab::Git::Commit::MAX_SHA_LENGTH}}\z)o,
'ci_editor',

View File

@ -45,29 +45,29 @@ module Gitlab
'bg' => 0,
'cs_CZ' => 0,
'da_DK' => 21,
'de' => 90,
'de' => 95,
'en' => 100,
'eo' => 0,
'es' => 25,
'es' => 33,
'fil_PH' => 0,
'fr' => 97,
'fr' => 95,
'gl_ES' => 0,
'id_ID' => 0,
'it' => 79,
'ja' => 91,
'ko' => 18,
'it' => 83,
'ja' => 92,
'ko' => 20,
'nb_NO' => 15,
'nl_NL' => 0,
'pl_PL' => 2,
'pt_BR' => 93,
'pt_BR' => 90,
'ro_RO' => 53,
'ru' => 16,
'si_LK' => 10,
'tr_TR' => 6,
'uk' => 40,
'zh_CN' => 96,
'zh_CN' => 94,
'zh_HK' => 1,
'zh_TW' => 93
'zh_TW' => 89
}.freeze
private_constant :TRANSLATION_LEVELS

View File

@ -38814,6 +38814,9 @@ msgstr ""
msgid "PackageRegistry|%{linkStart}Wildcards%{linkEnd} such as `my-package-*` are supported."
msgstr ""
msgid "PackageRegistry|%{message}. Delete this package and try again."
msgstr ""
msgid "PackageRegistry|%{name} version %{version} was first created %{datetime}"
msgstr ""
@ -39332,9 +39335,6 @@ msgstr ""
msgid "PackageRegistry|There was a problem fetching the details for this package."
msgstr ""
msgid "PackageRegistry|There was a timeout and the package was not published. Delete this package and try again."
msgstr ""
msgid "PackageRegistry|There was an error publishing %{count} packages"
msgstr ""
@ -48371,6 +48371,9 @@ msgstr ""
msgid "ScanResultPolicy|Attributes are automatically applied by the scanners"
msgstr ""
msgid "ScanResultPolicy|Automatically make approval rules optional when scan artifacts are missing from the target branch and a scan is required by a scan execution policy. This option only works with an existing scan execution policy that has matching scanners."
msgstr ""
msgid "ScanResultPolicy|Block the merge request until all criteria are met"
msgstr ""
@ -48386,6 +48389,9 @@ msgstr ""
msgid "ScanResultPolicy|Don't show me this again"
msgstr ""
msgid "ScanResultPolicy|Edge case settings"
msgstr ""
msgid "ScanResultPolicy|Except"
msgstr ""
@ -48404,7 +48410,10 @@ msgstr ""
msgid "ScanResultPolicy|Failure cases:"
msgstr ""
msgid "ScanResultPolicy|Fallback behavior in case of policy failure"
msgid "ScanResultPolicy|Fallback behavior"
msgstr ""
msgid "ScanResultPolicy|Fallback behavior and edge case settings"
msgstr ""
msgid "ScanResultPolicy|False positive"
@ -48437,6 +48446,9 @@ msgstr ""
msgid "ScanResultPolicy|License scanning allows only one criteria: Status"
msgstr ""
msgid "ScanResultPolicy|Make approval rules optional using scan execution policies"
msgstr ""
msgid "ScanResultPolicy|Matching"
msgstr ""
@ -49765,9 +49777,6 @@ msgstr ""
msgid "SecurityOrchestration|Every time a pipeline runs for %{branches}%{branchExceptionsString}"
msgstr ""
msgid "SecurityOrchestration|Ex: top_level_group"
msgstr ""
msgid "SecurityOrchestration|Exception branches"
msgstr ""
@ -50157,6 +50166,9 @@ msgstr ""
msgid "SecurityOrchestration|Show only linked security policy projects"
msgstr ""
msgid "SecurityOrchestration|Something went wrong, unable to fetch groups"
msgstr ""
msgid "SecurityOrchestration|Something went wrong, unable to fetch policies"
msgstr ""
@ -65297,6 +65309,9 @@ msgstr ""
msgid "does not match dast_site_validation.project"
msgstr ""
msgid "does not support select options."
msgstr ""
msgid "download it"
msgstr ""
@ -65377,6 +65392,9 @@ msgstr ""
msgid "exceeds the limit of %{count} links"
msgstr ""
msgid "exceeds the limit of %{count}."
msgstr ""
msgid "expired on %{timebox_due_date}"
msgstr ""

View File

@ -140,7 +140,7 @@
"colord": "^2.9.3",
"compression-webpack-plugin": "^5.0.2",
"copy-webpack-plugin": "^6.4.1",
"core-js": "^3.38.1",
"core-js": "^3.39.0",
"cron-validator": "^1.1.1",
"cronstrue": "^1.122.0",
"cropperjs": "^1.6.1",

View File

@ -264,7 +264,7 @@ GEM
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
retriable (3.1.2)
rexml (3.3.8)
rexml (3.3.9)
rotp (6.3.0)
rspec (3.13.0)
rspec-core (~> 3.13.0)

View File

@ -23,112 +23,187 @@ module QA
return @admin_api_client if @admin_api_client
info("Creating admin api client for api fabrications")
@admin_api_client = create_api_client(
token: Env.admin_personal_access_token,
user_proc: -> { admin_user },
default_token: DEFAULT_ADMIN_API_TOKEN,
check_admin: true)
if Env.admin_personal_access_token
info("Admin api token variable is set, using it for default admin api fabrications")
@admin_api_client = API::Client
.new(personal_access_token: Env.admin_personal_access_token)
.tap { |client| validate_admin_client!(client) }
elsif default_admin_token_valid?
info("Admin api token variable is not set, using default - '#{DEFAULT_ADMIN_API_TOKEN}'")
@admin_api_client = API::Client.new(personal_access_token: DEFAULT_ADMIN_API_TOKEN)
else
@admin_api_client = create_admin_api_client(admin_user)
end
info("Admin token set up successfully")
info("Global admin api client set up successfully")
@admin_api_client
end
alias_method :initialize_admin_api_client, :admin_api_client
# TODO: Implement unique user and user api client fabrication for every spec when running on non live envs
# Global test user api client
# This api client is used as a primary one for resource fabrication that do not require admin priviledges
#
# @return [QA::Runtime::API::Client]
def user_api_client
return @user_api_client if defined?(@user_api_client)
info("Creating api client for runtime user")
@user_api_client = create_api_client(token: Env.personal_access_token, user_proc: -> { runtime_user })
info("Runtime user api client set up successfully")
@user_api_client
rescue StandardError => e
# consider runtime user api client optional and set to nil if not setup
warn("Failed to create runtime user api client: #{e.message}")
@user_api_client = nil
end
alias_method :initialize_user_api_client, :user_api_client
# Global admin user
#
# @return [QA::Resource::User]
def admin_user
return @admin_user if @admin_user
@admin_user = Resource::User.init do |user|
user.username = if Env.admin_username
Env.admin_username
else
debug("Admin username variable not set, using default - '#{DEFAULT_ADMIN_USERNAME}'")
DEFAULT_ADMIN_USERNAME
end
user.password = if Env.admin_password
Env.admin_password
else
debug("Admin password variable not set, using default - '#{DEFAULT_ADMIN_PASSWORD}'")
DEFAULT_ADMIN_PASSWORD
end
end
if @admin_api_client && client_belongs_to_user?(@admin_api_client, @admin_user)
@admin_user.api_client = @admin_api_client
@admin_user.reload!
elsif @admin_api_client
warn(<<~WARN)
Configured global admin token does not belong to configured admin user
Please check values for GITLAB_QA_ADMIN_ACCESS_TOKEN, GITLAB_ADMIN_USERNAME and GITLAB_ADMIN_PASSWORD variables
WARN
end
@admin_user
@admin_user = create_user(username: Env.admin_username, password: Env.admin_password,
default_username: DEFAULT_ADMIN_USERNAME, default_password: DEFAULT_ADMIN_PASSWORD,
api_client: @admin_api_client)
end
alias_method :initialize_admin_user, :admin_user
# Global test user
# This user is used as a primary one for test execution
#
# @return [QA::Resource::User]
def runtime_user
return @runtime_user if defined?(@runtime_user)
@runtime_user = create_user(username: Env.user_username, password: Env.user_password,
api_client: @user_api_client)
rescue StandardError => e
# consider runtime user optional and set to nil if not setup
warn("Failed to create runtime user: #{e.message}")
@user_api_client = nil
end
alias_method :initialize_runtime_user, :runtime_user
private
delegate :debug, :info, :warn, :error, to: Logger
# Check if default admin token is present in environment and valid
# Create api client with provided token with fallback to UI creation of token
#
# @param [String] token
# @param [Proc] user_proc
# @param [String] default_token
# @return [QA::Runtime::API::Client]
def create_api_client(token:, user_proc:, default_token: nil, check_admin: false)
if token
info("Global api token variable is set, using it for api client setup")
API::Client
.new(personal_access_token: token)
.tap { |client| validate_api_client!(client, check_admin: check_admin) }
elsif token_valid?(default_token, check_admin: check_admin)
info("Api token variable is not set, using default - '#{default_token}'")
API::Client.new(personal_access_token: default_token)
else
# pass user through proc so it's lazily initialized only when fabricating token via UI
user = user_proc.call
create_api_token_via_ui!(user)
end
end
# Initialize new user
#
# @param [String] username
# @param [String] password
# @param [String] default_username
# @param [String] default_password
# @param [QA::Runtime::API::Client] api_client
# @return [QA::Resource::User]
def create_user(username:, password:, default_username: nil, default_password: nil, api_client: nil)
return if (username.nil? && default_username.nil?) || (password.nil? && default_password.nil?)
user = Resource::User.init do |user|
user.username = if username
username
else
debug("Username variable not set, using default - '#{default_username}'")
default_username
end
user.password = if password
password
else
debug("Password variable not set, using default - '#{default_password}'")
default_password
end
end
if api_client && client_belongs_to_user?(api_client, user)
user.api_client = api_client
user.reload!
elsif api_client
warn(<<~WARN)
Configured global api client does not belong to configured global user
Please check values for user authentication related variables
WARN
end
user
end
# Check if provided token is valid?
#
# @param [String] token
# @param [Boolean] check_admin
# @return [Boolean]
def default_admin_token_valid?
debug("Validating presence of default admin api token in environment")
validate_admin_client!(API::Client.new(personal_access_token: DEFAULT_ADMIN_API_TOKEN))
debug("Default admin token is present in environment and is valid")
def token_valid?(token, check_admin:)
return unless token
debug("Validating if api token is valid")
validate_api_client!(API::Client.new(personal_access_token: token), check_admin: check_admin)
debug("Api token is valid")
true
rescue InvalidTokenError
debug("Default admin token is not valid or present in environment, skipping...")
debug("Api token is not valid, skipping...")
false
end
# Create admin access client and validate it
# Create api token via UI for provided user
# Update user api_client to use fabricated token
#
# @param [QA::Resource::User] user
# @return [QA::Runtime::API::Client]
def create_admin_api_client(user)
info("Creating admin token via ui")
admin_token = Flow::Login.while_signed_in(as: user) do
Resource::PersonalAccessToken.fabricate_via_browser_ui! { |pat| pat.user = user }.token
def create_api_token_via_ui!(user)
info("Creating personal access token via UI for user #{user.username}")
pat = Flow::Login.while_signed_in(as: user) do
Resource::PersonalAccessToken.fabricate_via_browser_ui! { |pat| pat.user = user }
end
API::Client.new(:gitlab, personal_access_token: admin_token).tap do |client|
validate_admin_client!(client)
user.api_client = client
user.reload!
end
api_client = Runtime::API::Client.new(personal_access_token: pat.token)
user.api_client = api_client
user.reload!
api_client
end
# Validate if client belongs to an admin user
#
# @param [QA::Runtime::API::Client] client
# @return [void]
def validate_admin_client!(client)
debug("Validating admin access token")
def validate_api_client!(client, check_admin: true)
debug("Validating api client")
resp = fetch_user_details(client)
if resp.code == 403 && resp.body.include?("Your password expired")
raise ExpiredAdminPasswordError, "Admin password has expired and must be reset"
raise ExpiredAdminPasswordError, "Password for client's user has expired and must be reset"
elsif !status_ok?(resp)
raise InvalidTokenError, "Admin token validation failed! Code: #{resp.code}, Err: '#{resp.body}'"
raise InvalidTokenError, "API client validation failed! Code: #{resp.code}, Err: '#{resp.body}'"
end
is_admin = Support::API.parse_body(resp)[:is_admin]
raise InvalidTokenError, "Admin token does not belong to admin user" unless is_admin
if check_admin
is_admin = Support::API.parse_body(resp)[:is_admin]
raise InvalidTokenError, "Admin token does not belong to admin user" unless is_admin
end
debug("Admin token is valid")
debug("API client is valid")
end
# Check if token belongs to specific user
@ -139,7 +214,7 @@ module QA
def client_belongs_to_user?(client, user)
resp = fetch_user_details(client)
unless status_ok?(resp)
raise InvalidTokenError, "Token validation failed! Code: #{resp.code}, Err: '#{resp.body}'"
raise InvalidTokenError, "API client validation failed! Code: #{resp.code}, Err: '#{resp.body}'"
end
Support::API.parse_body(resp)[:username] == user.username

View File

@ -12,14 +12,17 @@ module QA
warn: nil,
error: nil
})
allow(Runtime::Env).to receive_messages({
admin_username: nil,
admin_password: nil,
admin_personal_access_token: nil
})
described_class.instance_variable_set(:@admin_api_client, nil)
described_class.instance_variable_set(:@admin_user, nil)
if described_class.instance_variable_defined?(:@user_api_client)
described_class.send(:remove_instance_variable, :@user_api_client)
end
if described_class.instance_variable_defined?(:@runtime_user)
described_class.send(:remove_instance_variable, :@runtime_user)
end
end
def mock_user_get(token:, code: 200, body: { is_admin: true, id: 1, username: "root" }.to_json)
@ -33,6 +36,11 @@ module QA
before do
allow(Runtime::Env).to receive(:admin_personal_access_token).and_return(admin_token)
allow(Runtime::Env).to receive_messages({
admin_username: nil,
admin_password: nil,
admin_personal_access_token: admin_token
})
end
context "when admin token variable is set" do
@ -66,7 +74,7 @@ module QA
it "raises InvalidTokenError" do
expect { described_class.admin_api_client }.to raise_error(
described_class::InvalidTokenError, "Admin token validation failed! Code: 401, Err: '401 Unauthorized'"
described_class::InvalidTokenError, "API client validation failed! Code: 401, Err: '401 Unauthorized'"
)
end
end
@ -80,35 +88,43 @@ module QA
it "raises ExpiredAdminPasswordError" do
expect { described_class.admin_api_client }.to raise_error(
described_class::ExpiredAdminPasswordError, "Admin password has expired and must be reset"
described_class::ExpiredAdminPasswordError, "Password for client's user has expired and must be reset"
)
end
end
context "with token creation via UI" do
let(:admin_user) { Resource::User.new }
let(:pat) { Resource::PersonalAccessToken.init { |pat| pat.token = "test" } }
let(:token) { "token" }
# dummy objects are created with populated id fields to simulate proper fabrication and reload calls
let(:admin_user) { Resource::User.init { |u| u.id = 1 } }
let(:pat) { Resource::PersonalAccessToken.init { |p| p.token = token } }
before do
allow(Flow::Login).to receive(:while_signed_in).with(as: admin_user).and_yield
allow(Resource::User).to receive(:init).and_yield(admin_user).and_return(admin_user)
allow(Resource::PersonalAccessToken).to receive(:fabricate_via_browser_ui!).and_yield(pat).and_return(pat)
allow(Flow::Login).to receive(:while_signed_in).with(as: admin_user).and_yield
allow(admin_user).to receive(:reload!)
mock_user_get(token: default_admin_token, code: 401)
mock_user_get(token: pat.token)
end
it "creates admin api client with token created from UI" do
expect(described_class.admin_api_client.personal_access_token).to eq(pat.token)
expect(admin_user.username).to eq("root")
expect(admin_user.password).to eq("5iveL!fe")
expect(described_class.admin_api_client.personal_access_token).to eq(token)
expect(admin_user).to have_received(:reload!)
end
end
end
describe "#admin_user" do
before do
allow(Runtime::Env).to receive_messages({
admin_username: nil,
admin_password: nil,
admin_personal_access_token: nil
})
end
context "when admin client has not been initialized" do
context "with admin user variables set" do
let(:username) { "admin-username" }
@ -166,8 +182,8 @@ module QA
described_class.initialize_admin_user
expect(Runtime::Logger).to have_received(:warn).with(<<~WARN)
Configured global admin token does not belong to configured admin user
Please check values for GITLAB_QA_ADMIN_ACCESS_TOKEN, GITLAB_ADMIN_USERNAME and GITLAB_ADMIN_PASSWORD variables
Configured global api client does not belong to configured global user
Please check values for user authentication related variables
WARN
end
end
@ -179,7 +195,182 @@ module QA
it "raises invalid token error" do
expect { described_class.admin_user }.to raise_error(
described_class::InvalidTokenError, "Token validation failed! Code: 403, Err: 'Unauthorized'"
described_class::InvalidTokenError, "API client validation failed! Code: 403, Err: 'Unauthorized'"
)
end
end
end
end
describe "#user_api_client" do
subject(:user_api_client) { described_class.user_api_client }
let(:username) { "username" }
let(:password) { "password" }
let(:api_token) { "token" }
before do
allow(Runtime::Env).to receive_messages({
user_username: username,
user_password: password,
personal_access_token: api_token
})
end
context "when api token variable is set" do
before do
mock_user_get(token: api_token)
end
it "creates admin api client with configured token" do
expect(user_api_client.personal_access_token).to eq(api_token)
end
end
context "when api token variable and user variables are not set" do
let(:api_token) { nil }
let(:username) { nil }
let(:password) { nil }
it "does not return api client" do
expect(user_api_client).to be_nil
end
end
context "with invalid token set via environment variable" do
before do
mock_user_get(token: api_token, code: 401, body: "401 Unauthorized")
end
it "does not return api client" do
expect(user_api_client).to be_nil
end
end
context "with expired admin password" do
before do
mock_user_get(token: api_token, code: 403, body: "Your password expired")
end
it "does not return api client" do
expect(user_api_client).to be_nil
end
end
context "with token creation via UI" do
let(:api_token) { nil }
# dummy objects are created with populated id fields to simulate proper fabrication and reload calls
let(:user_spy) { Resource::User.init { |u| u.id = 1 } }
let(:pat) { Resource::PersonalAccessToken.init { |p| p.token = "token" } }
before do
allow(Flow::Login).to receive(:while_signed_in).with(as: user_spy).and_yield
allow(Resource::User).to receive(:init).and_yield(user_spy).and_return(user_spy)
allow(Resource::PersonalAccessToken).to receive(:fabricate_via_browser_ui!).and_yield(pat).and_return(pat)
allow(user_spy).to receive(:reload!)
end
it "creates user api client with token created from UI" do
expect(user_api_client.personal_access_token).to eq(pat.token)
expect(user_spy).to have_received(:reload!)
end
end
end
describe "#runtime_user" do
subject(:runtime_user) { described_class.runtime_user }
let(:username) { "username" }
let(:password) { "password" }
before do
allow(Runtime::Env).to receive_messages({
user_username: username,
user_password: password,
personal_access_token: nil
})
end
context "when api client has not been initialized" do
context "with user variables set" do
it "returns user with configured credentials" do
expect(runtime_user.username).to eq(username)
expect(runtime_user.password).to eq(password)
end
end
context "without user variables set" do
let(:username) { nil }
let(:password) { nil }
it "does not return runtime user" do
expect(runtime_user).to be_nil
end
end
context "with only username set" do
let(:password) { nil }
it "does not return runtime user" do
expect(runtime_user).to be_nil
end
end
context "with only password set" do
let(:username) { nil }
it "does not return runtime user" do
expect(runtime_user).to be_nil
end
end
end
context "when api client has been initialized" do
let(:user_spy) { Resource::User.new }
let(:api_client) { Runtime::API::Client.new(personal_access_token: "token") }
before do
allow(Resource::User).to receive(:init).and_yield(user_spy).and_return(user_spy)
allow(user_spy).to receive(:reload!)
described_class.instance_variable_set(:@user_api_client, api_client)
end
context "with valid client belonging to user" do
before do
mock_user_get(token: api_client.personal_access_token, body: { username: username }.to_json)
end
it "sets api client on user and reloads it" do
expect(runtime_user.instance_variable_get(:@api_client)).to eq(api_client)
expect(runtime_user).to have_received(:reload!)
end
end
context "with valid client not belonging to user" do
before do
mock_user_get(token: api_client.personal_access_token, body: { username: "test" }.to_json)
end
it "prints warning message" do
described_class.initialize_runtime_user
expect(Runtime::Logger).to have_received(:warn).with(<<~WARN)
Configured global api client does not belong to configured global user
Please check values for user authentication related variables
WARN
end
end
context "with invalid api client" do
before do
mock_user_get(token: api_client.personal_access_token, code: 403, body: "Unauthorized")
end
it "raises invalid token error" do
expect(runtime_user).to be_nil
expect(Runtime::Logger).to have_received(:warn).with(
"Failed to create runtime user: API client validation failed! Code: 403, Err: 'Unauthorized'"
)
end
end

View File

@ -18,22 +18,32 @@ RSpec.describe 'Merge Requests > User filters by milestones', :js, feature_categ
end
it 'filters by no milestone' do
select_tokens 'Milestone', 'None', submit: true
select_tokens 'Milestone', '=', 'None', submit: true
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_css('.merge-request', count: 1)
end
it 'filters by a specific milestone' do
select_tokens 'Milestone', milestone.title, submit: true
select_tokens 'Milestone', '=', milestone.title, submit: true
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_css('.merge-request', count: 1)
end
it 'filters out a specific milestone' do
select_tokens 'Milestone', '!=', milestone.title, submit: true
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_css('.merge-request', count: 1)
page.within('.issuable-list') do
expect(page).not_to have_text(milestone.title)
end
end
describe 'filters by upcoming milestone' do
it 'does not show merge requests with no expiry' do
select_tokens 'Milestone', 'Upcoming', submit: true
select_tokens 'Milestone', '=', 'Upcoming', submit: true
expect(page).to have_issuable_counts(open: 0, closed: 0, all: 0)
expect(page).to have_css('.merge-request', count: 0)
@ -43,7 +53,7 @@ RSpec.describe 'Merge Requests > User filters by milestones', :js, feature_categ
let(:milestone) { create(:milestone, project: project, due_date: Date.tomorrow) }
it 'shows merge requests' do
select_tokens 'Milestone', 'Upcoming', submit: true
select_tokens 'Milestone', '=', 'Upcoming', submit: true
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_css('.merge-request', count: 1)
@ -54,7 +64,7 @@ RSpec.describe 'Merge Requests > User filters by milestones', :js, feature_categ
let(:milestone) { create(:milestone, project: project, due_date: Date.yesterday) }
it 'does not show any merge requests' do
select_tokens 'Milestone', 'Upcoming', submit: true
select_tokens 'Milestone', '=', 'Upcoming', submit: true
expect(page).to have_issuable_counts(open: 0, closed: 0, all: 0)
expect(page).to have_css('.merge-request', count: 0)

View File

@ -30,7 +30,7 @@ RSpec.describe 'Merge requests > User filters by multiple criteria', :js, featur
describe 'filtering by text, author, assignee, milestone, and label' do
it 'filters by text, author, assignee, milestone, and label' do
select_tokens 'Author', '=', user.username, 'Assignee', '=', user.username, 'Milestone', milestone.title, 'Label', '=', wontfix.title
select_tokens 'Author', '=', user.username, 'Assignee', '=', user.username, 'Milestone', '=', milestone.title, 'Label', '=', wontfix.title
send_keys 'Bug', :enter, :enter
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)

Some files were not shown because too many files have changed in this diff Show More