Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-02-28 18:09:29 +00:00
parent 2b669a60cb
commit 76747b143e
130 changed files with 1941 additions and 900 deletions

3
.gitignore vendored
View File

@ -126,3 +126,6 @@ config/helpers/tailwind/css_in_js.js
# ruby-lsp
.index.yml
# Rubocop cop documentation generation
.yardoc

View File

@ -16,6 +16,7 @@ pages:
- "compile-storybook"
- "update-tests-metadata"
- "generate-frontend-fixtures-mapping"
- "rubocop:docs-site"
before_script:
- apt-get update && apt-get -y install brotli gzip
script:
@ -24,6 +25,7 @@ pages:
- mkdir -p public/$(dirname "$KNAPSACK_RSPEC_SUITE_REPORT_PATH") public/$(dirname "$FLAKY_RSPEC_SUITE_REPORT_PATH") public/$(dirname "$RSPEC_PACKED_TESTS_MAPPING_PATH") public/$(dirname "$RSPEC_PACKED_TESTS_MAPPING_ALT_PATH") public/$(dirname "$FRONTEND_FIXTURES_MAPPING_PATH")
- mv coverage/ public/coverage-ruby/ || true
- mv coverage-frontend/ public/coverage-frontend/ || true
- mv rubocop/docs-hugo/public/ public/rubocop-docs/ || true
- mv storybook/public public/storybook || true
- cp .public/assets/application-*.css public/application.css || true
- mv $KNAPSACK_RSPEC_SUITE_REPORT_PATH public/$KNAPSACK_RSPEC_SUITE_REPORT_PATH || true

View File

@ -147,6 +147,24 @@ rubocop-docs:
script:
- bundle exec rubocop --only Gitlab/DocumentationLinks/Link
rubocop:docs-site:
extends:
- .static-analysis-base
- .rubocop-job-cache
allow_failure: true
script:
- apt-get update && apt-get -y install hugo
- rake rubocop:docs
- cd rubocop/docs-hugo
- hugo --minify
- cd ../../
artifacts:
name: rubocop-docs-site
expire_in: 31d
when: always
paths:
- rubocop/docs-hugo/public
rails-next-dependency-check:
stage: lint
needs: []

View File

@ -552,6 +552,8 @@ group :development, :test do
gem 'vite_ruby', '~> 3.9.0', feature_category: :shared
gem 'gitlab-housekeeper', path: 'gems/gitlab-housekeeper', feature_category: :tooling
gem 'yard', '~> 0.9', require: false, feature_category: :tooling
end
group :development, :test, :danger do
@ -746,7 +748,7 @@ gem 'net-http', '= 0.6.0', feature_category: :shared
# This is locked to 0.13.0 because the default parser changes from RFC2396 to RFC3986,
# which can be removed after Rails 7.2 upgrade
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/173142#note_2277952450
gem 'uri', '= 0.13.0', feature_category: :shared
gem 'uri', '= 0.13.2', feature_category: :shared
gem 'duo_api', '~> 1.3', feature_category: :system_access

View File

@ -771,7 +771,7 @@
{"name":"uniform_notifier","version":"1.16.0","platform":"ruby","checksum":"99b39ee4a0864e3b49f375b5e5803eb26d35ed6eb1719c96407573a87bc4dbb5"},
{"name":"unleash","version":"3.2.2","platform":"ruby","checksum":"0f6e56498de920de66a01bceffb93933693ade646bb853fc70eb16bd1026b93b"},
{"name":"unparser","version":"0.6.7","platform":"ruby","checksum":"ae42e73edfa273766e66c166368fb75ca5972cd8ec50c536253e0f6299a9dec8"},
{"name":"uri","version":"0.13.0","platform":"ruby","checksum":"26553c2a9399762e1e8bebd4444b4361c4b21298cf1c864b22eeabc9c4998f24"},
{"name":"uri","version":"0.13.2","platform":"ruby","checksum":"a557196e652011bcff0b36d29f9e427fefcf60cc35c0ab8cce08768a6287e457"},
{"name":"valid_email","version":"0.1.3","platform":"ruby","checksum":"b81452b51b64c4beb67913f68db52c20ecb4d73d45512f5b282ab4a3f4416570"},
{"name":"validate_url","version":"1.0.15","platform":"ruby","checksum":"72fe164c0713d63a9970bd6700bea948babbfbdcec392f2342b6704042f57451"},
{"name":"validates_hostname","version":"1.0.13","platform":"ruby","checksum":"eac40178cc0b4f727df9cc6a5cb5bc2550718ad8d9bb3728df9aba6354bdda19"},

View File

@ -1922,7 +1922,7 @@ GEM
unparser (0.6.7)
diff-lcs (~> 1.3)
parser (>= 3.2.0)
uri (0.13.0)
uri (0.13.2)
valid_email (0.1.3)
activemodel
mail (>= 2.6.1)
@ -2348,7 +2348,7 @@ DEPENDENCIES
undercover (~> 0.6.0)
unicode-emoji (~> 4.0)
unleash (~> 3.2.2)
uri (= 0.13.0)
uri (= 0.13.2)
valid_email (~> 0.1)
validates_hostname (~> 1.0.13)
version_sorter (~> 2.3)
@ -2362,6 +2362,7 @@ DEPENDENCIES
webrick (~> 1.8.1)
wikicloth (= 0.8.1)
yajl-ruby (~> 1.4.3)
yard (~> 0.9)
BUNDLED WITH
2.5.11

View File

@ -784,7 +784,7 @@
{"name":"uniform_notifier","version":"1.16.0","platform":"ruby","checksum":"99b39ee4a0864e3b49f375b5e5803eb26d35ed6eb1719c96407573a87bc4dbb5"},
{"name":"unleash","version":"3.2.2","platform":"ruby","checksum":"0f6e56498de920de66a01bceffb93933693ade646bb853fc70eb16bd1026b93b"},
{"name":"unparser","version":"0.6.7","platform":"ruby","checksum":"ae42e73edfa273766e66c166368fb75ca5972cd8ec50c536253e0f6299a9dec8"},
{"name":"uri","version":"0.13.0","platform":"ruby","checksum":"26553c2a9399762e1e8bebd4444b4361c4b21298cf1c864b22eeabc9c4998f24"},
{"name":"uri","version":"0.13.2","platform":"ruby","checksum":"a557196e652011bcff0b36d29f9e427fefcf60cc35c0ab8cce08768a6287e457"},
{"name":"valid_email","version":"0.1.3","platform":"ruby","checksum":"b81452b51b64c4beb67913f68db52c20ecb4d73d45512f5b282ab4a3f4416570"},
{"name":"validate_url","version":"1.0.15","platform":"ruby","checksum":"72fe164c0713d63a9970bd6700bea948babbfbdcec392f2342b6704042f57451"},
{"name":"validates_hostname","version":"1.0.13","platform":"ruby","checksum":"eac40178cc0b4f727df9cc6a5cb5bc2550718ad8d9bb3728df9aba6354bdda19"},

View File

@ -1956,7 +1956,7 @@ GEM
unparser (0.6.7)
diff-lcs (~> 1.3)
parser (>= 3.2.0)
uri (0.13.0)
uri (0.13.2)
valid_email (0.1.3)
activemodel
mail (>= 2.6.1)
@ -2383,7 +2383,7 @@ DEPENDENCIES
undercover (~> 0.6.0)
unicode-emoji (~> 4.0)
unleash (~> 3.2.2)
uri (= 0.13.0)
uri (= 0.13.2)
valid_email (~> 0.1)
validates_hostname (~> 1.0.13)
version_sorter (~> 2.3)
@ -2397,6 +2397,7 @@ DEPENDENCIES
webrick (~> 1.8.1)
wikicloth (= 0.8.1)
yajl-ruby (~> 1.4.3)
yard (~> 0.9)
BUNDLED WITH
2.5.11

View File

@ -1,4 +1,111 @@
import { GlFilteredSearchToken } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import {
OPERATORS_AFTER_BEFORE,
OPERATORS_IS,
} from '~/vue_shared/components/filtered_search_bar/constants';
import DateToken from '~/vue_shared/components/filtered_search_bar/tokens/date_token.vue';
// Token types
export const FEED_TOKEN = 'feedToken';
export const INCOMING_EMAIL_TOKEN = 'incomingEmailToken';
export const STATIC_OBJECT_TOKEN = 'staticObjectToken';
export const DEFAULT_SORT = { value: 'expires', isAsc: true };
export const SORT_OPTIONS = [
{
text: __('Name'),
value: 'name',
sort: {
asc: 'name_asc',
desc: 'name_desc',
},
},
{
text: __('Created date'),
value: 'created',
sort: {
asc: 'created_asc',
desc: 'created_desc',
},
},
{
text: __('Expiration date'),
value: 'expires',
sort: {
asc: 'expires_at_asc_id_desc',
},
},
];
export const TOKENS = [
{
icon: 'key',
title: s__('CredentialsInventory|Type'),
type: 'filter',
token: GlFilteredSearchToken,
operators: OPERATORS_IS,
unique: true,
options: [
{
value: 'personal_access_tokens',
title: s__('CredentialsInventory|Personal access tokens'),
},
{ value: 'ssh_keys', title: s__('CredentialsInventory|SSH keys') },
{
value: 'resource_access_tokens',
title: s__('CredentialsInventory|Project and group access tokens'),
},
{ value: 'gpg_keys', title: s__('CredentialsInventory|GPG keys') },
],
},
{
icon: 'status',
title: s__('CredentialsInventory|State'),
type: 'state',
token: GlFilteredSearchToken,
operators: OPERATORS_IS,
unique: true,
options: [
{ value: 'active', title: s__('CredentialsInventory|Active') },
{ value: 'inactive', title: s__('CredentialsInventory|Inactive') },
],
},
{
icon: 'remove',
title: s__('CredentialsInventory|Revoked'),
type: 'revoked',
token: GlFilteredSearchToken,
operators: OPERATORS_IS,
unique: true,
options: [
{ value: 'true', title: __('Yes') },
{ value: 'false', title: __('No') },
],
},
{
icon: 'history',
title: s__('CredentialsInventory|Created date'),
type: 'created',
token: DateToken,
operators: OPERATORS_AFTER_BEFORE,
unique: true,
},
{
icon: 'history',
title: s__('CredentialsInventory|Expiration date'),
type: 'expires',
token: DateToken,
operators: OPERATORS_AFTER_BEFORE,
unique: true,
},
{
icon: 'history',
title: s__('CredentialsInventory|Last used date'),
type: 'last_used',
token: DateToken,
operators: OPERATORS_AFTER_BEFORE,
unique: true,
},
];

View File

@ -1,8 +1,7 @@
<script>
import { GlFilteredSearch, GlSorting } from '@gitlab/ui';
import { setUrlParams, visitUrl } from '~/lib/utils/url_utility';
import { TOKENS, SORT_OPTIONS } from '../constants';
import { initializeValuesFromQuery, buildSortedUrl } from '../utils';
import { SORT_OPTIONS, TOKENS } from '~/access_tokens/constants';
import { initializeValuesFromQuery, goTo } from '../utils';
export default {
components: {
@ -10,10 +9,10 @@ export default {
GlSorting,
},
data() {
const { tokens, sorting } = initializeValuesFromQuery();
const { sorting, tokens } = initializeValuesFromQuery();
return {
tokens,
sorting,
tokens,
};
},
computed: {
@ -32,34 +31,22 @@ export default {
},
},
methods: {
change() {
change(tokens) {
this.tokens = tokens;
// Once SSH or GPG key is selected, discard the rest of the tokens
if (this.hasKey) {
this.tokens = this.tokens.filter(({ type }) => type === 'filter');
}
},
search(tokens) {
const newParams = {};
tokens?.forEach((token) => {
if (typeof token === 'string') {
newParams.search = token;
} else if (['created', 'expires', 'last_used'].includes(token.type)) {
const isBefore = token.value.operator === '<';
const key = `${token.type}${isBefore ? '_before' : '_after'}`;
newParams[key] = token.value.data;
} else {
newParams[token.type] = token.value.data;
}
});
const newUrl = setUrlParams(newParams, window.location.href, true);
visitUrl(newUrl);
goTo(this.sorting.value, this.sorting.isAsc, tokens);
},
handleSortChange(value) {
visitUrl(buildSortedUrl(value, this.sorting.isAsc));
handleSortChange(sortValue) {
goTo(sortValue, this.sorting.isAsc, this.tokens);
},
handleSortDirectionChange(isAsc) {
visitUrl(buildSortedUrl(this.sorting.value, isAsc));
handleSortDirectionChange(sortIsAsc) {
goTo(this.sorting.value, sortIsAsc, this.tokens);
},
},
SORT_OPTIONS,
@ -69,7 +56,7 @@ export default {
<template>
<div class="gl-flex gl-flex-col gl-gap-3 md:gl-flex-row">
<gl-filtered-search
v-model="tokens"
:value="tokens"
:placeholder="s__('CredentialsInventory|Search or filter credentials...')"
:available-tokens="availableTokens"
terms-as-tokens

View File

@ -1,110 +0,0 @@
import { GlFilteredSearchToken } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import {
OPERATORS_AFTER_BEFORE,
OPERATORS_IS,
} from '~/vue_shared/components/filtered_search_bar/constants';
import DateToken from '~/vue_shared/components/filtered_search_bar/tokens/date_token.vue';
export const SORT_KEY_NAME = 'name';
export const SORT_KEY_CREATED = 'created';
export const SORT_KEY_EXPIRES = 'expires';
export const TOKENS = [
{
icon: 'key',
title: s__('CredentialsInventory|Type'),
type: 'filter',
token: GlFilteredSearchToken,
operators: OPERATORS_IS,
unique: true,
options: [
{
value: 'personal_access_tokens',
title: s__('CredentialsInventory|Personal access tokens'),
},
{ value: 'ssh_keys', title: s__('CredentialsInventory|SSH keys') },
{
value: 'resource_access_tokens',
title: s__('CredentialsInventory|Project and group access tokens'),
},
{ value: 'gpg_keys', title: s__('CredentialsInventory|GPG keys') },
],
},
{
icon: 'status',
title: s__('CredentialsInventory|State'),
type: 'state',
token: GlFilteredSearchToken,
operators: OPERATORS_IS,
unique: true,
options: [
{ value: 'active', title: s__('CredentialsInventory|Active') },
{ value: 'inactive', title: s__('CredentialsInventory|Inactive') },
],
},
{
icon: 'remove',
title: s__('CredentialsInventory|Revoked'),
type: 'revoked',
token: GlFilteredSearchToken,
operators: OPERATORS_IS,
unique: true,
options: [{ value: 'true', title: __('Yes') }],
},
{
icon: 'history',
title: s__('CredentialsInventory|Created date'),
type: 'created',
token: DateToken,
operators: OPERATORS_AFTER_BEFORE,
unique: true,
},
{
icon: 'history',
title: s__('CredentialsInventory|Expiration date'),
type: 'expires',
token: DateToken,
operators: OPERATORS_AFTER_BEFORE,
unique: true,
},
{
icon: 'history',
title: s__('CredentialsInventory|Last used date'),
type: 'last_used',
token: DateToken,
operators: OPERATORS_AFTER_BEFORE,
unique: true,
},
];
export const SORT_OPTIONS = [
{
text: __('Name'),
value: SORT_KEY_NAME,
sort: {
asc: 'name_asc',
desc: 'name_desc',
},
},
{
text: __('Created date'),
value: SORT_KEY_CREATED,
sort: {
asc: 'created_asc',
desc: 'created_desc',
},
},
{
text: __('Expiration date'),
value: SORT_KEY_EXPIRES,
sort: {
asc: 'expires_at_asc_id_desc',
},
},
];
export const DEFAULT_SORT = {
value: SORT_KEY_EXPIRES,
isAsc: true,
};

View File

@ -1,27 +1,18 @@
import { queryToObject, setUrlParams } from '~/lib/utils/url_utility';
import { queryToObject, setUrlParams, visitUrl } from '~/lib/utils/url_utility';
import {
OPERATORS_BEFORE,
OPERATORS_AFTER,
} from '~/vue_shared/components/filtered_search_bar/constants';
import { TOKENS, SORT_OPTIONS, DEFAULT_SORT } from './constants';
import { DEFAULT_SORT, SORT_OPTIONS, TOKENS } from '~/access_tokens/constants';
/**
* @typedef {{type: string, value: {data: string, operator: string}}} Token
* @param {Object<string, string>} filters
* @param {string} [search]
*/
function initializeFilters(filters, search) {
const tokens = [];
/**
* Initialize token values based on the URL parameters
* @param {string} query - document.location.search
*
* @returns {Array<string|Token>}
*/
export function initializeValuesFromQuery(query = document.location.search) {
const tokens = /** @type {Array<string|Token>} */ ([]);
const sorting = DEFAULT_SORT;
const { search, sort, ...terms } = queryToObject(query);
for (const [key, value] of Object.entries(terms)) {
for (const [key, value] of Object.entries(filters)) {
const isBefore = key.endsWith('_before');
const isAfter = key.endsWith('_after');
@ -55,18 +46,60 @@ export function initializeValuesFromQuery(query = document.location.search) {
tokens.push(search);
}
return tokens;
}
/**
* @param {string} [sort]
*/
function initializeSort(sort) {
let sorting = DEFAULT_SORT;
const sortOption = SORT_OPTIONS.find((item) => [item.sort.desc, item.sort.asc].includes(sort));
if (sort && sortOption) {
sorting.value = sortOption.value;
sorting.isAsc = sortOption.sort.asc === sort;
sorting = {
value: sortOption.value,
isAsc: sortOption.sort.asc === sort,
};
}
return { tokens, sorting };
return sorting;
}
export function buildSortedUrl(value, isAsc) {
const sortedOption = SORT_OPTIONS.find((sortOption) => sortOption.value === value);
const sort = isAsc ? sortedOption.sort.asc : sortedOption.sort.desc;
const newUrl = setUrlParams({ sort });
return newUrl;
/**
* Initialize tokens and sort based on the URL parameters
* @param {string} query - document.location.search
*/
export function initializeValuesFromQuery(query = document.location.search) {
const { sort, search, ...filters } = queryToObject(query);
const sorting = initializeSort(sort);
const tokens = initializeFilters(filters, search);
return { sorting, tokens };
}
/**
* @param {string} sortValue
* @param {boolean} sortIsAsc
* @param {Array<string|{type: string, value:{data: string, operator: string}}>} tokens
*/
export function goTo(sortValue, sortIsAsc, tokens) {
const newParams = { page: 1 };
tokens?.forEach((token) => {
if (typeof token === 'string') {
newParams.search = token;
} else if (['created', 'expires', 'last_used'].includes(token.type)) {
const isBefore = token.value.operator === '<';
const key = `${token.type}${isBefore ? '_before' : '_after'}`;
newParams[key] = token.value.data;
} else {
newParams[token.type] = token.value.data;
}
});
const sortOption = SORT_OPTIONS.find((item) => item.value === sortValue).sort;
newParams.sort = sortIsAsc ? sortOption.asc : sortOption.desc;
const newUrl = setUrlParams(newParams, window.location.href, true);
visitUrl(newUrl);
}

View File

@ -124,7 +124,7 @@ export default {
const truncatedOldSha = escape(truncateSha(this.diffFile.submodule_compare.old_sha));
const truncatedNewSha = escape(truncateSha(this.diffFile.submodule_compare.new_sha));
return sprintf(
__('Compare %{oldCommitId}...%{newCommitId}'),
__('Compare %{oldCommitId}%{newCommitId}'),
{
oldCommitId: `<span class="commit-sha">${truncatedOldSha}</span>`,
newCommitId: `<span class="commit-sha">${truncatedNewSha}</span>`,

View File

@ -12,7 +12,7 @@ export const I18N_CONFIRM_TITLE = s__(
export const I18N_UPDATE_ERROR_MESSAGE = __('An error occurred while updating configuration.');
export const I18N_REFRESH_MESSAGE = __('Refresh the page and try again.');
export const I18N_PENDING_MESSAGE = s__('GroupSettings|Saving...');
export const I18N_PENDING_MESSAGE = s__('GroupSettings|Saving');
export const I18N_SUCCESS_MESSAGE = s__('GroupSettings|Change saved.');
export const I18N_ERROR_MESSAGE = s__('GroupSettings|Failed to save changes.');
export const I18N_RETRY_ACTION_TEXT = s__('GroupSettings|Retry');

View File

@ -54,7 +54,7 @@ export default {
validFeedback: s__('Groups|Group path is available.'),
},
},
apiLoadingMessage: s__('Groups|Checking group URL availability...'),
apiLoadingMessage: s__('Groups|Checking group URL availability'),
apiErrorMessage: __(
'An error occurred while checking group path. Please refresh and try again.',
),

View File

@ -124,9 +124,7 @@ export default {
),
],
groupAttrs: {
description: this.isPathLoading
? s__('Groups|Checking group URL availability...')
: null,
description: this.isPathLoading ? s__('Groups|Checking group URL availability…') : null,
},
},
...(this.isEditing

View File

@ -507,7 +507,7 @@ export default class CreateMergeRequestDropdown {
setUnavailableButtonState(isLoading = true) {
if (isLoading) {
this.unavailableButtonSpinner.classList.remove('gl-hidden');
this.unavailableButtonText.textContent = __('Checking branch availability...');
this.unavailableButtonText.textContent = __('Checking branch availability');
} else {
this.unavailableButtonSpinner.classList.add('gl-hidden');
this.unavailableButtonText.textContent = __('New branch unavailable');

View File

@ -54,7 +54,7 @@ export default {
'Something went wrong while promoting the issue to an epic. Please try again.',
),
promoteSuccessMessage: __(
'The issue was successfully promoted to an epic. Redirecting to epic...',
'The issue was successfully promoted to an epic. Redirecting to epic',
),
reportAbuse: __('Report abuse'),
referenceFetchError: __('An error occurred while fetching reference'),

View File

@ -11,7 +11,7 @@ export const timelineFormI18n = Object.freeze({
createErrorGeneric: s__(
'Incident|Something went wrong while creating the incident timeline event.',
),
areaPlaceholder: s__('Incident|Timeline text...'),
areaPlaceholder: s__('Incident|Timeline text'),
areaDefaultMessage: s__('Incident|Incident'),
selectTags: __('Select tags'),
tagsLabel: __('Event tag (optional)'),

View File

@ -72,7 +72,7 @@ export const isReadyToCommit = (state) => {
export const getCommitButtonText = (state) => {
const initial = s__('MergeConflict|Commit to source branch');
const inProgress = s__('MergeConflict|Committing...');
const inProgress = s__('MergeConflict|Committing');
return state.isSubmitting ? inProgress : initial;
};

View File

@ -157,7 +157,7 @@ export default {
<tr v-else>
<td colspan="5" :class="{ 'gl-py-6 gl-text-center': !error }">
<template v-if="loading">
{{ __('Loading...') }}
{{ __('Loading') }}
</template>
<template v-else-if="error">
<gl-alert variant="danger" :dismissible="false">

View File

@ -96,7 +96,7 @@ export default {
}
if (this.checkingMergeChecks.length) {
return __('Checking if merge request can be merged...');
return __('Checking if merge request can be merged');
}
if (!this.failedChecks.length) {

View File

@ -40,8 +40,8 @@ export default {
},
timerText() {
return n__(
'Refreshing in a second to show the updated status...',
'Refreshing in %d seconds to show the updated status...',
'Refreshing in a second to show the updated status',
'Refreshing in %d seconds to show the updated status',
this.timer,
);
},

View File

@ -283,7 +283,7 @@ export default {
}
if (this.status === PIPELINE_FAILED_STATE || this.isPipelineFailed) {
return __('Merge...');
return __('Merge');
}
return __('Merge');

View File

@ -26,13 +26,13 @@ export const I18N_SHA_MISMATCH = {
};
export const MERGE_TRAIN_BUTTON_TEXT = {
failed: __('Start merge train...'),
failed: __('Start merge train'),
passed: __('Start merge train'),
};
export const MR_WIDGET_CLOSED_REOPEN = __('Reopen');
export const MR_WIDGET_CLOSED_REOPENING = __('Reopening...');
export const MR_WIDGET_CLOSED_RELOADING = __('Refreshing...');
export const MR_WIDGET_CLOSED_REOPENING = __('Reopening');
export const MR_WIDGET_CLOSED_RELOADING = __('Refreshing');
export const MR_WIDGET_CLOSED_REOPEN_FAILURE = __(
'An error occurred. Unable to reopen this merge request.',
);

View File

@ -24,7 +24,7 @@ export default {
};
},
i18n: {
loading: s__('Terraform|Loading Terraform reports...'),
loading: s__('Terraform|Loading Terraform reports'),
error: s__('Terraform|Failed to load Terraform reports'),
reportGenerated: s__('Terraform|A Terraform report was generated in your pipelines.'),
namedReportGenerated: s__(

View File

@ -2,7 +2,7 @@
<script>
import { GlIcon, GlLoadingIcon } from '@gitlab/ui';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import VirtualList from 'vue-virtual-scroll-list';
import { RecycleScroller } from 'vendor/vue-virtual-scroller';
import { Mousetrap, addStopCallback } from '~/lib/mousetrap';
import { keysFor, MR_GO_TO_FILE } from '~/behaviors/shortcuts/keybindings';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
@ -16,7 +16,7 @@ export default {
GlIcon,
GlLoadingIcon,
Item,
VirtualList,
RecycleScroller,
},
props: {
files: {
@ -66,9 +66,6 @@ export default {
filteredBlobsLength() {
return this.filteredBlobs.length;
},
listShowCount() {
return this.filteredBlobsLength ? Math.min(this.filteredBlobsLength, 5) : 1;
},
listHeight() {
return FILE_FINDER_ROW_HEIGHT;
},
@ -99,29 +96,10 @@ export default {
this.focusedIndex = 0;
});
},
focusedIndex() {
focusedIndex(val) {
if (!this.mouseOver) {
this.$nextTick(() => {
if (!this.$refs.virtualScrollList?.$el) {
return;
}
const el = this.$refs.virtualScrollList.$el;
const scrollTop = this.focusedIndex * FILE_FINDER_ROW_HEIGHT;
const bottom = this.listShowCount * FILE_FINDER_ROW_HEIGHT;
if (this.focusedIndex === 0) {
// if index is the first index, scroll straight to start
el.scrollTop = 0;
} else if (this.focusedIndex === this.filteredBlobsLength - 1) {
// if index is the last index, scroll to the end
el.scrollTop = this.filteredBlobsLength * FILE_FINDER_ROW_HEIGHT;
} else if (scrollTop >= bottom + el.scrollTop) {
// if element is off the bottom of the scroll list, scroll down one item
el.scrollTop = scrollTop - bottom + FILE_FINDER_ROW_HEIGHT;
} else if (scrollTop < el.scrollTop) {
// if element is off the top of the scroll list, scroll up one item
el.scrollTop = scrollTop;
}
this.$refs.virtualScrollList?.scrollToItem(val);
});
}
},
@ -255,11 +233,29 @@ export default {
/>
</div>
<div>
<virtual-list ref="virtualScrollList" :size="listHeight" :remain="listShowCount" wtag="ul">
<template v-if="filteredBlobsLength">
<li v-for="(file, index) in filteredBlobs" :key="file.key">
<recycle-scroller
ref="virtualScrollList"
:items="filteredBlobs"
:item-size="listHeight"
key-field="key"
style="max-height: 275px"
>
<template #before>
<li v-if="!filteredBlobs.length || loading" class="dropdown-menu-empty-item">
<div class="gl-my-3 gl-ml-3 gl-mr-3">
<template v-if="loading">
<gl-loading-icon />
</template>
<template v-else>
{{ __('No files found.') }}
</template>
</div>
</li>
</template>
<template #default="{ item, index }">
<li>
<item
:file="file"
:file="item"
:search-text="searchText"
:focused="index === focusedIndex"
:index="index"
@ -271,17 +267,7 @@ export default {
/>
</li>
</template>
<li v-else class="dropdown-menu-empty-item">
<div class="gl-mb-3 gl-ml-3 gl-mr-3 gl-mt-5">
<template v-if="loading">
<gl-loading-icon />
</template>
<template v-else>
{{ __('No files found.') }}
</template>
</div>
</li>
</virtual-list>
</recycle-scroller>
</div>
</div>
</div>

View File

@ -85,10 +85,10 @@ export default {
},
applyingSuggestionsMessage() {
if (this.isApplyingSingle || this.batchSuggestionsCount < 2) {
return __('Applying suggestion...');
return __('Applying suggestion');
}
return __('Applying suggestions...');
return __('Applying suggestions');
},
isLoggedIn() {
return isLoggedIn();

View File

@ -2,6 +2,9 @@
module Auth # rubocop:disable Gitlab/BoundedContexts -- following the same structure as other services
class DpopAuthenticationService < ::BaseContainerService
# Demonstrating Proof of Possession (DPoP) blueprint:
# https://gitlab.com/gitlab-com/gl-security/product-security/appsec/security-feature-blueprints/-/blob/main/sender_constraining_access_tokens/index.md
def initialize(current_user:, personal_access_token_plaintext:, request:)
@current_user = current_user
@personal_access_token_plaintext = personal_access_token_plaintext
@ -24,7 +27,15 @@ module Auth # rubocop:disable Gitlab/BoundedContexts -- following the same struc
attr_reader :current_user, :personal_access_token_plaintext, :request
def extract_dpop_from_request!(request)
request.headers.fetch('dpop') { raise Gitlab::Auth::DpopValidationError, 'DPoP header is missing' }
dpop_token = request.headers.fetch('dpop') { raise Gitlab::Auth::DpopValidationError, 'DPoP header is missing' }
# Ensure there is exactly one token
if dpop_token.strip.match?(/[\s,]+/)
raise Gitlab::Auth::DpopValidationError,
'Only 1 DPoP header is allowed in request'
end
dpop_token
end
end
end

View File

@ -211,7 +211,7 @@ module SystemNotes
end
def reviewed
body = "left review comments without approving or requesting changes"
body = "left review comments"
create_note(NoteSummary.new(noteable, project, author, body, action: 'reviewed'))
end

View File

@ -45,7 +45,7 @@
- pat_link = link_to('', help_page_path('user/profile/personal_access_tokens.md'), target: '_blank')
- short_living_link = link_to('', help_page_path('security/tokens/_index.md', anchor: 'security-considerations'), target: '_blank')
= safe_format(s_('GroupsNew|Create a token in the %{pat_link_start}user settings%{pat_link_end} of the source GitLab instance with the %{code_start}api%{code_end} scope. For source instances on GitLab 15.0 or earlier, the token must additionally have the %{code_start}read_repository%{code_end} scope. For %{short_living_link_start}security reasons%{short_living_link_end}, set a short expiration date for the token. Keep in mind that large migrations take more time.'), tag_pair('<code></code>'.html_safe, :code_start , :code_end), tag_pair(pat_link, :pat_link_start, :pat_link_end), tag_pair(short_living_link, :short_living_link_start, :short_living_link_end))
= f.password_field :bulk_import_gitlab_access_token, placeholder: s_('GroupsNew|e.g. h8d3f016698e...'), class: 'gl-form-input gl-mt-3 col-xs-12 col-sm-8',
= f.password_field :bulk_import_gitlab_access_token, placeholder: s_('GroupsNew|e.g. h8d3f016698e'), class: 'gl-form-input gl-mt-3 col-xs-12 col-sm-8',
required: true,
disabled: !bulk_imports_enabled,
autocomplete: 'off',

View File

@ -14,7 +14,7 @@
.js-markdown-editor{ data: { render_markdown_path: group_preview_markdown_path,
markdown_docs_path: help_page_path('user/markdown.md'),
testid: 'milestone-description-field',
form_field_placeholder: _('Write milestone description...'),
form_field_placeholder: _('Write milestone description'),
supports_quick_actions: 'false',
enable_autocomplete: 'true',
autofocus: 'false',

View File

@ -1,10 +1,9 @@
---
migration_job_name: FixNamespaceIdForWorkItemParentLinks
description:
This fixes a bug where the namespace_id for work_item_parent_links got set to the issue's group ID instead
of the project's namespace_id.
description: This fixes a bug where the namespace_id for work_item_parent_links got
set to the issue's group ID instead of the project's namespace_id.
feature_category: team_planning
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/160264
milestone: '17.3'
queued_migration_version: 20240722144720
finalized_by: # version of the migration that finalized this BBM
finalized_by: '20250226231923'

View File

@ -1,6 +1,7 @@
---
table_name: sbom_occurrences
classes:
- Sbom::DependencyPath
- Sbom::Occurrence
feature_categories:
- dependency_management

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FinalizeHkFixNamespaceIdForWorkItemParentLinks < Gitlab::Database::Migration[2.2]
milestone '17.10'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'FixNamespaceIdForWorkItemParentLinks',
table_name: :work_item_parent_links,
column_name: :id,
job_arguments: [],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1 @@
08c64374ddc12f0f15bda6698579766a6e80474eddbd21293ba297119a477832

View File

@ -99,7 +99,7 @@ in GitLab 18.0. Runner authentication tokens should be used instead. For more in
Prerequisites:
- Runner registration tokens must be [enabled](../settings/continuous_integration.md#allow-runner-registrations-tokens) in the **Admin** area.
- Runner registration tokens must be [enabled](../settings/continuous_integration.md#allow-runner-registration-tokens) in the **Admin** area.
```ruby
Gitlab::CurrentSettings.current_application_settings.runners_registration_token

View File

@ -22,8 +22,7 @@ provide the following information to your account team:
- Expected number of users.
- Initial storage size for your repositories in GB.
- Email addresses of the users who are responsible to complete the onboarding and create your
GitLab Dedicated instance.
- Email addresses of any users that need to complete the onboarding and create your GitLab Dedicated instance.
- Whether you want to [bring your own encryption keys (BYOK)](#encrypted-data-at-rest-byok). If so, GitLab provides an AWS account ID, which is necessary to enable BYOK.
- Whether you want to use Geo migration for inbound migration of your Dedicated instance.
@ -160,44 +159,50 @@ Make sure the AWS KMS keys are replicated to your desired primary, secondary and
## Step 2: Create your GitLab Dedicated instance
Once signed in to Switchboard, you will need to go through a series of four steps to provide the
information required to create your GitLab Dedicated instance.
After you sign in to Switchboard, follow these steps to create your instance:
1. Confirm account details: Confirm key attributes of your GitLab Dedicated account:
- Reference architecture: Corresponds with the number of users you provided to your account team
when beginning the onboarding process. For more information, see
[reference architectures](../../subscriptions/gitlab_dedicated/data_residency_and_high_availability.md#availability-and-scalability).
- Total repository storage size: Corresponds with the storage size you provided to your account
team when beginning the onboarding process.
- If you need to make changes to these attributes,
[submit a support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650).
1. Tenant configuration: Provides the minimum required information needed to create your GitLab
Dedicated instance:
- Desired instance subdomain: The main domain for GitLab Dedicated instances is
`gitlab-dedicated.com`. You choose the subdomain name where your instance is accessible from.
For example, `customer_name.gitlab-dedicated.com`. You can add a custom hostname in a later step.
- Desired primary region: Primary AWS region in which your data is stored. Note the
1. On the **Account details** page, review and confirm your subscription settings. These settings are based on the information you provided to your account team:
- **Reference architecture**: The maximum number of users allowed in your instance. For more information, see [availability and scalability](../../subscriptions/gitlab_dedicated/data_residency_and_high_availability.md#availability-and-scalability). For example, up to 3,000 users.
- **Total repository capacity**: The total storage space available for all repositories in your instance. For example, 16 GB. This setting cannot be reduced after you create your instance. You can increase storage capacity later if needed.
If you need to change either of these values, [submit a support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650).
1. On the **Configuration** page, choose your environment access, location, and maintenance window settings:
- **Tenant name**: Enter a name for your tenant. This name is permanent unless you [bring your own domain](configure_instance/network_security.md#bring-your-own-domain-byod).
- **Tenant URL**: Your instance URL is automatically generated as `<tenant_name>.gitlab-dedicated.com`.
- **Primary region**: Select the primary AWS region to use for data storage. Note the
[available AWS regions](../../subscriptions/gitlab_dedicated/data_residency_and_high_availability.md#available-aws-regions).
- Desired secondary region: Secondary AWS region in which your data is stored. This region is
used to recover your GitLab Dedicated instance in case of a disaster.
- Desired backup region: An AWS region where the primary backups of your data are replicated.
This can be the same as the primary or secondary region, or different.
- Desired maintenance window: A weekly four-hour time slot that GitLab uses to perform routine
maintenance and upgrade operations on all tenant instances. For more information, see
[maintenance windows](../dedicated/maintenance.md#maintenance-windows).
1. Optional. Security: You can provide your own [KMS keys](https://docs.aws.amazon.com/kms/latest/developerguide/overview.html)
for encrypted AWS services. If you choose not to provide KMS keys, encryption keys are generated
for your instance when it is created. For more information, see [encrypting your data at rest](#encrypted-data-at-rest-byok).
1. Summary: Confirm that the information you've provided in the previous steps is accurate
before initiating the creation of your instance.
{{< alert type="note" >}}
- **Secondary region**: Select a secondary AWS region to use for data storage and [disaster recovery](../../subscriptions/gitlab_dedicated/data_residency_and_high_availability.md#disaster-recovery). This field does not appear for Geo migrations from an existing GitLab Self-Managed instance.
Some configuration settings (like the option to bring your own keys and your tenant name) are permanent and cannot be changed once your instance has been created.
- **Backup region**: Select a region to replicate and store your primary data backups.
You can use the same option as your primary or secondary regions, or choose a different region for [increased redundancy](../../subscriptions/gitlab_dedicated/data_residency_and_high_availability.md#disaster-recovery).
{{< /alert >}}
- **Time zone**: Select a weekly four-hour time slot when GitLab performs routine
maintenance and upgrades. For more information, see [maintenance windows](../dedicated/maintenance.md#maintenance-windows).
It can take up to 3 hours to create the GitLab Dedicated instance. When the setup is complete, you will receive a confirmation email.
1. Optional. On the **Security** page, add your [AWS KMS keys](https://docs.aws.amazon.com/kms/latest/developerguide/overview.html) for encrypted AWS services. If you do not add keys, GitLab generates encryption keys for your instance. For more information, see [encrypting your data at rest](#encrypted-data-at-rest-byok).
1. On the **Tenant summary** page, review the tenant configuration details. After you confirm that the information you've provided in the previous steps is accurate, select **Create tenant**.
{{< alert type="note" >}}
Confirm these settings carefully before you create your instance,
as you cannot change them later:
- Security keys and AWS KMS keys (BYOK) configuration
- AWS regions (primary, secondary, backup)
- Total repository capacity (you can increase storage but cannot reduce it)
- Tenant name and URL (unless you [bring your own domain](configure_instance/network_security.md#bring-your-own-domain-byod))
{{< /alert >}}
Your GitLab Dedicated instance can take up to three hours to create. GitLab sends a confirmation email when the setup is complete.
## Step 3: Access and configure your GitLab Dedicated instance

View File

@ -205,6 +205,8 @@ The following metrics are available:
| `gitlab_rack_attack_throttle_period_seconds` | Gauge | 17.6 | Reports the duration over which requests for a client are counted before Rack Attack throttles them. | `event_name` |
| `gitlab_application_rate_limiter_throttle_utilization_ratio` | Histogram | 17.6 | Utilization ratio of a throttle in GitLab Application Rate Limiter. | `throttle_key`, `peek`, `feature_category` |
| `search_zoekt_task_processing_queue_size` | Gauge | 17.9 | Number of tasks waiting to be processed by Zoekt. | `node_name` |
| `gitlab_dependency_path_cte_real_duration_seconds` | Histogram | 17.10 | Duration in seconds spent resolving the ancestor dependency paths for a given component. | |
| `dependency_path_cte_paths_found` | Counter | 17.10 | Counts the number of ancestor dependency paths found for a given dependency. | `max_depth_reached`, `cyclic` |
## Metrics controlled by a feature flag

View File

@ -326,49 +326,182 @@ It uses the first of these settings that has a value.
The service account must have permission to access the bucket. For more information,
see the [Cloud Storage authentication documentation](https://cloud.google.com/storage/docs/authentication).
{{< alert type="note" >}}
#### Google Cloud Application Default Credentials
To use bucket encryption with [customer-managed encryption keys](https://cloud.google.com/storage/docs/encryption/using-customer-managed-keys), use the [consolidated form](#configure-a-single-storage-connection-for-all-object-types-consolidated-form).
{{< /alert >}}
#### GCS example
For Linux Package installations, this is an example of the `connection` setting in the consolidated form:
```ruby
gitlab_rails['object_store']['connection'] = {
'provider' => 'Google',
'google_project' => '<GOOGLE PROJECT>',
'google_json_key_location' => '<FILENAME>'
}
```
#### GCS example with ADC
Google Cloud Application Default Credentials (ADC) are typically
used with GitLab to use the default service account. This eliminates the
need to supply credentials for the instance. For example, in the consolidated form:
```ruby
gitlab_rails['object_store']['connection'] = {
'provider' => 'Google',
'google_project' => '<GOOGLE PROJECT>',
'google_application_default' => true
}
```
[Google Cloud Application Default Credentials (ADC)](https://cloud.google.com/docs/authentication/application-default-credentials) are typically
used with GitLab to use the default service account or [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation).
Set `google_application_default` to `true` and omit `google_json_key_location` and `google_json_key_string`.
If you use ADC, be sure that:
- The service account that you use has the
[`iam.serviceAccounts.signBlob` permission](https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signBlob).
Typically this is done by granting the `Service Account Token Creator` role to the service account.
- Your virtual machines have the [correct access scopes to access Google Cloud APIs](https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances#changeserviceaccountandscopes). If the machines do not have the right scope, the error logs may show:
- If you are using Google Compute virtual machines, ensure they have the [correct access scopes to access Google Cloud APIs](https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances#changeserviceaccountandscopes). If the machines do not have the right scope, the error logs may show:
```markdown
Google::Apis::ClientError (insufficientPermissions: Request had insufficient authentication scopes.)
```
{{< alert type="note" >}}
To use bucket encryption with [customer-managed encryption keys](https://cloud.google.com/storage/docs/encryption/using-customer-managed-keys), use the [consolidated form](#configure-a-single-storage-connection-for-all-object-types-consolidated-form).
{{< /alert >}}
{{< tabs >}}
{{< tab title="Linux package (Omnibus)" >}}
1. Edit `/etc/gitlab/gitlab.rb` and add the following lines, substituting
the values you want:
```ruby
gitlab_rails['object_store']['connection'] = {
'provider' => 'Google',
'google_project' => '<GOOGLE PROJECT>',
'google_json_key_location' => '<FILENAME>'
}
```
To use ADC, use `google_application_default` instead:
```ruby
gitlab_rails['object_store']['connection'] = {
'provider' => 'Google',
'google_project' => '<GOOGLE PROJECT>',
'google_application_default' => true
}
```
1. Save the file and reconfigure GitLab:
```shell
sudo gitlab-ctl reconfigure
```
{{< /tab >}}
{{< tab title="Helm chart (Kubernetes)" >}}
1. Put the following content in a file named `object_storage.yaml` to be used as a
[Kubernetes Secret](https://docs.gitlab.com/charts/charts/globals.html#connection):
```yaml
provider: Google
google_project: <GOOGLE PROJECT>
google_json_key_location: '<FILENAME>'
```
To use ADC, use `google_application_default` instead:
```yaml
provider: Google
google_project: <GOOGLE PROJECT>
google_application_default: true
```
1. Create the Kubernetes Secret:
```shell
kubectl create secret generic -n <namespace> gitlab-object-storage --from-file=connection=object_storage.yaml
```
1. Export the Helm values:
```shell
helm get values gitlab > gitlab_values.yaml
```
1. Edit `gitlab_values.yaml`:
```yaml
global:
appConfig:
artifacts:
bucket: gitlab-artifacts
ciSecureFiles:
bucket: gitlab-ci-secure-files
enabled: true
dependencyProxy:
bucket: gitlab-dependency-proxy
enabled: true
externalDiffs:
bucket: gitlab-mr-diffs
enabled: true
lfs:
bucket: gitlab-lfs
object_store:
connection:
secret: gitlab-object-storage
enabled: true
proxy_download: false
packages:
bucket: gitlab-packages
terraformState:
bucket: gitlab-terraform-state
enabled: true
uploads:
bucket: gitlab-uploads
```
1. Save the file and apply the new values:
```shell
helm upgrade -f gitlab_values.yaml gitlab gitlab/gitlab
```
{{< /tab >}}
{{< tab title="Docker" >}}
1. Edit `docker-compose.yml`:
```yaml
version: "3.6"
services:
gitlab:
environment:
GITLAB_OMNIBUS_CONFIG: |
# Consolidated object storage configuration
gitlab_rails['object_store']['enabled'] = true
gitlab_rails['object_store']['proxy_download'] = false
gitlab_rails['object_store']['connection'] = {
'provider' => 'Google',
'google_project' => '<GOOGLE PROJECT>',
'google_json_key_location' => '<FILENAME>'
}
gitlab_rails['object_store']['objects']['artifacts']['bucket'] = 'gitlab-artifacts'
gitlab_rails['object_store']['objects']['external_diffs']['bucket'] = 'gitlab-mr-diffs'
gitlab_rails['object_store']['objects']['lfs']['bucket'] = 'gitlab-lfs'
gitlab_rails['object_store']['objects']['uploads']['bucket'] = 'gitlab-uploads'
gitlab_rails['object_store']['objects']['packages']['bucket'] = 'gitlab-packages'
gitlab_rails['object_store']['objects']['dependency_proxy']['bucket'] = 'gitlab-dependency-proxy'
gitlab_rails['object_store']['objects']['terraform_state']['bucket'] = 'gitlab-terraform-state'
gitlab_rails['object_store']['objects']['ci_secure_files']['bucket'] = 'gitlab-ci-secure-files'
gitlab_rails['object_store']['objects']['pages']['bucket'] = 'gitlab-pages'
```
To use ADC, use `google_application_default` instead:
```ruby
gitlab_rails['object_store']['connection'] = {
'provider' => 'Google',
'google_project' => '<GOOGLE PROJECT>',
'google_application_default' => true
}
```
1. Save the file and restart GitLab:
```shell
docker compose up -d
```
{{< /tab >}}
{{< /tabs >}}
### Azure Blob storage
Although Azure uses the word `container` to denote a collection of

View File

@ -525,9 +525,7 @@ Additionally, the following cloud provider services are recommended for use as p
### Best practices for the database services
Use an [external database service](../postgresql/external.md) that runs a standard, performant, and [supported PostgreSQL version](../../install/requirements.md#postgresql).
If you choose to use a third-party external service:
If you choose to use a third-party external service, use an [external database service](../postgresql/external.md) that runs a standard, performant, and [supported PostgreSQL version](../../install/requirements.md#postgresql) and take note of the following considerations:
1. The HA Linux package PostgreSQL setup encompasses PostgreSQL, PgBouncer, and Consul. All of these components are no longer required when using a third party external service.
1. For optimal performance, enable [Database Load Balancing](../postgresql/database_load_balancing.md) with Read Replicas. Match the node counts to those used in standard

View File

@ -142,7 +142,7 @@ To restrict runner registration by members in a specific group:
1. Clear the **New group runners can be registered** checkbox if you want to disable runner registration by all members in the group. If the setting is read-only, you must enable runner registration for the [instance](#restrict-runner-registration-by-all-users-in-an-instance).
1. Select **Save changes**.
### Allow runner registrations tokens
### Allow runner registration tokens
{{< history >}}

View File

@ -24220,6 +24220,29 @@ A software dependency used by a project.
| <a id="dependencyversion"></a>`version` | [`String`](#string) | Version of the dependency. |
| <a id="dependencyvulnerabilitycount"></a>`vulnerabilityCount` | [`Int!`](#int) | Number of vulnerabilities within the dependency. |
### `DependencyPath`
Ancestor path of a given dependency.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="dependencypathiscyclic"></a>`isCyclic` | [`Boolean!`](#boolean) | Indicates if the path is cyclic. |
| <a id="dependencypathmaxdepthreached"></a>`maxDepthReached` | [`Boolean!`](#boolean) | Indicates if the path reached the maximum depth (20). |
| <a id="dependencypathpath"></a>`path` | [`[DependencyPathPartial!]!`](#dependencypathpartial) | Name of the dependency. |
### `DependencyPathPartial`
Ancestor path partial of a given dependency.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="dependencypathpartialname"></a>`name` | [`String!`](#string) | Name of the dependency. |
| <a id="dependencypathpartialversion"></a>`version` | [`String!`](#string) | Version of the dependency. |
### `DependencyProxyBlob`
Dependency proxy blob.
@ -34085,6 +34108,24 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="projectdependenciessort"></a>`sort` | [`DependencySort`](#dependencysort) | Sort dependencies by given criteria. |
| <a id="projectdependenciessourcetypes"></a>`sourceTypes` | [`[SbomSourceType!]`](#sbomsourcetype) | Filter dependencies by source type. |
##### `Project.dependencyPaths`
Ancestor dependency paths for a dependency used by the project. \
Returns `null` if `dependency_graph_graphql` feature flag is disabled.
{{< details >}}
**Introduced** in GitLab 17.10.
**Status**: Experiment.
{{< /details >}}
Returns [`[DependencyPath!]`](#dependencypath).
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="projectdependencypathscomponent"></a>`component` | [`SbomComponentID!`](#sbomcomponentid) | Dependency path for component. |
##### `Project.deployment`
Details of the deployment of the project.

View File

@ -39,7 +39,7 @@ Prerequisites:
| `projects_to_exclude` | array of integers | no | The IDs of projects to exclude from the feature. |
```shell
curl --header PUT "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/7/security_settings?secret_push_protection_enabled=true&projects_to_exclude=1,2,3"
curl --header PUT "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/7/security_settings?secret_push_protection_enabled=true&projects_to_exclude[]=1&projects_to_exclude[]=2"
```
Example response:

View File

@ -88,6 +88,12 @@ For submodules not located on the same GitLab server, always use the full URL:
## Use Git submodules in CI/CD jobs
Prerequisites:
- If you use the [`CI_JOB_TOKEN`](../jobs/ci_job_token.md) to clone a submodule in a
pipeline job, you must have at least the Reporter role for the submodule repository to pull the code.
- [CI/CD job token access](../jobs/ci_job_token.md#control-job-token-access-to-your-project) must be properly configured in the upstream submodule project.
To make submodules work correctly in CI/CD jobs:
1. You can set the `GIT_SUBMODULE_STRATEGY` variable to either `normal` or `recursive`
@ -125,11 +131,6 @@ To make submodules work correctly in CI/CD jobs:
GIT_SUBMODULE_UPDATE_FLAGS: --jobs 4
```
If you use the [`CI_JOB_TOKEN`](../jobs/ci_job_token.md) to clone a submodule in a
pipeline job, the user executing the job must be assigned to a role that has
[permission](../../user/permissions.md#cicd) to trigger a pipeline
in the upstream submodule project. Additionally, [CI/CD job token access](../jobs/ci_job_token.md#control-job-token-access-to-your-project) must be properly configured in the upstream submodule project.
## Troubleshooting
### Can't find the `.gitmodules` file

View File

@ -67,7 +67,7 @@ To avoid a broken workflow, you must:
In GitLab 17.0 and later, runner registration tokens are disabled.
To use stored runner registration tokens to register new runners,
you must [enable the tokens](../../administration/settings/continuous_integration.md#allow-runner-registrations-tokens).
you must [enable the tokens](../../administration/settings/continuous_integration.md#allow-runner-registration-tokens).
{{< /alert >}}
@ -77,7 +77,7 @@ To continue using registration tokens after GitLab 17.0:
- On GitLab.com, you can manually [enable the legacy runner registration process](runners_scope.md#enable-use-of-runner-registration-tokens-in-projects-and-groups)
in the top-level group settings until GitLab 18.0.
- On GitLab Self-Managed, you can manually [enable the legacy runner registration process](../../administration/settings/continuous_integration.md#allow-runner-registrations-tokens)
- On GitLab Self-Managed, you can manually [enable the legacy runner registration process](../../administration/settings/continuous_integration.md#allow-runner-registration-tokens)
in the **Admin** area settings until GitLab 18.0.
## Impact on existing runners

View File

@ -94,7 +94,7 @@ in GitLab 18.0. Use runner authentication tokens instead. For more information,
Prerequisites:
- Runner registration tokens must be [enabled](../../administration/settings/continuous_integration.md#allow-runner-registrations-tokens) in the **Admin** area.
- Runner registration tokens must be [enabled](../../administration/settings/continuous_integration.md#allow-runner-registration-tokens) in the **Admin** area.
- You must be an administrator.
To create an instance runner:
@ -792,7 +792,7 @@ In GitLab 17.0, the use of runner registration tokens is disabled in all GitLab
Prerequisites:
- Runner registration tokens must be [enabled](../../administration/settings/continuous_integration.md#allow-runner-registrations-tokens) in the **Admin** area.
- Runner registration tokens must be [enabled](../../administration/settings/continuous_integration.md#allow-runner-registration-tokens) in the **Admin** area.
To enable the use of runner registration token in project and groups:

View File

@ -1441,18 +1441,24 @@ Use `spec/docs_screenshots/container_registry_docs.rb` as a guide to create your
Use a diagram to illustrate a process or the relationship between entities, if the information is too
complex to be understood from text only.
To create a diagram, use [Mermaid](https://mermaid.js.org/#/), which has the following advantages:
To create a diagram, use either [Mermaid](https://mermaid.js.org/#/) (recommended) or [Draw.io](https://draw.io).
- The Mermaid format is easier to maintain because the:
- Diagram definition is stored as a code block in the documentation's Markdown source.
- Diagram is rendered dynamically at runtime.
- Text content in the diagram (such as feature names) can be found with text search
tools and edited.
- The diagram is rendered as a scalable image, better suited to various output devices and sizes.
Mermaid is the recommended diagramming tool, but it is not suitable for all situations. For example,
complex diagram requirements might result in a layout that is difficult to understand.
To learn how to create diagrams with the [Mermaid syntax](https://mermaid.js.org/intro/syntax-reference.html),
see the [Mermaid user guide](https://mermaid.js.org/intro/getting-started.html)
and the examples on the Mermaid site.
GUI diagramming tools can help authors overcome Mermaid's complexity and layout issue. Draw.io is
the preferred GUI tool because, when using the editor, both the diagram and its definition are
stored in the SVG file, so it can be easily edited. Draw.io is also integrated with the GitLab wiki.
| Feature| Mermaid | Draw.io |
|--------|---------|---------|
| **Editor required** | Text editor | Draw.io editor |
| **WYSIWYG editing** | {{< icon name="dash-circle" >}} No | {{< icon name="check-circle-filled" >}} Yes |
| **Text content findable by `grep`** | {{< icon name="check-circle-filled" >}} Yes | {{< icon name="dash-circle" >}} No |
| **Appearance controlled by** | Web site's CSS | Diagram's author |
| **File format** | SVG | SVG |
| **VS Code integration (with extensions)** | {{< icon name="check-circle-filled" >}} Yes (Preview and local editing) | {{< icon name="check-circle-filled" >}} Yes (Preview and local editing) |
| **Generated dynamically** | {{< icon name="check-circle-filled" >}} Yes | {{< icon name="dash-circle" >}} No |
#### Guidelines
@ -1475,9 +1481,13 @@ To create accessible and easily maintainable diagrams, follow these guidelines:
- Do not include links. Links embedded in diagrams with [`click` actions](https://mermaid.js.org/syntax/classDiagram.html#interaction) are not testable with our link checking tools.
- Update diagrams along with documentation or code when processes change to maintain accuracy.
#### Create a diagram
#### Create a diagram with Mermaid
To create a diagram for GitLab documentation:
To learn how to create diagrams with the [Mermaid syntax](https://mermaid.js.org/intro/syntax-reference.html),
see the [Mermaid user guide](https://mermaid.js.org/intro/getting-started.html)
and the examples on the Mermaid site.
To create a diagram for GitLab documentation with Mermaid:
1. In the [Mermaid Live Editor](https://mermaid.live/), create the diagram.
1. Copy the content of the **Code** pane and paste it in the Markdown file, wrapped in a `mermaid` code block. For more
@ -1512,6 +1522,34 @@ flowchart TD
```
````
#### Create a diagram with Draw.io
Use either the [Draw.io](https://draw.io) web application or the (unofficial)
VS Code [Draw.io Integration](https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio)
extension to create the diagram. Each tool provides the same diagram editing experience, however the web
application provides example diagrams that you can edit to suit your needs.
##### Use the web application
To create a diagram by using the Draw.io web application:
1. In the [Draw.io](https://draw.io) web application, create the diagram.
1. Save the diagram:
1. In the Draw.io web application, select **File** > **Export as** > **SVG**.
1. Select the **Include a copy of my diagram: All pages** checkbox, then select **Export**. Use
the file extension `drawio.svg` to indicate it can be edited in Draw.io.
##### Use the VS Code extension
To create a diagram by using the Draw.io Integration extension for VS Code:
1. In the directory that will contain the diagram, create an empty file with the suffix
`drawio.svg`.
1. Open the file in VS Code then create the diagram.
1. Save the file.
The diagram's definition is stored in Draw.io-compatible format in the SVG file.
## Emoji
Don't use the Markdown emoji format, for example `:smile:`, for any purpose. Use

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -46,14 +46,14 @@ Remember to **Save** each translation.
In Crowdin, each string contains a link that shows all instances of the string in the entire GitLab codebase.
When you translate a string, you can go to the relevant commit or merge request to get more context.
![Crowdin Editor showing a string with a link for more translation context](img/crowdin-editor-string-context.png)
![Crowdin Editor showing a string with a link for more translation context](img/crowdin-editor-string-context_v17_10.png)
When you select the link, code search results appear for that string.
You can [view Git blame from code search](../../user/search/_index.md#view-git-blame-from-code-search)
to see the commits that added the string.
For a list of relevant merge requests, select a commit.
![GitLab code search displaying results for a string](img/gitlab-search-occurrence.png)
![GitLab code search displaying results for a string](img/gitlab-search-occurrence_v17_10.png)
## General Translation Guidelines

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 167 KiB

View File

Before

Width:  |  Height:  |  Size: 338 KiB

After

Width:  |  Height:  |  Size: 338 KiB

View File

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 KiB

View File

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

@ -280,7 +280,7 @@ and a new method [`ruby-prof`](https://ruby-prof.github.io/).
[Performance bar](../administration/monitoring/performance/performance_bar.md) is a great tool to get a stackprof report
and see a flamegraph via a single click;
![Performance Bar Flamegraph Link](img/performance_bar_flamegraph_link.png)
![Performance Bar Flamegraph Link](img/performance_bar_flamegraph_link_v17_7.png)
However, it's not available for other than GET requests.
@ -302,7 +302,7 @@ curl --request POST \
To get around this, we copy the request as `curl` and use it in the terminal.
![Performance copy as curl](img/performance_copy_as_curl.png)
![Performance copy as curl](img/performance_copy_as_curl_v17_7.png)
We'll have a `curl` command like this:
@ -333,12 +333,12 @@ curl "https://gitlab.com/api/v4/projects/:id/merge_requests/:iid/pipelines?perfo
Then, we use the `flamegraph.json` file on the `https://www.speedscope.app/` website to see the flamegraph.
![Speedscope flamegraph example](img/performance_speedscope_example.png)
![Speedscope flamegraph example](img/performance_speedscope_example_v17_7.png)
As an example, when investigating into this speedscope flamegraph, we saw that the `kubernetes_variables` method was
taking a lot of time and created [an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/498648).
![Speedscope flamegraph Kubernetes example](img/performance_speedscope_example_kubernetes.png)
![Speedscope flamegraph Kubernetes example](img/performance_speedscope_example_kubernetes_v17_7.png)
### Using `ruby-prof`
@ -381,7 +381,7 @@ end
::Ci::DestroyPipelineService.new(project, user).execute(Ci::Pipeline.last)
```
![Ruby-prof callstack report](img/performance_ruby-prof_example.png)
![Ruby-prof callstack report](img/performance_ruby-prof_example_v17_7.png)
Here, we can see that we call `Ci::GenerateKubeconfigService` ~2k times.
This is a good indicator that we need to investigate this.

View File

@ -24,7 +24,7 @@ This feature is an [experiment](../../policy/development_stages_support.md).
## Report issues with the plugin
You can report any issues, bugs, or feature requests in the
[`gitlab-eclipse-plugin` issue queue](https://gitlab.com/gitlab-org/editor-extensions/gitlab-eclipse-plugin/-/issues).
[`gitlab-eclipse-plugin` issue tracker](https://gitlab.com/gitlab-org/editor-extensions/gitlab-eclipse-plugin/-/issues).
Use the `Bug` or `Feature Proposal` template.
## Related topics

View File

@ -69,7 +69,7 @@ From the IDE:
## Report issues with the plugin
You can report any issues, bugs, or feature requests in the
[`gitlab-jetbrains-plugin` issue queue](https://gitlab.com/gitlab-org/editor-extensions/gitlab-jetbrains-plugin/-/issues).
[`gitlab-jetbrains-plugin` issue tracker](https://gitlab.com/gitlab-org/editor-extensions/gitlab-jetbrains-plugin/-/issues).
Use the `Bug` or `Feature Proposal` template.
If you encounter an error while using GitLab Duo, you can also report it with your IDE's

View File

@ -48,7 +48,7 @@ require('gitlab').setup({
## Report issues with the extension
Report any issues, bugs, or feature requests in the
[`gitlab.vim` issue queue](https://gitlab.com/gitlab-org/editor-extensions/gitlab.vim/-/issues).
[`gitlab.vim` issue tracker](https://gitlab.com/gitlab-org/editor-extensions/gitlab.vim/-/issues).
Submit your feedback in [issue 22](https://gitlab.com/gitlab-org/editor-extensions/gitlab.vim/-/issues/22)
in the `gitlab.vim` repository.

View File

@ -14,7 +14,7 @@ and [GitLab Duo Chat](../../user/gitlab_duo_chat/_index.md#use-gitlab-duo-chat-i
[Install and configure the extension](setup.md).
Report any issues, bugs, or feature requests in the
[`gitlab-visual-studio-extension` issue queue](https://gitlab.com/gitlab-org/editor-extensions/gitlab-visual-studio-extension/-/issues).
[`gitlab-visual-studio-extension` issue tracker](https://gitlab.com/gitlab-org/editor-extensions/gitlab-visual-studio-extension/-/issues).
## Related topics

View File

@ -26,7 +26,7 @@ Prerequisites:
A[Self-signed CA] -- signed --> B[Your GitLab instance certificate]
```
For more information, see [Self-signed certificate error when installing Python support in WSL](https://github.com/microsoft/vscode/issues/131836#issuecomment-909983815) in the Visual Studio Code issue queue.
For more information, see [Self-signed certificate error when installing Python support in WSL](https://github.com/microsoft/vscode/issues/131836#issuecomment-909983815) in the Visual Studio Code issue tracker.
1. In your VS Code `settings.json`, set `"http.systemCertificates": true`. The default value is `true`, so you might not need to change this value.
1. Follow the instructions for your operating system:

View File

@ -10,7 +10,7 @@ If you encounter any issues with the GitLab Workflow extension for VS Code, or h
1. Check the [extension documentation](_index.md)
for known issues and solutions.
1. Report bugs or request features in the
[`gitlab-vscode-extension` issue queue](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues).
[`gitlab-vscode-extension` issue tracker](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues).
For troubleshooting VS Code for GitLab Duo Code Suggestions,
see [Troubleshooting Code Suggestions for VS Code](../../user/project/repository/code_suggestions/troubleshooting.md#vs-code-troubleshooting).

View File

@ -908,7 +908,7 @@ Prerequisites:
The name of the attribute must contain the groups that a user belongs to.
To tell GitLab where to find these groups, add a `groups_attribute:`
element to your SAML settings.
element to your SAML settings. This attribute is case-sensitive.
### Required groups

View File

@ -93,9 +93,9 @@ The rest of this guide assumes you already have a instance of GitLab up and runn
Operating GitLab Duo Self-Hosted requires both a GitLab Ultimate license and a GitLab Duo Enterprise license. The GitLab Ultimate license works with either online or offline licensing options. This documentation assumes that both licenses have been previously obtained and are available for implementation.
![License screenshot](img/self_hosted_model/license_ultimate_onlinelicense.png)
![License screenshot](img/self_hosted_model/license_ultimate_onlinelicense_v17_10.png)
![License screenshot](img/self_hosted_model/license_duo_enterprise.png)
![License screenshot](img/self_hosted_model/license_duo_enterprise_v17_10.png)
#### SSL/TLS
@ -162,7 +162,7 @@ Designed for simplicity and performance, Ollama empowers users to harness the po
### AI Gateway
While the official installation guide is available [here](../../install/install_ai_gateway.md), here's a streamlined approach for setting up the AI Gateway. Note that, as of January 2025, the image `gitlab/model-gateway:self-hosted-v17.6.0-ee` has been verified to work with GitLab 17.7.
While the official installation guide is available [here](../../install/install_ai_gateway.md), here's a streamlined approach for setting up the AI Gateway. Note that, as of January 2025, the image `gitlab/model-gateway:self-hosted-v17.6.0-ee` has been verified to work with GitLab 17.7.
1. Please ensure that ...
@ -247,7 +247,7 @@ During the initial setup and testing phase, you can set AIGW_AUTH__BYPASS_EXTERN
```
The command should show the installed model in the list.
![Installed Model List in Ollama](img/self_hosted_model/ollama_installed_model.png)
![Installed Model List in Ollama](img/self_hosted_model/ollama_installed_model_v17_10.png)
## Integration
@ -271,7 +271,7 @@ During the initial setup and testing phase, you can set AIGW_AUTH__BYPASS_EXTERN
- Toggle the switch in the "GitLab Duo Enterprise" column to enable Duo for the root user
- The toggle button should turn blue when enabled
![Enable Duo for root user](img/self_hosted_model/duo_enable_root.png)
![Enable Duo for root user](img/self_hosted_model/duo_enable_root_v17_10.png)
{{< alert type="note" >}}
@ -286,7 +286,7 @@ Enabling Duo for just the root user is sufficient for initial setup and testing.
- Navigate to Admin Area > GitLab Duo > "Self-hosted models"
- Click "Add self-hosted model" button
![Configure Self-Hosted Model](img/self_hosted_model/self-hosted-model-initial.png)
![Configure Self-Hosted Model](img/self_hosted_model/self-hosted-model-initial_v17_10.png)
1. Configure Model Settings
@ -303,7 +303,7 @@ Enabling Duo for just the root user is sufficient for initial setup and testing.
- **Model identifier**: Enter `custom_openai/mistral:instruct`
- **API Key**: Enter any placeholder text (for example, `test`) as this field cannot be left blank
![Configure Mistral Model](img/self_hosted_model/self-hosted-model-initial_mistral.png)
![Configure Mistral Model](img/self_hosted_model/self-hosted-model-initial_mistral_v17_10.png)
1. Enable AI Features
@ -314,7 +314,7 @@ Enabling Duo for just the root user is sufficient for initial setup and testing.
- GitLab Duo Chat > General Chat
- Select your deployed model from the dropdown list for each feature
![Mapping model and duo feature](img/self_hosted_model/self-hosted-model-mapping.png)
![Mapping model and duo feature](img/self_hosted_model/self-hosted-model-mapping_v17_10.png)
These settings establish the connection between your GitLab instance and the self-hosted Ollama model through the AI Gateway, enabling AI-powered features within GitLab.
@ -324,4 +324,4 @@ These settings establish the connection between your GitLab instance and the sel
1. The GitLab Duo Chat icon should appear in the top right corner
1. This indicates successful integration between GitLab and the AI Gateway
![Talk in Duo Chat](img/self_hosted_model/self-hosted-model-talk-in-duochat.png)
![Talk in Duo Chat](img/self_hosted_model/self-hosted-model-talk-in-duochat_v17_10.png)

View File

@ -13,8 +13,13 @@ title: GitLab Duo add-ons
{{< /details >}}
You can purchase GitLab Duo seats to give users in your organization access to more GitLab features. GitLab Duo is only available for Premium and Ultimate customers.
Access to features provided by GitLab Duo is managed through seat assignment. GitLab Duo can be assigned to any user in your group namespace or instance.
GitLab Duo add-ons extend your Premium or Ultimate subscription with AI-powered features.
Use GitLab Duo to help accelerate development workflows, reduce repetitive coding tasks,
and gain deeper insights across your projects.
Purchase GitLab Duo seats and assign them to team members.
The seat-based model gives you control over feature access and cost management
based on your specific team needs.
## Purchase GitLab Duo

View File

@ -28,12 +28,16 @@ Software Bill of Materials, SBOM, or BOM.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For an overview, see [Project Dependency](https://www.youtube.com/watch?v=ckqkn9Tnbw4).
## Prerequisites
## Set up the dependency list
To list your project's dependencies the SBOM document must:
To list your project's dependencies, run [Dependency Scanning](../dependency_scanning/_index.md)
or [Container Scanning](../container_scanning/_index.md) on the default branch of your project.
- Comply with [the CycloneDX specification](https://github.com/CycloneDX/specification) version `1.4`, `1.5`, or `1.6`. Online validator available on [CycloneDX Web Tool](https://cyclonedx.github.io/cyclonedx-web-tool/validate).
- Be uploaded as [a CI/CD artifact report](../../../ci/yaml/artifacts_reports.md#artifactsreportscyclonedx) from a successful pipeline on the default branch.
The dependency list also shows dependencies from any
[CycloneDX reports](../../../ci/yaml/artifacts_reports.md#artifactsreportscyclonedx) uploaded from the
latest default branch pipeline.
The CycloneDX reports must comply with [the CycloneDX specification](https://github.com/CycloneDX/specification) version `1.4`, `1.5`, or `1.6`.
You can use the [CycloneDX Web Tool](https://cyclonedx.github.io/cyclonedx-web-tool/validate) to validate CycloneDX reports.
{{< alert type="note" >}}
@ -42,18 +46,6 @@ Although this is not mandatory for populating the dependency list, the SBOM docu
{{< /alert >}}
GitLab already generates this document when the following requirements are met:
- The [Dependency Scanning](../dependency_scanning/_index.md)
or [Container Scanning](../container_scanning/_index.md)
CI job must be configured for your project.
- Your project uses at least one of the
[languages and package managers](../dependency_scanning/_index.md#supported-languages-and-package-managers)
supported by Gemnasium.
- A successful pipeline was run on the default branch.
You should not change the default behavior of allowing the
[application security jobs](../detect/_index.md) to fail.
## View project dependencies
{{< history >}}

View File

@ -116,7 +116,7 @@ To improve your security, try these features:
| [GitLab Duo for the CLI](../../editor_extensions/gitlab_cli/_index.md#gitlab-duo-for-the-cli) | Ultimate | GitLab Duo Enterprise | GitLab.com, Self-managed, GitLab Dedicated | General availability |
| [Merge Request Summary](../project/merge_requests/duo_in_merge_requests.md#generate-a-description-by-summarizing-code-changes) | Ultimate | GitLab Duo Enterprise | GitLab.com | Beta |
| [Code Review](../project/merge_requests/duo_in_merge_requests.md#have-gitlab-duo-review-your-code) | Ultimate | GitLab Duo Enterprise | GitLab.com | Experiment |
| [Code Review Summary](../project/merge_requests/duo_in_merge_requests.md#summarize-a-code-review) | Ultimate | GitLab Duo Enterprise | GitLab.com | Experiment |
| [Code Review Summary](../project/merge_requests/duo_in_merge_requests.md#summarize-a-code-review) | Ultimate | GitLab Duo Enterprise | GitLab.com, Self-managed | Experiment |
| [Merge Commit Message Generation](../project/merge_requests/duo_in_merge_requests.md#generate-a-merge-commit-message) | Ultimate | GitLab Duo Enterprise | GitLab.com, Self-managed, GitLab Dedicated | General availability |
| [Root Cause Analysis](../gitlab_duo_chat/examples.md#troubleshoot-failed-cicd-jobs-with-root-cause-analysis) | Ultimate | GitLab Duo Enterprise | GitLab.com, Self-managed, GitLab Dedicated | General availability |
| [Vulnerability Explanation](../application_security/vulnerabilities/_index.md#explaining-a-vulnerability) | Ultimate | GitLab Duo Enterprise | GitLab.com, Self-managed, GitLab Dedicated | General availability |

View File

@ -16,7 +16,7 @@ GitLab Duo is powered by large language models (LLMs), with data sent through an
To use GitLab Duo on a self-managed instance, you can do either of the following:
- Use the LLMs and the cloud-based AI gateway that's hosted by GitLab. This is the default option.
- [Use LLMs from the supported list and self-host the AI gateway and LLMs](../../administration/gitlab_duo_self_hosted/_index.md).
- [Use LLMs from the supported list and self-host the AI gateway and LLMs](../../administration/gitlab_duo_self_hosted/_index.md#set-up-a-gitlab-duo-self-hosted-infrastructure).
This option provides full control over your data and security.
{{< alert type="note" >}}

View File

@ -13,12 +13,11 @@ title: Contribution analytics
{{< /details >}}
Contribution analytics provide an overview of the
[contribution events](../../profile/contributions_calendar.md#user-contribution-events) made by your group's members.
[contribution events](../../profile/contributions_calendar.md#user-contribution-events) your group's members made in the last week, month, or three months.
Interactive bar charts and a detailed table show contribution events
(such as push events, issues, and merge requests) by group member.
Contribution analytics provides an overview of the contribution events made by the group's members, offering valuable insights into team activity and individual performance.
This report includes visualizations like bar charts and a detailed table that breaks down contribution types (such as push events, issues, and merge requests) by group member.
Use contribution analytics data visualizations for:
Use contribution analytics to get insights into team activity and individual performance, and use this information for:
- Workload balancing: Analyze your group's contributions over a period of time, and identify group members who are high performers or may benefit from additional support.
- Team collaboration: Evaluate the balance of contributions, such as code pushes versus reviews or approvals, to ensure collaborative development practices.

View File

@ -168,7 +168,7 @@ enabling Group Sync in GitLab.
To configure SAML Group Sync for GitLab Self-Managed:
1. Configure the [SAML OmniAuth Provider](../../../integration/saml.md).
1. Ensure your SAML identity provider sends an attribute statement with the same name as the value of the `groups_attribute` setting. See the following provider configuration example in `/etc/gitlab/gitlab.rb` for reference:
1. Ensure your SAML identity provider sends an attribute statement with the same name as the value of the `groups_attribute` setting. This attribute is case-sensitive. See the following provider configuration example in `/etc/gitlab/gitlab.rb` for reference:
```ruby
gitlab_rails['omniauth_providers'] = [

View File

@ -81,7 +81,7 @@ information, see the following endpoints:
GitLab CI/CD doesn't provide a built-in way to remove your container images. This example uses a
third-party tool called [`regctl`](https://github.com/regclient/regclient) that talks to the GitLab Registry API.
For assistance with this third-party tool, see [the issue queue for regclient](https://github.com/regclient/regclient/issues).
For assistance with this third-party tool, see [the issue tracker for regclient](https://github.com/regclient/regclient/issues).
{{< /alert >}}

View File

@ -428,8 +428,8 @@ The following table lists all GitLab-specific email headers:
| `X-GitLab-ConfidentialIssue` | The boolean value indicating issue confidentiality for notifications. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/222908) in GitLab 16.0. |
| `X-GitLab-Discussion-ID` | The ID of the thread the comment belongs to, in notification emails for comments. |
| `X-GitLab-Group-Id` | The group's ID. Only present on notification emails for [epics](../group/epics/_index.md). |
| `X-GitLab-Group-Path` | The group's path. Only present on notification emails for [epics](../group/epics/_index.md) |
| `X-GitLab-NotificationReason` | The reason for the notification. [See possible values.](#x-gitlab-notificationreason). |
| `X-GitLab-Group-Path` | The group's path. Only present on notification emails for [epics](../group/epics/_index.md). |
| `X-GitLab-NotificationReason` | The reason for the notification. [See possible values](#x-gitlab-notificationreason). |
| `X-GitLab-Pipeline-Id` | The ID of the pipeline the notification is for, in notification emails for pipelines. |
| `X-GitLab-Project-Id` | The project's ID. |
| `X-GitLab-Project-Path` | The project's path. |

View File

@ -113,6 +113,7 @@ Interactions with GitLab Duo can help to improve the suggestions and feedback as
{{< history >}}
- [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10466) in GitLab 16.0 as an [experiment](../../../policy/development_stages_support.md#experiment).
- Feature flag `summarize_my_code_review` [enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/182448) in GitLab 17.10.
{{< /history >}}

View File

@ -325,6 +325,7 @@ Merge requests are related to these features:
Revert changes from any commit from a merge request.
- [Keyboard shortcuts](../../../shortcuts.md#merge-requests):
Access and change specific parts of a merge request with keyboard commands.
- [Value stream analytics](../../../group/value_stream_analytics/_index.md): Track key merge request steps (such as `reviewed` and `approved`) to identify where most time is spent in the software development lifecycle. This information helps uncover actionable insights to optimize merge request workflows for groups and projects, and improve overall developer productivity. Read more about [How we reduced MR review time with value stream analytics](https://about.gitlab.com/blog/2025/02/20/how-we-reduced-mr-review-time-with-value-stream-management/).
## Related topics

View File

@ -10,6 +10,7 @@
module Gitlab
module Auth
class DpopToken
MAX_EXPIRY_TIME_IN_SECS = 300
KID_DELIMITER = ':'
attr_reader :data, :payload, :header
@ -25,22 +26,24 @@ module Gitlab
nil, # we do not pass a key here as we are not checking the signature
false # we are not verifying the signature or claims
)
rescue JWT::DecodeError => e
rescue StandardError => e
raise Gitlab::Auth::DpopValidationError, "Malformed JWT, unable to decode. #{e.message}"
end
# All comparisons should be case-sensitive, using secure comparison
# See https://www.rfc-editor.org/rfc/rfc7515#section-4.1.1
raise Gitlab::Auth::DpopValidationError, 'Missing required claim, typ' unless header['typ'].present?
raise Gitlab::Auth::DpopValidationError, 'Invalid typ value in JWT' unless header['typ'].casecmp?('dpop+jwt')
raise Gitlab::Auth::DpopValidationError, 'No kid in JWT, unable to fetch key' if header['kid'].nil?
raise Gitlab::Auth::DpopValidationError, 'No kid in JWT, unable to fetch key' unless header['kid'].present?
raise Gitlab::Auth::DpopValidationError, 'Missing required claim, jwk' unless header['jwk'].present?
# Check header[alg] is one of SUPPORTED_JWS_ALGORITHMS.
# Remove when support for ED25519 is added
# This checks for 'alg' in the header and exits early
unless header['alg'].casecmp?('RS512')
raise Gitlab::Auth::DpopValidationError,
'Currently only RSA keys are supported'
'Currently only RS512 algorithm is supported'
end
# Check the format of header[kid] (ALGORITHM DELIMITER b64(HASH))
@ -53,6 +56,17 @@ module Gitlab
raise Gitlab::Auth::DpopValidationError, 'Unsupported fingerprint algorithm in kid'
end
# Check exp is not set to more than
# iat + 5.minutes. We do not want users to
# make JWTs with long expiration times. The
# spec currently states 5 minutes (300 seconds)
raise Gitlab::Auth::DpopValidationError, 'exp is missing' unless payload['exp'].present?
raise Gitlab::Auth::DpopValidationError, 'iat is missing' unless payload['iat'].present?
if payload['exp'] - payload['iat'] > MAX_EXPIRY_TIME_IN_SECS
raise Gitlab::Auth::DpopValidationError, 'exp must not exceed iat by more than 5 minutes'
end
return if header.dig('jwk', 'kty').eql?('RSA')
raise Gitlab::Auth::DpopValidationError, 'JWK algorithm must be RSA'

View File

@ -49,6 +49,8 @@ module Gitlab
jwk = header['jwk']
begin
raise Gitlab::Auth::DpopValidationError, 'JWK contains private key' if JWT::JWK::RSA.import(jwk).private?
unless openssh_public_key.to_s == OpenSSL::PKey.read(JWT::JWK::RSA.import(jwk).public_key.to_pem).to_s
raise 'Failed to parse JWK: invalid JWK'
end
@ -69,13 +71,17 @@ module Gitlab
openssh_public_key,
true,
{
required_claims: %w[exp ath iat],
required_claims: %w[exp ath iat jti],
algorithm: algorithm,
verify_iat: true
}
)
rescue JWT::DecodeError => e
raise Gitlab::Auth::DpopValidationError, "Malformed JWT, unable to decode. #{e.message}"
rescue JWT::ExpiredSignature
raise Gitlab::Auth::DpopValidationError, "Signature expired"
rescue JWT::MissingRequiredClaim => e
raise Gitlab::Auth::DpopValidationError, e.message
rescue JWT::InvalidIatError
raise Gitlab::Auth::DpopValidationError, "Invalid IAT value"
end
def signing_key_for_user!

View File

@ -73,5 +73,80 @@ unless Rails.env.production?
todo_dir.delete_inspected
end
end
desc 'Update documentation of all cops'
task :docs do
require 'yard'
YARD::Rake::YardocTask.new(:yard_rubocop_docs) do |task|
task.files = ['rubocop/cop/**/*.rb'] + gitlab_styles_cops
task.options = ['--no-output']
end
Rake::Task[:yard_rubocop_docs].invoke
cops_registry = RuboCop::Cop::Registry.new
gitlab_cops.each { |cop| cops_registry.enlist(cop) }
FileUtils.rm_rf('tmp/docs/')
FileUtils.rm_rf('rubocop/docs-hugo/content/doc/')
require_relative '../../rubocop/cops_documentation_generator'
departments = cops_registry.map { |cop_class| cop_class.to_s.split("::")[-2] }.uniq
formatter = RuboCop::CopsDocumentationGenerator::Formatters::HugoMarkdown.new
base_dir = 'tmp'
RuboCop::CopsDocumentationGenerator.new(departments:, cops_registry:, formatter:, base_dir:).call
puts "Moving content to `rubocop/docs-hugo/content/doc/`..."
FileUtils.mv('tmp/docs/modules/ROOT/pages/', 'rubocop/docs-hugo/content/doc/')
FileUtils.rm_rf('tmp/docs/')
update_headers_for_cop_documentations
end
def update_headers_for_cop_documentations
documentation_metadata = <<~META
---
title: %{title}
---
META
Dir.glob('rubocop/docs-hugo/content/doc/*.md').each do |file|
content = File.read(file)
page_h1_match = content.match(/^# +(.*)\n/)
title = page_h1_match.present? ? "#{page_h1_match[1]} RuboCop docs" : 'RuboCop docs'
content.sub!(page_h1_match[0], '') if page_h1_match
current_documentation_metadata = format(documentation_metadata, title:)
File.write(file, current_documentation_metadata + content) unless content.start_with?("---\n")
end
end
def gitlab_cops
# Pre-load existing cops so we can exclude them from the list of cops we generate documentation for
require 'rubocop'
require 'rubocop-capybara'
require 'rubocop-factory_bot'
require 'rubocop-graphql'
require 'rubocop-performance'
require 'rubocop-rails'
require 'rubocop-rspec'
require 'rubocop-rspec_rails'
existing_cops = RuboCop::Cop::Registry.global.to_a
Dir['rubocop/cop/**/*.rb'].each { |file| require_relative File.join('../..', file) }
gitlab_styles_cops.each { |file| require file }
RuboCop::Cop::Registry.global.to_a - existing_cops
end
def gitlab_styles_cops
return @gitlab_styles_cops if defined?(@gitlab_styles_cops)
gem_dir = Gem::Specification.find_by_name('gitlab-styles').gem_dir
@gitlab_styles_cops ||= Dir["#{gem_dir}/lib/rubocop/cop/**/*.rb"]
end
end
end

View File

@ -7621,10 +7621,10 @@ msgstr ""
msgid "Applying multiple commands"
msgstr ""
msgid "Applying suggestion..."
msgid "Applying suggestions…"
msgstr ""
msgid "Applying suggestions..."
msgid "Applying suggestion"
msgstr ""
msgid "Approval Rules"
@ -12061,9 +12061,6 @@ msgstr ""
msgid "Checking approval status"
msgstr ""
msgid "Checking branch availability..."
msgstr ""
msgid "Checking branch availability…"
msgstr ""
@ -12076,7 +12073,7 @@ msgstr ""
msgid "Checking group path availability..."
msgstr ""
msgid "Checking if merge request can be merged..."
msgid "Checking if merge request can be merged"
msgstr ""
msgid "Checking source validity"
@ -14587,7 +14584,7 @@ msgstr ""
msgid "Compare"
msgstr ""
msgid "Compare %{oldCommitId}...%{newCommitId}"
msgid "Compare %{oldCommitId}%{newCommitId}"
msgstr ""
msgid "Compare Branches"
@ -25146,6 +25143,9 @@ msgstr ""
msgid "Filter results..."
msgstr ""
msgid "Filter results…"
msgstr ""
msgid "Filter users"
msgstr ""
@ -28493,7 +28493,7 @@ msgstr ""
msgid "GroupSettings|Retry"
msgstr ""
msgid "GroupSettings|Saving..."
msgid "GroupSettings|Saving"
msgstr ""
msgid "GroupSettings|Select a project"
@ -28742,7 +28742,7 @@ msgstr ""
msgid "GroupsNew|You can also %{linkStart}import an existing group%{linkEnd}."
msgstr ""
msgid "GroupsNew|e.g. h8d3f016698e..."
msgid "GroupsNew|e.g. h8d3f016698e"
msgstr ""
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
@ -28778,7 +28778,7 @@ msgstr ""
msgid "Groups|Changing group URL can have unintended side effects. %{linkStart}Learn more%{linkEnd}."
msgstr ""
msgid "Groups|Checking group URL availability..."
msgid "Groups|Checking group URL availability"
msgstr ""
msgid "Groups|Create and add README"
@ -30650,7 +30650,7 @@ msgstr ""
msgid "Incident|Timeline text"
msgstr ""
msgid "Incident|Timeline text..."
msgid "Incident|Timeline text"
msgstr ""
msgid "Include additional optional data in Service Ping. To enable optional data, first enable Service Ping."
@ -35983,9 +35983,6 @@ msgstr ""
msgid "Merge when pipeline succeeds"
msgstr ""
msgid "Merge..."
msgstr ""
msgid "MergeChecks|All threads must be resolved"
msgstr ""
@ -36025,7 +36022,7 @@ msgstr ""
msgid "MergeConflict|Commit to source branch"
msgstr ""
msgid "MergeConflict|Committing..."
msgid "MergeConflict|Committing"
msgstr ""
msgid "MergeConflict|Edit inline"
@ -36292,6 +36289,9 @@ msgstr ""
msgid "Merges this merge request immediately."
msgstr ""
msgid "Merge…"
msgstr ""
msgid "Mermaid diagram"
msgstr ""
@ -47670,12 +47670,12 @@ msgstr ""
msgid "Refresh the page to view sync status"
msgstr ""
msgid "Refreshing in a second to show the updated status..."
msgid_plural "Refreshing in %d seconds to show the updated status..."
msgid "Refreshing in a second to show the updated status"
msgid_plural "Refreshing in %d seconds to show the updated status"
msgstr[0] ""
msgstr[1] ""
msgid "Refreshing..."
msgid "Refreshing"
msgstr ""
msgid "Regenerate export"
@ -48321,7 +48321,7 @@ msgstr ""
msgid "Reopening %{issuableType}..."
msgstr ""
msgid "Reopening..."
msgid "Reopening"
msgstr ""
msgid "Reopens this %{quick_action_target}."
@ -49043,7 +49043,7 @@ msgstr ""
msgid "Results limit reached"
msgstr ""
msgid "Results pending..."
msgid "Results pending"
msgstr ""
msgid "Resume"
@ -56157,7 +56157,7 @@ msgstr ""
msgid "Start merge train"
msgstr ""
msgid "Start merge train..."
msgid "Start merge train"
msgstr ""
msgid "Start review"
@ -57646,7 +57646,7 @@ msgstr ""
msgid "Terraform|Job status"
msgstr ""
msgid "Terraform|Loading Terraform reports..."
msgid "Terraform|Loading Terraform reports"
msgstr ""
msgid "Terraform|Lock"
@ -58269,7 +58269,7 @@ msgstr ""
msgid "The invitation was successfully resent."
msgstr ""
msgid "The issue was successfully promoted to an epic. Redirecting to epic..."
msgid "The issue was successfully promoted to an epic. Redirecting to epic"
msgstr ""
msgid "The last owner cannot be set to awaiting"
@ -66807,6 +66807,9 @@ msgstr ""
msgid "Write milestone description..."
msgstr ""
msgid "Write milestone description…"
msgstr ""
msgid "Write your release notes or drag your files here…"
msgstr ""

View File

@ -9,29 +9,29 @@ module RuboCop
#
# @example
#
# # bad
# class SomeService
# def execute
# do_something_with(Current.organization)
# end
# end
#
# # good
# class SomeController < ApplicationController
# def create
# response = SomeService.new(organization: Current.organization).execute
# end
# end
#
# class SomeService
# def initialize(organization:)
# @organization = organization
# # bad
# class SomeService
# def execute
# do_something_with(Current.organization)
# end
# end
#
# def execute
# do_something_with(@organization)
# # good
# class SomeController < ApplicationController
# def create
# response = SomeService.new(organization: Current.organization).execute
# end
# end
#
# class SomeService
# def initialize(organization:)
# @organization = organization
# end
#
# def execute
# do_something_with(@organization)
# end
# end
# end
#
#
class AvoidCurrentOrganization < RuboCop::Cop::Base

View File

@ -9,26 +9,28 @@ module RuboCop
# must implement the method #handle_event(event) and
# must not override the method #perform(*args)
#
# # bad
# class MySubscriber
# include Gitlab::EventStore::Subscriber
# @example
#
# def perform(*args)
# end
# end
# # bad
# class MySubscriber
# include Gitlab::EventStore::Subscriber
#
# # bad
# class MySubscriber
# include Gitlab::EventStore::Subscriber
# end
# def perform(*args)
# end
# end
#
# # good
# class MySubscriber
# include Gitlab::EventStore::Subscriber
# # bad
# class MySubscriber
# include Gitlab::EventStore::Subscriber
# end
#
# def handle_event(event)
# end
# end
# # good
# class MySubscriber
# include Gitlab::EventStore::Subscriber
#
# def handle_event(event)
# end
# end
#
class EventStoreSubscriber < RuboCop::Cop::Base
SUBSCRIBER_MODULE_NAME = 'Gitlab::EventStore::Subscriber'

View File

@ -3,7 +3,7 @@
module RuboCop
module Cop
module Rails
# Cop that detects passing params as an argument without making them
# Cop that detects passing `params` as an argument without making them
# StrongParams first. Used to reduce the likelihood of input
# validation errors outside of ActiveModel. E.g. when a parameter
# is an array of strings instead of a single string, and that gets

View File

@ -2,8 +2,8 @@
module RuboCop
module Cop
# This class complements Rubocop::Cop::SidekiqRedisCall by disallowing the use of
# Gitlab::Redis::Queues with the exception of initialising Sidekiq and monitoring.
# This class complements `Rubocop::Cop::SidekiqRedisCall` by disallowing the use of
# `Gitlab::Redis::Queues` with the exception of initialising Sidekiq and monitoring.
class RedisQueueUsage < RuboCop::Cop::Base
MSG = 'Gitlab::Redis::Queues should only be used by Sidekiq initializers. '\
'Assignments or using its params to initialise another connection is not allowed.'

View File

@ -3,7 +3,7 @@
module RuboCop
module Cop
module Scalability
# This cop checks for `File` params in API
# This cop checks for `File` `params` in API
#
# @example
#

View File

@ -9,19 +9,19 @@ module RuboCop
#
# @example
#
# # bad
# class TroubleMakerWorker
# def perform
# # bad
# class TroubleMakerWorker
# def perform
# end
# end
# end
#
# # good
# class NiceWorker
# idempotent!
# # good
# class NiceWorker
# idempotent!
#
# def perform
# def perform
# end
# end
# end
#
class IdempotentWorker < RuboCop::Cop::Base
include CodeReuseHelpers

View File

@ -9,19 +9,19 @@ module RuboCop
#
# @example
#
# # bad
# class BadWorker
# def perform
# # bad
# class BadWorker
# def perform
# end
# end
# end
#
# # good
# class GoodWorker
# data_consistency :delayed
# # good
# class GoodWorker
# data_consistency :delayed
#
# def perform
# def perform
# end
# end
# end
#
class WorkerDataConsistency < RuboCop::Cop::Base
include CodeReuseHelpers

View File

@ -9,47 +9,47 @@ module RuboCop
#
# @example
#
# # bad
# class MyExample
# # Constant
# Translation = _('A translation.')
# # bad
# class MyExample
# # Constant
# Translation = _('A translation.')
#
# # Class scope
# field :foo, title: _('A title')
# # Class scope
# field :foo, title: _('A title')
#
# validates :title, :presence, message: _('is missing')
# validates :title, :presence, message: _('is missing')
#
# # Memoized
# def self.translations
# @cached ||= { text: _('A translation.') }
# # Memoized
# def self.translations
# @cached ||= { text: _('A translation.') }
# end
#
# included do # or prepended or class_methods
# self.error_message = _('Something went wrong.')
# end
# end
#
# included do # or prepended or class_methods
# self.error_message = _('Something went wrong.')
# # good
# class MyExample
# # Keep translations dynamic.
# Translation = -> { _('A translation.') }
# # OR
# def translation
# _('A translation.')
# end
#
# field :foo, title: -> { _('A title') }
#
# validates :title, :presence, message: -> { _('is missing') }
#
# def self.translations
# { text: _('A translation.') }
# end
#
# included do # or prepended or class_methods
# self.error_message = -> { _('Something went wrong.') }
# end
# end
# end
#
# # good
# class MyExample
# # Keep translations dynamic.
# Translation = -> { _('A translation.') }
# # OR
# def translation
# _('A translation.')
# end
#
# field :foo, title: -> { _('A title') }
#
# validates :title, :presence, message: -> { _('is missing') }
#
# def self.translations
# { text: _('A translation.') }
# end
#
# included do # or prepended or class_methods
# self.error_message = -> { _('Something went wrong.') }
# end
# end
#
class StaticTranslationDefinition < RuboCop::Cop::Base
MSG = <<~TEXT.tr("\n", ' ')

View File

@ -2,20 +2,20 @@
module RuboCop
module Cop
# rubocop:disable Lint/RedundantCopDisableDirective -- For examples
module Style
# rubocop:disable Lint/RedundantCopDisableDirective -- For examples
# Checks that rubocop inline disabling is formatted according
# Checks that RuboCop inline disabling is formatted according
# to guidelines.
# See: https://docs.gitlab.com/ee/development/rubocop_development_guide.html#disabling-rules-inline,
# https://gitlab.com/gitlab-org/gitlab/-/issues/428762
#
# @example
# # bad
# # rubocop:disable Some/Cop, Another/Cop
#
# # good
# # rubocop:disable Some/Cop, Another/Cop -- Some reason
#
# rubocop:enable Lint/RedundantCopDisableDirective
class InlineDisableAnnotation < RuboCop::Cop::Base
include RangeHelp
@ -47,5 +47,6 @@ module RuboCop
end
end
end
# rubocop:enable Lint/RedundantCopDisableDirective
end
end

View File

@ -0,0 +1,494 @@
# frozen_string_literal: true
require 'fileutils'
require 'yard'
# This file has been copied from https://github.com/rubocop/rubocop/blob/master/lib/rubocop/cops_documentation_generator.rb
# It has been altered to work with Markdown, and the changes will be pushed upstream
# rubocop:disable Lint/RedundantCopDisableDirective -- Don't remove the disables already in place from the upstream repo, so it's easier to merge back
# Class for generating documentation of all cops departments
# @api private
module RuboCop
class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength -- Exception already existed in upstream repo
module Formatters
class Markdown
def to_filename(base_name)
"#{base_name}.md"
end
def to_header(text, level: 1)
"\n#{'#' * level} #{text}\n\n"
end
def to_link(link_to, text, anchor: nil)
link_to = "#{link_to}##{anchor}" if anchor
"[#{text}](#{link_to})"
end
def to_bullet_point(text)
"- #{text}\n"
end
def to_anchor(anchor_text)
"<a name=\"#{anchor_text}\"></a>\n"
end
def to_comment(comment)
<<~COMMENT
<!---
#{comment}
-->
COMMENT
end
def to_code(ruby_code)
<<~CODE
```ruby
#{ruby_code.text.gsub('@good', '# good').gsub('@bad', '# bad').strip}
```
CODE
end
def to_table(headers, content)
table = "| #{headers.join(' | ')} |\n"
table += "| #{headers.map { |header| ('-' * header.length) }.join(' | ')} |\n"
content.each do |row|
table += "| #{row.join(' | ')} |\n"
end
table
end
end
class HugoMarkdown < Markdown
def to_link(link_to, text, anchor: nil)
link_to = "#{link_to}##{anchor}" if anchor
%{[#{text}]({{< ref "#{link_to}" >}})}
end
end
class Asciidoc
def to_filename(base_name)
"#{base_name}.adoc"
end
def to_header(text, level: 1)
"#{'=' * level} #{text}\n\n"
end
def to_link(link_to, text, anchor: nil)
if anchor
"xref:#{link_to}[#{text}#{anchor}]"
else
"xref:#{link_to}[#{text}]"
end
end
def to_code(ruby_code)
content = +"[source,ruby]\n----\n"
content << ruby_code.text.gsub('@good', '# good').gsub('@bad', '# bad').strip
content << "\n----\n"
content
end
def to_bullet_point(text)
"* #{text}\n"
end
def to_anchor(anchor_text)
"[##{anchor_text}]\n"
end
def to_comment(comment)
<<~COMMENT
////
#{comment}
////
COMMENT
end
def to_table(header, content)
table = ['|===', "| #{header.join(' | ')}\n\n"].join("\n")
marked_contents = content.map do |plain_content|
# Escape `|` with backslash to prevent the regexp `|` is not used as a table separator.
plain_content.map { |c| "| #{c.gsub('|', '\|')}" }.join("\n")
end
table << marked_contents.join("\n\n")
table << "\n|===\n"
end
end
end
include ::RuboCop::Cop::Documentation
CopData = Struct.new(
:cop, :description, :example_objects, :safety_objects, :see_objects, :config, keyword_init: true
)
# rubocop:disable Layout/HashAlignment -- Rule differs in upstream repo
STRUCTURE = {
name: ->(data) { cop_header(data.cop) },
required_ruby_version: ->(data) { required_ruby_version(data.cop) },
properties: ->(data) { properties(data.cop) },
description: ->(data) { "#{data.description}\n" },
safety: ->(data) { safety_object(data.safety_objects, data.cop) },
examples: ->(data) { examples(data.example_objects, data.cop) },
configuration: ->(data) { configurations(data.cop.department, data.config, data.cop) },
references: ->(data) { references(data.cop, data.see_objects) }
}.freeze
# rubocop:enable Layout/HashAlignment
# This class will only generate documentation for cops that belong to one of
# the departments given in the `departments` array. E.g. if we only wanted
# documentation for Lint cops:
#
# CopsDocumentationGenerator.new(departments: ['Lint']).call
#
# You can append additional information:
#
# callback = ->(data) { required_rails_version(data.cop) }
# CopsDocumentationGenerator.new(extra_info: { ruby_version: callback }).call
#
# This will insert the string returned from the lambda _after_ the section from RuboCop itself.
# See `CopsDocumentationGenerator::STRUCTURE` for available sections.
#
def initialize(
formatter: Formatters::Asciidoc.new, cops_registry: RuboCop::Cop::Registry.global,
departments: [], extra_info: {}, base_dir: Dir.pwd
)
@departments = departments.map(&:to_sym).sort!
@extra_info = extra_info
@formatter = formatter
@cops = cops_registry
@config = RuboCop::ConfigLoader.default_configuration
@base_dir = base_dir
@docs_path = "#{base_dir}/docs/modules/ROOT"
FileUtils.mkdir_p("#{@docs_path}/pages")
end
def call
YARD::Registry.load!
departments.each { |department| print_cops_of_department(department) }
print_table_of_contents
ensure
RuboCop::ConfigLoader.default_configuration = nil
end
private
attr_reader :departments, :cops, :config, :docs_path, :formatter
def cops_of_department(department)
cops.with_department(department).sort!
end
def cops_body(data)
check_examples_to_have_the_default_enforced_style!(data.example_objects, data.cop)
content = +''
STRUCTURE.each do |section, block|
content << instance_exec(data, &block)
content << @extra_info[section].call(data) if @extra_info[section]
end
content
end
def check_examples_to_have_the_default_enforced_style!(example_objects, cop)
return if example_objects.none?
examples_describing_enforced_style = example_objects.map(&:name).grep(/EnforcedStyle:/)
return if examples_describing_enforced_style.none?
if examples_describing_enforced_style.index { |name| name.match?('default') }.nonzero?
raise "Put the example with the default EnforcedStyle on top for #{cop.cop_name}"
end
return if examples_describing_enforced_style.any? { |name| name.match?('default') }
raise "Specify the default EnforcedStyle for #{cop.cop_name}"
end
def examples(example_objects, cop)
return '' if example_objects.none?
example_objects.each_with_object(cop_subsection('Examples', cop).dup) do |example, content|
content << "\n" unless content.end_with?("\n\n")
content << example_header(example.name, cop) unless example.name == ''
content << formatter.to_code(example)
end
end
def safety_object(safety_objects, cop)
return '' if safety_objects.all? { |s| s.text.blank? }
safety_objects.each_with_object(cop_subsection('Safety', cop).dup) do |safety_object, content|
next if safety_object.text.blank?
content << "\n" unless content.end_with?("\n\n")
content << safety_object.text
content << "\n"
end
end
def required_ruby_version(cop)
return '' unless cop.respond_to?(:required_minimum_ruby_version)
if cop.required_minimum_ruby_version
requirement = cop.required_minimum_ruby_version
elsif cop.required_maximum_ruby_version
requirement = "<= #{cop.required_maximum_ruby_version}"
else
return ''
end
"NOTE: Requires Ruby version #{requirement}\n\n"
end
# rubocop:disable Metrics/MethodLength -- Exception already existed in upstream repo
def properties(cop)
header = [
'Enabled by default', 'Safe', 'Supports autocorrection', 'Version Added',
'Version Changed'
]
autocorrect = if cop.support_autocorrect? # rubocop:disable Cop/LineBreakAroundConditionalBlock -- Rule differs in upstream repo
context = cop.new.always_autocorrect? ? 'Always' : 'Command-line only'
"#{context}#{' (Unsafe)' unless cop.new(config).safe_autocorrect?}"
else
'No'
end
cop_config = config.for_cop(cop)
content = [[
cop_status(cop_config.fetch('Enabled')),
cop_config.fetch('Safe', true) ? 'Yes' : 'No',
autocorrect,
cop_config.fetch('VersionAdded', '-'),
cop_config.fetch('VersionChanged', '-')
]]
"#{formatter.to_table(header, content)}\n"
end
# rubocop:enable Metrics/MethodLength
def cop_header(cop)
content = +"\n"
content << formatter.to_anchor(to_anchor(cop.cop_name))
content << formatter.to_header(cop.cop_name, level: 2)
content
end
def cop_subsection(title, cop)
content = +"\n"
content << formatter.to_anchor("#{to_anchor(title)}-#{to_anchor(cop.cop_name)}")
content << formatter.to_header(title, level: 3)
content
end
def example_header(title, cop)
content = +formatter.to_anchor("#{to_anchor(title)}-#{to_anchor(cop.cop_name)}")
content << formatter.to_header(title, level: 4)
content
end
def configurations(department, pars, cop) # rubocop:disable Metrics/MethodLength -- Rule differs in upstream repo
return '' if pars.empty?
header = ['Name', 'Default value', 'Configurable values']
configs = pars
.each_key
.reject { |key| key.start_with?('Supported') }
.reject { |key| key.start_with?('AllowMultipleStyles') }
content = configs.map do |name|
configurable = configurable_values(pars, name)
default = format_table_value(pars[name])
[configuration_name(department, name), default, configurable]
end
cop_subsection('Configurable attributes', cop) + formatter.to_table(header, content)
end
def configuration_name(department, name)
return name unless name == 'AllowMultilineFinalElement'
filename = formatter.to_filename(department_to_basename(department))
"xref:#{filename}#allowmultilinefinalelement[AllowMultilineFinalElement]"
end
# rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength -- Exception already existed in upstream repo
def configurable_values(pars, name)
case name
when /^Enforced/
supported_style_name = RuboCop::Cop::Util.to_supported_styles(name)
format_table_value(pars[supported_style_name])
when 'IndentationWidth'
'Integer'
when 'Database'
format_table_value(pars['SupportedDatabases'])
else
case pars[name]
when String
'String'
when Integer
'Integer'
when Float
'Float'
when true, false
'Boolean'
when Array
'Array'
else
''
end
end
end
# rubocop:enable Metrics/CyclomaticComplexity,Metrics/MethodLength
def format_table_value(val) # rubocop:disable Metrics/MethodLength -- Rule differs in upstream repo
value =
case val
when Array
if val.empty?
'`[]`'
else
val.map { |config| format_table_value(config) }.join(', ')
end
else
wrap_backtick(val.nil? ? '<none>' : val)
end
value.gsub("#{@base_dir}/", '').rstrip
end
def wrap_backtick(value)
if value.is_a?(String)
# Use `+` to prevent text like `**/*.gemspec`, `spec/**/*` from being bold.
value.include?('*') ? "`+#{value}+`" : "`#{value}`"
else
"`#{value}`"
end
end
def references(cop, see_objects) # rubocop:disable Metrics/AbcSize -- Exception already existed in upstream repo
cop_config = config.for_cop(cop)
urls = RuboCop::Cop::MessageAnnotator.new(config, cop.name, cop_config, {}).urls
return '' if urls.empty? && see_objects.empty?
content = cop_subsection('References', cop)
content << urls.map { |url| "* #{url}" }.join("\n")
content << "\n" unless urls.empty?
content << see_objects.map { |see| "* #{see.name}" }.join("\n")
content << "\n" unless see_objects.empty?
content
end
def footer_for_department(department)
return '' unless department == :Layout
filename = formatter.to_filename("#{department_to_basename(department)}_footer")
file = "#{docs_path}/partials/#{filename}"
return '' unless File.exist?(file)
"\ninclude::../partials/#{filename}[]\n"
end
# rubocop:disable Metrics/MethodLength -- Exception already existed in upstream repo
def print_cops_of_department(department)
selected_cops = cops_of_department(department)
content = formatter.to_comment(+<<~HEADER)
Do NOT edit this file by hand directly, as it is automatically generated.
Please make any necessary changes to the cop documentation within the source files themselves.
HEADER
content += formatter.to_header(department)
selected_cops.each { |cop| content << print_cop_with_doc(cop) }
content << footer_for_department(department)
file_name = formatter.to_filename("#{docs_path}/pages/#{department_to_basename(department)}")
File.open(file_name, 'w') do |file|
puts "* generated #{file_name}"
file.write("#{content.strip}\n")
end
end
# rubocop:enable Metrics/MethodLength
def print_cop_with_doc(cop) # rubocop:todo Metrics/AbcSize, Metrics/MethodLength -- Exception already existed in upstream repo
cop_config = config.for_cop(cop)
non_display_keys = %w[
AutoCorrect Description Enabled StyleGuide Reference Safe SafeAutoCorrect VersionAdded
VersionChanged
]
pars = cop_config.reject { |k| non_display_keys.include? k }
description = 'No documentation'
example_objects = safety_objects = see_objects = []
cop_code(cop) do |code_object|
description = code_object.docstring unless code_object.docstring.blank?
example_objects = code_object.tags('example')
safety_objects = code_object.tags('safety')
see_objects = code_object.tags('see')
end
data = CopData.new(cop: cop, description: description, example_objects: example_objects,
safety_objects: safety_objects, see_objects: see_objects, config: pars) # rubocop:disable Layout/ArgumentAlignment -- Exception already existed in upstream repo
cops_body(data)
end
def cop_code(cop)
YARD::Registry.all(:class).detect do |code_object|
next unless RuboCop::Cop::Badge.for(code_object.to_s) == cop.badge
yield code_object
end
end
def table_of_content_for_department(department)
type_title = department[0].upcase + department[1..]
filename = formatter.to_filename(department_to_basename(department))
content = +formatter.to_header("Department #{formatter.to_link(filename, type_title)}", level: 3)
cops_of_department(department).each do |cop|
anchor = to_anchor(cop.cop_name)
content << formatter.to_bullet_point(formatter.to_link(filename, cop.cop_name, anchor:))
end
content
end
def print_table_of_contents
path = formatter.to_filename("#{docs_path}/pages/cops")
File.write(path, table_contents) and return unless File.exist?(path) # rubocop:disable Style/AndOr -- Rule differs in upstream repo
original = File.read(path)
content = +"// START_COP_LIST\n\n"
content << table_contents
content << "\n// END_COP_LIST"
content = original.sub(%r{// START_COP_LIST.+// END_COP_LIST}m, content)
File.write(path, content)
end
def table_contents
departments.map { |department| table_of_content_for_department(department) }.join("\n")
end
def cop_status(status)
return 'Disabled' unless status
status == 'pending' ? 'Pending' : 'Enabled'
end
# HTML anchor are somewhat limited in what characters they can contain, just
# accept a known-good subset. As long as it's consistent it doesn't matter.
#
# Style/AccessModifierDeclarations => styleaccessmodifierdeclarations
# OnlyFor: [] (default) => onlyfor_-__-_default_
def to_anchor(title)
title.delete('/').tr(' ', '-').gsub(/[^a-zA-Z0-9-]/, '_').downcase
end
end
end
# rubocop:enable Lint/RedundantCopDisableDirective

4
rubocop/docs-hugo/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
public/
resources/_gen/
.hugo_build.lock
content/doc/

View File

@ -0,0 +1,10 @@
---
title: "GitLab RuboCop Documentation"
type: page
cascade:
_target:
kind: home
layout: single
---
{{< readFile "doc/cops.md" >}}

5
rubocop/docs-hugo/go.mod Normal file
View File

@ -0,0 +1,5 @@
module gitlab.com/gitlab-org/gitlab/rubocop/docs-hugo
go 1.23
require github.com/nanxiaobei/hugo-paper v0.0.0-20241205182052-8eeb1e8a513d // indirect

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