Add latest changes from gitlab-org/gitlab@master
|
|
@ -126,3 +126,6 @@ config/helpers/tailwind/css_in_js.js
|
|||
|
||||
# ruby-lsp
|
||||
.index.yml
|
||||
|
||||
# Rubocop cop documentation generation
|
||||
.yardoc
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: []
|
||||
|
|
|
|||
4
Gemfile
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>`,
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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)'),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -283,7 +283,7 @@ export default {
|
|||
}
|
||||
|
||||
if (this.status === PIPELINE_FAILED_STATE || this.isPipelineFailed) {
|
||||
return __('Merge...');
|
||||
return __('Merge…');
|
||||
}
|
||||
|
||||
return __('Merge');
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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__(
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
table_name: sbom_occurrences
|
||||
classes:
|
||||
- Sbom::DependencyPath
|
||||
- Sbom::Occurrence
|
||||
feature_categories:
|
||||
- dependency_management
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
08c64374ddc12f0f15bda6698579766a6e80474eddbd21293ba297119a477832
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
|
@ -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.
|
||||
|
||||

|
||||

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

|
||||

|
||||
|
||||
## General Translation Guidelines
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 338 KiB After Width: | Height: | Size: 338 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
|
@ -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;
|
||||
|
||||

|
||||

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

|
||||

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

|
||||

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

|
||||

|
||||
|
||||
### Using `ruby-prof`
|
||||
|
||||
|
|
@ -381,7 +381,7 @@ end
|
|||
::Ci::DestroyPipelineService.new(project, user).execute(Ci::Pipeline.last)
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
Here, we can see that we call `Ci::GenerateKubeconfigService` ~2k times.
|
||||
This is a good indicator that we need to investigate this.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

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

|
||||

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

|
||||

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

|
||||

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

|
||||

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

|
||||

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

|
||||

|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 >}}
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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" >}}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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'] = [
|
||||
|
|
|
|||
|
|
@ -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 >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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 >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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", ' ')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
public/
|
||||
resources/_gen/
|
||||
.hugo_build.lock
|
||||
content/doc/
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: "GitLab RuboCop Documentation"
|
||||
type: page
|
||||
cascade:
|
||||
_target:
|
||||
kind: home
|
||||
layout: single
|
||||
---
|
||||
|
||||
{{< readFile "doc/cops.md" >}}
|
||||
|
|
@ -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
|
||||