Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-12-04 12:37:50 +00:00
parent d5d5781300
commit d4bd728d01
83 changed files with 1131 additions and 278 deletions

View File

@ -3,7 +3,6 @@
Layout/LineContinuationSpacing:
Exclude:
- 'app/controllers/projects/google_cloud/databases_controller.rb'
- 'app/graphql/types/environment_type.rb'
- 'app/helpers/application_settings_helper.rb'
- 'app/helpers/projects_helper.rb'
- 'app/helpers/tags_helper.rb'

View File

@ -8,7 +8,6 @@ Layout/SpaceInsideParens:
- 'spec/lib/gitlab/ci/config/entry/trigger_spec.rb'
- 'spec/lib/gitlab/ci/parsers/security/common_spec.rb'
- 'spec/lib/gitlab/ci/parsers_spec.rb'
- 'spec/lib/gitlab/ci/pipeline/seed/build_spec.rb'
- 'spec/lib/gitlab/ci/reports/test_suite_spec.rb'
- 'spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb'
- 'spec/lib/gitlab/ci/templates/MATLAB_spec.rb'

View File

@ -1,3 +0,0 @@
The content of this document was moved to [another location](https://docs.gitlab.com/ee/development/contributing/).
<!-- This redirect file can be deleted after <2023-10-01>. -->

View File

@ -1,6 +1,5 @@
import { Blockquote } from '@tiptap/extension-blockquote';
import { wrappingInputRule } from '@tiptap/core';
import { getParents } from '~/lib/utils/dom_utils';
import { getMarkdownSource } from '../services/markdown_sourcemap';
export default Blockquote.extend({
@ -20,12 +19,7 @@ export default Blockquote.extend({
multiline: {
default: false,
parseHTML: (element) => {
const source = getMarkdownSource(element);
const parentsIncludeBlockquote = getParents(element).some(
(p) => p.nodeName.toLowerCase() === 'blockquote',
);
return source && !source.startsWith('>') && !parentsIncludeBlockquote;
return getMarkdownSource(element)?.trim().startsWith('>>>');
},
},
};

View File

@ -16,27 +16,26 @@ export default Node.create({
},
src: {
default: null,
parseHTML: (element) => {
const playable = queryPlayableElement(element, this.options.mediaType);
return playable.src;
},
parseHTML: (element) => queryPlayableElement(element, this.options.mediaType).src,
},
canonicalSrc: {
default: null,
parseHTML: (element) => {
const playable = queryPlayableElement(element, this.options.mediaType);
return playable.dataset.canonicalSrc;
},
parseHTML: (element) =>
queryPlayableElement(element, this.options.mediaType).dataset.canonicalSrc,
},
alt: {
default: null,
parseHTML: (element) => {
const playable = queryPlayableElement(element, this.options.mediaType);
return playable.dataset.title;
},
parseHTML: (element) => queryPlayableElement(element, this.options.mediaType).dataset.title,
},
width: {
default: null,
parseHTML: (element) =>
queryPlayableElement(element, this.options.mediaType).getAttribute('width'),
},
height: {
default: null,
parseHTML: (element) =>
queryPlayableElement(element, this.options.mediaType).getAttribute('height'),
},
};
},

View File

@ -10,18 +10,35 @@ const defaultIgnoreAttrs = ['sourceMarkdown', 'sourceMapKey', 'sourceTagName'];
const ignoreAttrs = {
dd: ['isTerm'],
dt: ['isTerm'],
blockquote: ['multiline'],
h1: ['level'],
h2: ['level'],
h3: ['level'],
h4: ['level'],
h5: ['level'],
h6: ['level'],
};
// Buffers the output of the given action (fn) and returns the output that was written
// to the prosemirror-markdown serializer state output.
export function buffer(state, action = () => {}) {
export function buffer(state, action = () => {}, trackOnly = true) {
const buf = state.out;
action();
const retval = state.out.substring(buf.length);
state.out = buf;
if (trackOnly) state.out = buf;
return retval;
}
export function placeholder(state) {
const id = Math.floor(Math.random() * Date.now() * 10e3).toString(16);
return {
replaceWith: (content) => {
state.out = state.out.replace(new RegExp(id, 'g'), content);
},
value: id,
};
}
export function containsOnlyText(node) {
if (node.childCount === 1) {
const child = node.child(0);
@ -31,11 +48,20 @@ export function containsOnlyText(node) {
return false;
}
export function containsParagraphWithOnlyText(cell) {
if (cell.childCount === 1) {
const child = cell.child(0);
export function containsEmptyParagraph(node) {
if (node.childCount === 1) {
const child = node.child(0);
return child.type.name === 'paragraph' && child.childCount === 0;
}
return false;
}
export function containsParagraphWithOnlyText(node) {
if (node.childCount === 1) {
const child = node.child(0);
if (child.type.name === 'paragraph') {
return containsOnlyText(child);
return child.childCount === 0 || containsOnlyText(child);
}
}
@ -108,7 +134,8 @@ export function renderContent(state, node, forceRenderInline) {
state.flushClose();
}
} else {
const renderInline = forceRenderInline || containsParagraphWithOnlyText(node);
const renderInline =
forceRenderInline || containsParagraphWithOnlyText(node) || containsOnlyText(node);
if (!renderInline) {
state.closeBlock(node);
state.flushClose();
@ -133,6 +160,13 @@ export function renderTextInline(text, state, node) {
}
}
let inBlockquote = false;
export const isInBlockquote = () => inBlockquote;
export const setIsInBlockquote = (value) => {
inBlockquote = value;
};
const expandPreserveUnchangedConfig = (configOrRender) =>
isFunction(configOrRender)
? { render: configOrRender, overwriteSourcePreservationStrategy: false, inline: false }
@ -147,7 +181,8 @@ export function preserveUnchanged(configOrRender) {
const { sourceMarkdown } = node.attrs;
const same = state.options.changeTracker.get(node);
if (same && !overwriteSourcePreservationStrategy) {
// sourcemaps for elements in blockquotes are not accurate
if (same && !overwriteSourcePreservationStrategy && !isInBlockquote()) {
state.write(sourceMarkdown);
if (!inline) {

View File

@ -1,23 +1,44 @@
import { preserveUnchanged } from '../serialization_helpers';
import {
preserveUnchanged,
containsEmptyParagraph,
buffer,
placeholder,
setIsInBlockquote,
} from '../serialization_helpers';
import { renderHTMLNode } from './html_node';
const blockquote = preserveUnchanged((state, node) => {
if (state.options.skipEmptyNodes) {
if (!node.childCount) return;
if (node.childCount === 1) {
const child = node.child(0);
if (child.type.name === 'paragraph' && !child.childCount) return;
}
if (!node.childCount || containsEmptyParagraph(node)) return;
}
if (node.attrs.multiline) {
state.write('>>>');
state.ensureNewLine();
state.renderContent(node);
state.ensureNewLine();
state.write('>>>');
state.closeBlock(node);
const { multiline, sourceMarkdown, sourceTagName } = node.attrs;
if (sourceTagName && !sourceMarkdown) {
renderHTMLNode(sourceTagName)(state, node);
return;
}
if (multiline) {
const placeholderQuotes = placeholder(state);
const bufferedContent = buffer(
state,
() => {
state.write(placeholderQuotes.value);
state.ensureNewLine();
state.renderContent(node);
state.ensureNewLine();
state.write(placeholderQuotes.value);
state.closeBlock(node);
},
false,
);
const numQuotes = Math.max(2, bufferedContent.match(/>>>+/g)?.[0]?.length || 0) + 1;
placeholderQuotes.replaceWith('>'.repeat(numQuotes));
} else {
setIsInBlockquote(true);
state.wrapBlock('> ', null, node, () => state.renderContent(node));
setIsInBlockquote(false);
}
});

View File

@ -38,7 +38,7 @@ export const renderList = (state, node, delim, firstDelim) => {
export const renderBulletList = (state, node) => {
const { sourceMarkdown, bullet: bulletAttr } = node.attrs;
const bullet = /^(\*|\+|-)\s/.exec(sourceMarkdown)?.[1] || bulletAttr || '*';
const bullet = /^(\*|\+|-)\s/.exec(sourceMarkdown?.trim())?.[1] || bulletAttr || '*';
renderList(state, node, ' ', () => `${bullet} `);
};

View File

@ -1,9 +1,13 @@
import { preserveUnchanged } from '../serialization_helpers';
import { openTag } from '../serialization_helpers';
import { isInTable } from './table';
// eslint-disable-next-line max-params
function renderHardBreak(state, node, parent, index) {
const br = isInTable(parent) ? '<br>' : '\\\n';
let br = '\\\n';
const { sourceMarkdown, sourceTagName } = node.attrs;
if (typeof sourceMarkdown === 'string') br = sourceMarkdown.includes('\\') ? '\\\n' : ' \n';
else if (isInTable(parent) || sourceTagName) br = openTag('br');
for (let i = index + 1; i < parent.childCount; i += 1) {
if (parent.child(i).type !== node.type) {
@ -13,6 +17,6 @@ function renderHardBreak(state, node, parent, index) {
}
}
const hardBreak = preserveUnchanged(renderHardBreak);
const hardBreak = renderHardBreak;
export default hardBreak;

View File

@ -1,8 +1,15 @@
import { preserveUnchanged } from '../serialization_helpers';
import { preserveUnchanged, containsOnlyText } from '../serialization_helpers';
import { renderHTMLNode } from './html_node';
const heading = preserveUnchanged((state, node) => {
if (state.options.skipEmptyNodes && !node.childCount) return;
const { sourceMarkdown, sourceTagName } = node.attrs;
if (sourceTagName && !sourceMarkdown && containsOnlyText(node)) {
renderHTMLNode(sourceTagName, true)(state, node);
return;
}
state.write(`${'#'.repeat(node.attrs.level)} `);
state.renderInline(node, false);
state.closeBlock(node);

View File

@ -1,4 +1,5 @@
import { preserveUnchanged } from '../serialization_helpers';
import { pickBy, identity } from 'lodash';
import { preserveUnchanged, openTag } from '../serialization_helpers';
function getMediaSrc(node, useCanonicalSrc = true) {
const { canonicalSrc, src } = node.attrs;
@ -9,12 +10,18 @@ function getMediaSrc(node, useCanonicalSrc = true) {
const image = preserveUnchanged({
render: (state, node) => {
const { alt, title, width, height, isReference } = node.attrs;
const { alt, title, width, height, isReference, sourceMarkdown, sourceTagName } = node.attrs;
const realSrc = getMediaSrc(node, state.options.useCanonicalSrc);
// eslint-disable-next-line @gitlab/require-i18n-strings
if (realSrc.startsWith('data:') || realSrc.startsWith('blob:')) return;
if (realSrc) {
if (sourceTagName && !sourceMarkdown) {
const attrs = pickBy({ alt, title, width, height }, identity);
state.write(openTag(sourceTagName, { src: realSrc, ...attrs }));
return;
}
const quotedTitle = title ? ` ${state.quote(title)}` : '';
const sourceExpression = isReference ? `[${realSrc}]` : `(${realSrc}${quotedTitle})`;

View File

@ -7,7 +7,7 @@ export function renderOrderedList(state, node) {
let delimiter;
if (sourceMarkdown) {
const match = /^(\d+)(\)|\.)/.exec(sourceMarkdown);
const match = /^(\d+)(\)|\.)/.exec(sourceMarkdown.trim());
start = parseInt(match[1], 10) || 1;
[, , delimiter] = match;
} else {

View File

@ -1,6 +1,13 @@
import { preserveUnchanged } from '../serialization_helpers';
import { preserveUnchanged, containsOnlyText } from '../serialization_helpers';
import { renderHTMLNode } from './html_node';
const paragraph = preserveUnchanged((state, node) => {
const { sourceMarkdown, sourceTagName } = node.attrs;
if (sourceTagName && !sourceMarkdown && containsOnlyText(node)) {
renderHTMLNode(sourceTagName, true)(state, node);
return;
}
state.renderInline(node);
state.closeBlock(node);
});

View File

@ -7,6 +7,10 @@ const sensitiveDataPatterns = () => {
name: 'GitLab personal access token',
regex: `${patPrefix}[0-9a-zA-Z_-]{20}`,
},
{
name: 'GitLab personal access token (routable)',
regex: `${patPrefix}(?<base64_payload>[0-9a-zA-Z_-]{27,300})\\.(?<base64_payload_length>[0-9a-z]{2})(?<crc32>[0-9a-z]{7})`,
},
{
name: 'Feed Token',
regex: 'feed_token=[0-9a-zA-Z_-]{20}|glft-[0-9a-zA-Z_-]{20}|glft-[a-h0-9]+-[0-9]+_',

View File

@ -732,7 +732,13 @@ export default {
>
<template #nav-actions>
<div class="gl-flex gl-gap-3">
<gl-button v-if="mergeTrainsPath" :href="mergeTrainsPath" data-testid="merge-trains">
<gl-button
v-if="mergeTrainsPath"
:href="mergeTrainsPath"
data-testid="merge-trains"
variant="link"
class="gl-mr-3"
>
{{ __('Merge trains') }}
</gl-button>
<gl-button

View File

@ -830,7 +830,7 @@
}
.merge-request-sticky-header {
z-index: $top-bar-z-index;
z-index: calc($top-bar-z-index - 1);
height: $mr-sticky-header-height;
}

View File

@ -48,6 +48,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:reviewer_assign_drawer, current_user)
push_frontend_feature_flag(:vulnerability_code_flow, project)
push_frontend_feature_flag(:pipeline_vulnerability_code_flow, project)
push_frontend_feature_flag(:mr_vulnerability_code_flow, current_user)
end
around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :diffs, :rapid_diffs, :discussions]

View File

@ -10,7 +10,7 @@ module Types
authorize :read_environment
expose_permissions Types::PermissionTypes::Environment,
description: 'Permissions for the current user on the resource. '\
description: 'Permissions for the current user on the resource. ' \
'This field can only be resolved for one environment in any single request.' do
extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1
end
@ -61,7 +61,7 @@ module Types
description: 'Folder name of the environment.'
field :deployments_display_count, GraphQL::Types::String, null: true,
description: 'Number of deployments in the environment for display. '\
description: 'Number of deployments in the environment for display. ' \
'Returns the precise number up to 999, and "999+" for counts exceeding this limit.'
field :latest_opened_most_severe_alert,

View File

@ -3,6 +3,7 @@
class Ci::BuildPendingState < Ci::ApplicationRecord
include Ci::Partitionable
before_validation :set_project_id, on: :create
belongs_to :build,
->(pending_state) { in_partition(pending_state) },
class_name: 'Ci::Build',
@ -16,10 +17,15 @@ class Ci::BuildPendingState < Ci::ApplicationRecord
enum failure_reason: CommitStatus.failure_reasons
validates :build, presence: true
validates :project_id, presence: true, on: :create
def crc32
trace_checksum.try do |checksum|
checksum.to_s.split('crc32:').last.to_i(16)
end
end
def set_project_id
self.project_id ||= build&.project_id
end
end

View File

@ -47,6 +47,8 @@ module GroupDescendant
Parent was not preloaded for child when rendering group hierarchy.
This error is not user facing, but causes a +1 query.
MSG
exception.set_backtrace(caller)
extras = {
parent: parent.inspect,
child: child.inspect,

View File

@ -54,6 +54,8 @@ class GroupGroupLink < ApplicationRecord
unscoped.from(distinct_group_links, :group_group_links)
end
scope :with_at_least_group_access, ->(group_access) { where(group_access: group_access..) }
alias_method :shared_from, :shared_group
def self.search(query, **options)

View File

@ -201,6 +201,7 @@ class Member < ApplicationRecord
scope :with_user, ->(user) { where(user: user) }
scope :by_access_level, ->(access_level) { active.where(access_level: access_level) }
scope :all_by_access_level, ->(access_level) { where(access_level: access_level) }
scope :with_at_least_access_level, ->(access_level) { where(access_level: access_level..) }
scope :preload_users, -> { preload(:user) }

View File

@ -19,7 +19,14 @@ class PersonalAccessToken < ApplicationRecord
add_authentication_token_field :token,
digest: true,
format_with_prefix: :prefix_from_application_current_settings
format_with_prefix: :prefix_from_application_current_settings,
routable_token: {
if: ->(token_owner_record) { Feature.enabled?(:routable_pat, token_owner_record.user) },
payload: {
o: ->(token_owner_record) { token_owner_record.organization_id },
u: ->(token_owner_record) { token_owner_record.user_id }
}
}
columns_changing_default :organization_id

View File

@ -0,0 +1,9 @@
---
name: auth_finder_no_token_length_detection
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/487009
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169322
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/504421
milestone: '17.7'
group: group::cells infrastructure
type: gitlab_com_derisk
default_enabled: false

View File

@ -1,9 +1,9 @@
---
name: duo_chat_requires_licensed_seat
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/457090
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/150072
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/457091
milestone: '17.0'
group: group::duo chat
type: beta
name: routable_pat
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/487009
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169322
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/504419
milestone: '17.7'
group: group::cells infrastructure
type: gitlab_com_derisk
default_enabled: false

View File

@ -1,9 +1,9 @@
---
name: duo_chat_requires_licensed_seat_sm
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/457283
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/150391
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/457757
milestone: '17.0'
group: group::duo chat
type: beta
name: mr_vulnerability_code_flow
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/458062
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/173325
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/504345
milestone: '17.7'
group: group::security platform management
type: wip
default_enabled: false

View File

@ -0,0 +1,9 @@
---
migration_job_name: BackfillCiBuildPendingStatesProjectId
description: Backfills sharding key `ci_build_pending_states.project_id` from `p_ci_builds`.
feature_category: continuous_integration
introduced_by_url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/171427'
milestone: '17.7'
queued_migration_version: 20241126151234
finalize_after: '2024-12-22'
finalized_by: # version of the migration that finalized this BBM

View File

@ -8,5 +8,15 @@ description: TODO
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41585
milestone: '13.4'
gitlab_schema: gitlab_ci
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/458479
desired_sharding_key:
project_id:
references: projects
backfill_via:
parent:
foreign_key: build_id
table: p_ci_builds
sharding_key: project_id
belongs_to: build
foreign_key_name: fk_861cd17da3_p
desired_sharding_key_migration_job_name: BackfillCiBuildPendingStatesProjectId
table_size: small

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddProjectIdToCiBuildPendingStates < Gitlab::Database::Migration[2.2]
milestone '17.7'
def change
add_column :ci_build_pending_states, :project_id, :bigint
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class IndexCiBuildsPendingStatesOnProjectId < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '17.7'
TABLE_NAME = :ci_build_pending_states
INDEX_NAME = :index_ci_build_pending_states_on_project_id
def up
add_concurrent_index TABLE_NAME, :project_id, name: INDEX_NAME
end
def down
remove_concurrent_index_by_name TABLE_NAME, INDEX_NAME
end
end

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
class QueueBackfillCiBuildPendingStatesProjectId < Gitlab::Database::Migration[2.2]
milestone '17.7'
restrict_gitlab_migration gitlab_schema: :gitlab_ci
MIGRATION = "BackfillCiBuildPendingStatesProjectId"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 25_000
SUB_BATCH_SIZE = 150
def up
queue_batched_background_migration(
MIGRATION,
:ci_build_pending_states,
:id,
:project_id,
:p_ci_builds,
:project_id,
:build_id,
:partition_id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(
MIGRATION,
:ci_build_pending_states,
:id,
[
:project_id,
:p_ci_builds,
:project_id,
:build_id,
:partition_id
]
)
end
end

View File

@ -0,0 +1 @@
1e4c98ea6bfaf63af341423d8d48df5806c45342bc27282ac177276ad2e79c6a

View File

@ -0,0 +1 @@
aedf9ef2d23cd65dadc7e8286379aa1615130f9e28a829f88e3841c04cd7a062

View File

@ -0,0 +1 @@
36ee5d348f055adecc23487eb336bf04c319051c6f1836b6ef793731eaa4d803

View File

@ -8852,7 +8852,8 @@ CREATE TABLE ci_build_pending_states (
failure_reason smallint,
trace_checksum bytea,
trace_bytesize bigint,
partition_id bigint NOT NULL
partition_id bigint NOT NULL,
project_id bigint
);
CREATE SEQUENCE ci_build_pending_states_id_seq
@ -29467,6 +29468,8 @@ CREATE INDEX index_ci_build_needs_on_partition_id_build_id ON ci_build_needs USI
CREATE UNIQUE INDEX index_ci_build_pending_states_on_build_id ON ci_build_pending_states USING btree (build_id);
CREATE INDEX index_ci_build_pending_states_on_project_id ON ci_build_pending_states USING btree (project_id);
CREATE UNIQUE INDEX index_ci_build_report_results_on_partition_id_build_id ON ci_build_report_results USING btree (partition_id, build_id);
CREATE INDEX index_ci_build_report_results_on_project_id ON ci_build_report_results USING btree (project_id);

View File

@ -10,7 +10,14 @@ DETAILS:
**Tier:** Premium, Ultimate
**Offering:** Self-managed
> - HTTP proxying for secondary sites with separate URLs is [enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/346112) in GitLab 15.1.
> - HTTP proxying for secondary sites with separate URLs [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/346112) in GitLab 14.5 [with a flag](../../../administration/feature_flags.md) named `geo_secondary_proxy_separate_urls`. Disabled by default.
> - [Enabled on GitLab.com, self-managed and GitLab Dedicated](https://gitlab.com/gitlab-org/gitlab/-/issues/346112) in GitLab 15.1.
FLAG:
The availability of this feature is controlled by a feature flag.
For more information, see the history.
The `geo_secondary_proxy_separate_urls` feature flag is planned to be deprecated and removed in a future release.
Support for read-only Geo secondary sites is proposed in [issue 366810](https://gitlab.com/gitlab-org/gitlab/-/issues/366810).
Secondary sites behave as full read-write GitLab instances. They transparently proxy all operations to the primary site, with [some notable exceptions](#features-accelerated-by-secondary-geo-sites).
@ -222,9 +229,6 @@ To request acceleration of a feature, check if an issue already exists in [epic
## Disable secondary site HTTP proxying
NOTE:
The feature flag described in this section is planned to be deprecated and removed in a future release. Support for read-only Geo secondary sites is proposed in [issue 366810](https://gitlab.com/gitlab-org/gitlab/-/issues/366810), you can upvote and share your use cases in that issue.
Secondary site HTTP proxying is enabled by default on a secondary site when it uses a unified URL, meaning, it is configured with the same `external_url` as the primary site. Disabling proxying in this case tends not to be helpful due to completely different behavior being served at the same URL, depending on routing.
HTTP proxying is enabled by default in GitLab 15.1 on a secondary site even without a unified URL. If proxying needs to be disabled on all secondary sites, it is easiest to disable the feature flag:

View File

@ -88,7 +88,7 @@ Markdown does not support any parameters, and always uses PNG format.
Before you can enable PlantUML in GitLab, set up your own PlantUML
server to generate the diagrams:
- [In Docker](#docker).
- Recommended. [In Docker](#docker).
- [In Debian/Ubuntu](#debianubuntu).
### Docker
@ -122,43 +122,15 @@ services:
- "8005:8080"
```
#### Configure local PlantUML access
Next, you can:
The PlantUML server runs locally on your server, so it can't be accessed
externally by default. Your server must catch external PlantUML
calls to `https://gitlab.example.com/-/plantuml/` and redirect them to the
local PlantUML server. Depending on your setup, the URL is either of the
following:
- `http://plantuml:8080/`
- `http://localhost:8080/plantuml/`
- `http://plantuml:8005/`
- `http://localhost:8005/plantuml/`
If you're running [GitLab with TLS](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
you must configure this redirection, because PlantUML uses the insecure HTTP protocol.
Newer browsers such as [Google Chrome 86+](https://www.chromestatus.com/feature/4926989725073408)
don't load insecure HTTP resources on pages served over HTTPS.
To enable this redirection:
1. Add the following line in `/etc/gitlab/gitlab.rb`, depending on your setup method:
```ruby
# Docker deployment
nginx['custom_gitlab_server_config'] = "location /-/plantuml/ { \n rewrite ^/-/plantuml/(.*) /$1 break;\n proxy_cache off; \n proxy_pass http://plantuml:8005/; \n}\n"
```
1. To activate the changes, run the following command:
```shell
sudo gitlab-ctl reconfigure
```
1. [Configure local PlantUML access](#configure-local-plantuml-access)
1. [Verify that the PlantUML installation](#verify-the-plantuml-installation) succeeded
### Debian/Ubuntu
You can install and configure a PlantUML server in Debian/Ubuntu distributions
using Tomcat or Jetty.
using Tomcat or Jetty. The instructions below are for Tomcat.
Prerequisites:
@ -168,7 +140,7 @@ Prerequisites:
#### Installation
PlantUML recommends to install Tomcat 10 or above. The scope of this page only
PlantUML recommends to install Tomcat 10.1 or above. The scope of this page only
includes setting up a basic Tomcat server. For more production-ready configurations,
see the [Tomcat Documentation](https://tomcat.apache.org/tomcat-10.1-doc/index.html).
@ -176,7 +148,7 @@ see the [Tomcat Documentation](https://tomcat.apache.org/tomcat-10.1-doc/index.h
```shell
sudo apt update
sudo apt-get install graphviz default-jdk git-core
sudo apt install default-jre-headless graphviz git
```
1. Add a user for Tomcat:
@ -188,7 +160,7 @@ see the [Tomcat Documentation](https://tomcat.apache.org/tomcat-10.1-doc/index.h
1. Install and configure Tomcat 10:
```shell
wget https://dlcdn.apache.org/tomcat/tomcat-10/v10.1.15/bin/apache-tomcat-10.1.15.tar.gz -P /tmp
wget https://dlcdn.apache.org/tomcat/tomcat-10/v10.1.33/bin/apache-tomcat-10.1.33.tar.gz -P /tmp
sudo tar xzvf /tmp/apache-tomcat-10*tar.gz -C /opt/tomcat --strip-components=1
sudo chown -R tomcat:tomcat /opt/tomcat/
sudo chmod -R u+x /opt/tomcat/bin
@ -227,13 +199,18 @@ see the [Tomcat Documentation](https://tomcat.apache.org/tomcat-10.1-doc/index.h
`JAVA_HOME` should be the same path as seen in `sudo update-java-alternatives -l`.
1. To configure ports, edit your `/opt/tomcat/conf/server.xml` and choose your
ports. Avoid using port `8080`, as [Puma](../operations/puma.md) listens on port `8080` for metrics.
ports. Recommended:
```shell
<Server port="8006" shutdown="SHUTDOWN">
...
<Connector port="8005" protocol="HTTP/1.1"
...
- Change the Tomcat shutdown port from `8005` to `8006`
- Use port `8005` for the Tomcat HTTP endpoint. The default port `8080` should be avoided,
because [Puma](../operations/puma.md) listens on port `8080` for metrics.
```diff
- <Server port="8006" shutdown="SHUTDOWN">
+ <Server port="8005" shutdown="SHUTDOWN">
- <Connector port="8005" protocol="HTTP/1.1"
+ <Connector port="8080" protocol="HTTP/1.1"
```
1. Reload and start Tomcat:
@ -248,46 +225,27 @@ see the [Tomcat Documentation](https://tomcat.apache.org/tomcat-10.1-doc/index.h
The Java process should be listening on these ports:
```shell
root@gitlab-omnibus:/plantuml-server# netstat -plnt | grep java
tcp6 0 0 127.0.0.1:8006 :::* LISTEN 14935/java
tcp6 0 0 :::8005 :::* LISTEN 14935/java
```
1. Modify your NGINX configuration in `/etc/gitlab/gitlab.rb`. Ensure the `proxy_pass` port matches the Connector port in `server.xml`:
```shell
nginx['custom_gitlab_server_config'] = "location /-/plantuml {
rewrite ^/-/(plantuml.*) /$1 break;
proxy_set_header HOST $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache off;
proxy_pass http://localhost:8005/plantuml;
}"
```
1. Reconfigure GitLab to read the new changes:
```shell
sudo gitlab-ctl reconfigure
root@gitlab-omnibus:/plantuml-server# ss -plnt | grep java
LISTEN 0 1 [::ffff:127.0.0.1]:8006 *:* users:(("java",pid=27338,fd=52))
LISTEN 0 100 *:8005 *:* users:(("java",pid=27338,fd=43))
```
1. Install PlantUML and copy the `.war` file:
Use the [latest release](https://github.com/plantuml/plantuml-server/releases) of plantuml-jsp (example: plantuml-jsp-v1.2023.12.war). For context, see [this issue](https://github.com/plantuml/plantuml-server/issues/265).
Use the [latest release](https://github.com/plantuml/plantuml-server/releases) of `plantuml-jsp`
(for example: `plantuml-jsp-v1.2023.12.war`).
For context, see [issue 265](https://github.com/plantuml/plantuml-server/issues/265).
```shell
wget -P /tmp https://github.com/plantuml/plantuml-server/releases/download/v1.2023.12/plantuml-jsp-v1.2023.12.war
sudo cp /tmp/plantuml-jsp-v1.2023.12.war /opt/tomcat/webapps/plantuml.war
wget -P /tmp https://github.com/plantuml/plantuml-server/releases/download/v1.2024.8/plantuml-jsp-v1.2024.8.war
sudo cp /tmp/plantuml-jsp-v1.2024.8.war /opt/tomcat/webapps/plantuml.war
sudo chown tomcat:tomcat /opt/tomcat/webapps/plantuml.war
sudo systemctl restart tomcat
```
The Tomcat service should restart. After the restart is complete, the
PlantUML integration is ready and listening for requests on port `8005`:
`http://localhost:8005/plantuml`
To test if the PlantUML server is working, run `curl --location --verbose "http://localhost:8005/plantuml/"`.
`http://localhost:8005`.
To change the Tomcat defaults, edit the `/opt/tomcat/conf/server.xml` file.
@ -296,13 +254,76 @@ The default URL is different when using this approach. The Docker-based image
makes the service available at the root URL, with no relative path. Adjust
the configuration below accordingly.
#### `404` error when opening the PlantUML page in the browser
Next, you can:
You might get a `404` error when visiting `https://gitlab.example.com/-/plantuml/`, when the PlantUML
server is set up [in Debian or Ubuntu](#debianubuntu).
1. [Configure local PlantUML access](#configure-local-plantuml-access). Ensure the `proxy_pass` port
configured in the link matches the Connector port in `server.xml`.
1. [Verify that the PlantUML installation](#verify-the-plantuml-installation) succeeded.
This can happen even when the integration is working.
It does not necessarily indicate a problem with your PlantUML server or configuration.
### Configure local PlantUML access
The PlantUML server runs locally on your server, so it can't be accessed
externally by default. Your server must catch external PlantUML
calls to `https://gitlab.example.com/-/plantuml/` and redirect them to the
local PlantUML server. Depending on your setup, the URL is either of the
following:
- `http://plantuml:8080/`
- `http://localhost:8080/plantuml/`
- `http://plantuml:8005/`
- `http://localhost:8005/plantuml/`
If you're running [GitLab with TLS](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
you must configure this redirection, because PlantUML uses the insecure HTTP protocol.
Newer browsers, such as [Google Chrome 86+](https://www.chromestatus.com/feature/4926989725073408),
don't load insecure HTTP resources on pages served over HTTPS.
To enable this redirection:
1. Add the following line in `/etc/gitlab/gitlab.rb`, depending on your setup method:
```ruby
# Docker install
nginx['custom_gitlab_server_config'] = "location /-/plantuml/ { \n rewrite ^/-/plantuml/(.*) /$1 break;\n proxy_cache off; \n proxy_pass http://plantuml:8005/; \n}\n"
# Debian/Ubuntu install
nginx['custom_gitlab_server_config'] = "location /-/plantuml/ { \n rewrite ^/-/plantuml/(.*) /$1 break;\n proxy_cache off; \n proxy_pass http://localhost:8005/plantuml; \n}\n"
```
1. To activate the changes, run the following command:
```shell
sudo gitlab-ctl reconfigure
```
### Verify the PlantUML installation
To verify the installation was successful:
1. Test the PlantUML server directly:
```shell
# Docker install
curl --location --verbose "http://localhost:8005/svg/SyfFKj2rKt3CoKnELR1Io4ZDoSa70000"
# Debian/Ubuntu install
curl --location --verbose "http://localhost:8005/plantuml/svg/SyfFKj2rKt3CoKnELR1Io4ZDoSa70000"
```
You should receive SVG output containing the text `hello`.
1. Test that GitLab can access PlantUML through NGINX by visiting:
```plaintext
http://gitlab.example.com/-/plantuml/svg/SyfFKj2rKt3CoKnELR1Io4ZDoSa70000
```
Replace `gitlab.example.com` with your GitLab instance URL. You should see a rendered
PlantUML diagram displaying `hello`.
```plaintext
Bob -> Alice : hello
```
### Configure PlantUML security
@ -339,8 +360,8 @@ these steps:
this command:
```ruby
gitlab_rails['env'] = { 'PLANTUML_ENCODING' => 'deflate' }
```
gitlab_rails['env'] = { 'PLANTUML_ENCODING' => 'deflate' }
```
In GitLab Helm chart, you can set it by adding a variable to the
[global.extraEnv](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/charts/globals.md#extraenv)
@ -372,3 +393,13 @@ If you're still not seeing the updated URL, check the following:
- Verify that the PlantUML integration is enabled in your GitLab settings.
- Check the GitLab logs for errors related to PlantUML rendering.
- [Clear your GitLab Redis cache](../raketasks/maintenance.md#clear-redis-cache).
### `404` error when opening the PlantUML page in the browser
You might get a `404` error when visiting `https://gitlab.example.com/-/plantuml/`, when the PlantUML
server is set up [in Debian or Ubuntu](#debianubuntu).
This can happen even when the integration is working.
It does not necessarily indicate a problem with your PlantUML server or configuration.
To confirm if PlantUML is working correctly, you can [verify the PlantUML installation](#verify-the-plantuml-installation).

View File

@ -38,6 +38,7 @@ Returns information about a token.
Supported tokens:
- [Personal access tokens](../../user/profile/personal_access_tokens.md)
- [Impersonation tokens](../../api/rest/authentication.md#impersonation-tokens)
- [Deploy tokens](../../user/project/deploy_tokens/index.md)
- [Feed tokens](../../security/tokens/index.md#feed-token)
- [OAuth application secrets](../../integration/oauth_provider.md)

View File

@ -18546,6 +18546,7 @@ Self-hosted LLM servers.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="aiselfhostedmodelapitoken"></a>`apiToken` | [`String`](#string) | Optional API key for the self-hosted model server. |
| <a id="aiselfhostedmodelcreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of creation. |
| <a id="aiselfhostedmodelendpoint"></a>`endpoint` | [`String!`](#string) | Endpoint of the self-hosted model server. |
| <a id="aiselfhostedmodelfeaturesettings"></a>`featureSettings` | [`AiFeatureSettingConnection`](#aifeaturesettingconnection) | AI feature settings using the self-hosted model. (see [Connections](#connections)) |

View File

@ -24,7 +24,7 @@ This should enable everyone to see locally any change in an IDE being sent to th
1. In VS Code, go to the Extensions page and find "GitLab Workflow" in the list.
1. Open the extension settings by clicking a small cog icon and select "Extension Settings" option.
1. Check a "GitLab: Debug" checkbox.
1. If you'd like to test that Code Suggestions is working from inside the GitLab Workflow extension for VS Code, then follow the [steps to set up a personal access token](../../editor_extensions/visual_studio_code/index.md#set-up-the-gitlab-workflow-extension) with your GDK inside the new window of VS Code that pops up when you run the "Run and Debug" command.
1. If you'd like to test that Code Suggestions is working from inside the GitLab Workflow extension for VS Code, then follow the [authenticate with GitLab steps](../../editor_extensions/visual_studio_code/index.md#authenticate-with-gitlab) with your GDK inside the new window of VS Code that pops up when you run the "Run and Debug" command.
- Once you complete the steps below, to test you are hitting your local `/code_suggestions/completions` endpoint and not production, follow these steps:
1. Inside the new window, in the built in terminal select the "Output" tab then "GitLab Language Server" from the drop down menu on the right.
1. Open a new file inside of this VS Code window and begin typing to see Code Suggestions in action.

View File

@ -16,9 +16,10 @@ like IntelliJ, PyCharm, GoLand, Webstorm, and Rubymine. The plugin supports thes
While coding, accept Code Suggestions by pressing <kbd>Tab</kbd>. To dismiss Code Suggestions,
press <kbd>Escape</kbd>.
## Download the extension
## Install the extension
Download the plugin from the [JetBrains Plugin Marketplace](https://plugins.jetbrains.com/plugin/22325-gitlab-duo).
Download the plugin from the [JetBrains Plugin Marketplace](https://plugins.jetbrains.com/plugin/22325-gitlab-duo)
and install it.
Prerequisites:
@ -29,7 +30,7 @@ If you use an older version of a JetBrains IDE, check the version compatibility
It contains a table of plugin versions and their
[supported IDE versions](https://plugins.jetbrains.com/plugin/22325-gitlab-duo/versions).
## Configure the extension
### Configure the extension
Prerequisites:
@ -45,7 +46,9 @@ To enable the plugin:
1. Select the **GitLab Duo** plugin, and select **Install**.
1. Select **OK** or **Save**.
To configure the plugin in your IDE after you enable it:
### Authenticate with GitLab
After you configure the plugin in your IDE, connect it to your GitLab account:
1. Go to your IDE's top menu bar and select **Settings**.
1. On the left sidebar, expand **Tools**, then select **GitLab Duo**.
@ -58,7 +61,7 @@ To configure the plugin in your IDE after you enable it:
1. Select **Verify setup**.
1. Select **OK** or **Save**.
### Enable experimental or beta features
## Enable experimental or beta features
Some features in the plugin are in experiment or beta status. To use them, you must opt in:

View File

@ -79,15 +79,21 @@ use {
::EndTabs
## Authenticate with GitLab
To connect this extension to your GitLab account, configure your environment variables:
| Environment variable | Default | Description |
|----------------------|----------------------|-------------|
| `GITLAB_TOKEN`. | not applicable | The default GitLab personal access token to use for authenticated requests. If provided, skips interactive authentication. |
| `GITLAB_VIM_URL`. | `https://gitlab.com` | Override the GitLab instance to connect with. Defaults to `https://gitlab.com`. |
A full list of environment variables is available in the extension's help text at
[`doc/gitlab.txt`](https://gitlab.com/gitlab-org/editor-extensions/gitlab.vim/-/blob/main/doc/gitlab.txt).
## Configure the extension
1. Configure environment variables. While these are the most common, a full list is available in this
plugin's help text at [`doc/gitlab.txt`](https://gitlab.com/gitlab-org/editor-extensions/gitlab.vim/-/blob/main/doc/gitlab.txt):
| Environment variable | Default | Description |
|----------------------|----------------------|-------------|
| `GITLAB_TOKEN`. | not applicable | The default GitLab personal access token to use for authenticated requests. If provided, skips interactive authentication. |
| `GITLAB_VIM_URL`. | `https://gitlab.com` | Override the GitLab instance to connect with. Defaults to `https://gitlab.com`. |
To configure this extension:
1. Configure your desired file types. For example, because this plugin supports Ruby, it adds a `FileType ruby` auto-command.
To configure this behavior for additional file types, add more file types to the `code_suggestions.auto_filetypes` setup option:

View File

@ -11,10 +11,11 @@ The [GitLab extension](https://marketplace.visualstudio.com/items?itemName=GitLa
integrates GitLab with Visual Studio for Windows. GitLab for Visual Studio supports
[GitLab Duo Code Suggestions](../../user/project/repository/code_suggestions/index.md).
## Download the extension
## Install the extension
Download the extension from the
[Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=GitLab.GitLabExtensionForVisualStudio).
[Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=GitLab.GitLabExtensionForVisualStudio)
and install it.
The extension requires:
@ -24,33 +25,29 @@ The extension requires:
- GitLab Duo Code Suggestions requires GitLab version 16.8 or later.
- You are not using Visual Studio for Mac, as it is unsupported.
## Configure the extension
No new additional data is collected to enable this feature. Private non-public GitLab customer data is not used as training data.
Learn more about [Google Vertex AI Codey APIs Data Governance](https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance).
After you download and install the extension, you must configure it.
### Authenticate with GitLab
After you download and install the extension, connect it to your GitLab account.
Prerequisites:
- GitLab Duo [is available and configured](../../user/gitlab_duo/turn_on_off.md) for your project.
- You have created a [GitLab personal access token](../../user/profile/personal_access_tokens.md#create-a-personal-access-token) with the `api` and `read_user` scope, and copied that token.
- Your project must use one of the
[supported languages](../../user/project/repository/code_suggestions/supported_extensions.md#supported-languages).
To do this:
1. Install the extension from the Visual Studio Marketplace, and enable it.
1. In GitLab, create a [GitLab personal access token](../../user/profile/personal_access_tokens.md#create-a-personal-access-token)
with the `api` and `read_user` scopes.
1. Copy the token. _For security reasons, this value is never displayed again, so you must copy this value now._
1. Open Visual Studio.
1. On the top bar, go to **Tools > Options > GitLab**.
1. For **Access Token**, paste in your token. The token is not displayed, nor is it accessible to others.
1. For **GitLab URL** field, provide the URL of your GitLab instance. For GitLab SaaS, use `https://gitlab.com`.
1. In Visual Studio, on the top bar, go to **Tools > Options > GitLab**.
1. In the **Access Token** field, paste in your token. The token is not displayed, nor is it accessible to others.
1. In the **GitLab URL** text box, enter the URL of your GitLab instance. For GitLab SaaS, use `https://gitlab.com`.
No new additional data is collected to enable this feature. Private non-public GitLab customer data is not used as training data.
Learn more about [Google Vertex AI Codey APIs Data Governance](https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance).
### Configure the extension
### Customize keyboard shortcuts
This extension provides these custom commands:
This extension provides these custom commands, which you can configure:
| Command name | Default keyboard shortcut | Feature |
|--------------------------------|---------------------------|---------|

View File

@ -38,18 +38,18 @@ When you view a GitLab project in VS Code, the extension shows you information a
- If the merge request includes an [issue closing pattern](../../user/project/issues/managing_issues.md#closing-issues-automatically),
a link to the issue.
## Set up the GitLab Workflow extension
## Install the extension
To set up the GitLab Workflow extension for VS Code:
First, install the extension:
To install the GitLab Workflow extension for VS Code:
- [Go to the Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow)
and install and enable the extension.
- If you use an unofficial version of VS Code, install the
extension from the [Open VSX Registry](https://open-vsx.org/extension/GitLab/gitlab-workflow).
Second, authenticate with GitLab:
### Authenticate with GitLab
After you download and install the extension, connect it to your GitLab account.
1. Open the Command Palette:
- For macOS, press <kbd>Command</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd>.
@ -78,9 +78,9 @@ The extension shows information in the VS Code status bar if both:
- Your project has a pipeline for the last commit.
- Your current branch is associated with a merge request.
## Configure extension settings
### Configure the extension
After you install GitLab Workflow, go to **Settings > Extensions > GitLab Workflow**
After you have installed GitLab Workflow and authenticated with GitLab, go to **Settings > Extensions > GitLab Workflow**
in VS Code to configure settings.
- [GitLab Duo Chat](../../user/gitlab_duo_chat/index.md#use-gitlab-duo-chat-in-vs-code).
@ -91,10 +91,10 @@ in VS Code to configure settings.
### Confirm that GitLab Duo is on
If you are assigned a seat, GitLab Duo AI-powered features are turned on by default.
However, to confirm that you have GitLab Duo, including Duo Chat and Code Suggestions,
However, to confirm that you have GitLab Duo (including Duo Chat and Code Suggestions)
turned on:
1. In VS Code, open the extension by going to **Settings > Extensions > GitLab Workflow**.
1. In VS Code, go to **Settings > Extensions > GitLab Workflow**.
1. Select **Manage** (**{settings}**).
1. Ensure that **GitLab Duo Chat: Enabled** and **GitLab Duo Code Suggestions: Enabled** are selected.
1. Optional. For **GitLab Duo Code Suggestions: Enabled Supported Languages**,
@ -299,7 +299,7 @@ Prerequisites:
- You're a member of a GitLab project.
- You've installed the [GitLab Workflow extension](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow).
- You've signed in to your GitLab instance, as described in [Set up the GitLab Workflow extension](#set-up-the-gitlab-workflow-extension).
- You've signed in to your GitLab instance, as described in [Authenticate with GitLab](#authenticate-with-gitlab).
To search the titles and description fields in your project:

View File

@ -48,7 +48,7 @@ HTTPS or Git URLs.
Prerequisites:
- To return search results from a GitLab instance, you must have
[added an access token](index.md#set-up-the-gitlab-workflow-extension) to that GitLab instance.
[added an access token](index.md#authenticate-with-gitlab) to that GitLab instance.
- You must be a member of a project for search to return it as a result.
To search for, then clone, a GitLab project:
@ -71,7 +71,7 @@ With this extension, you can browse a GitLab repository in read-only mode withou
Prerequisites:
- You have [registered an access token](index.md#set-up-the-gitlab-workflow-extension) for that GitLab instance.
- You have [registered an access token](index.md#authenticate-with-gitlab) for that GitLab instance.
To browse a GitLab repository in read-only mode:

View File

@ -202,6 +202,6 @@ before starting VS Code. If you set the token in a
you don't have to set a personal access token each time you delete your VS Code storage. Set these variables:
- `GITLAB_WORKFLOW_INSTANCE_URL`: Your GitLab instance URL, like `https://gitlab.com`.
- `GITLAB_WORKFLOW_TOKEN`: Your personal access token, which you created [during setup](index.md#set-up-the-gitlab-workflow-extension).
- `GITLAB_WORKFLOW_TOKEN`: Your personal access token, which you created [when authenticating with GitLab](index.md#authenticate-with-gitlab).
The token configured in an environment variable is overridden if you configure a token for the same GitLab instance in the extension.

View File

@ -159,11 +159,11 @@ panels:
If multiple topics are provided, all topics must match for the project to be included in the results.
### Projects by DORA categories
### Projects by DORA metric
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/408516) in GitLab 17.7.
The **Projects by [DORA](dora_metrics.md) categories** panel is a group-level table that lists the status of the organization's DevOps performance levels across projects.
The **Projects by [DORA](dora_metrics.md) metric** panel is a group-level table that lists the status of the organization's DevOps performance levels across projects.
The table lists all projects with their DORA metrics, aggregating data from child projects in groups and subgroups.
The metrics are aggregated for the last full calendar month.

View File

@ -74,10 +74,10 @@ Then you can confirm that GitLab Duo is available in your IDE and test some of t
For more information, see:
- [Set up the extension for VS Code](../../editor_extensions/visual_studio_code/index.md#set-up-the-gitlab-workflow-extension).
- [Set up the extension for JetBrains](../../editor_extensions/jetbrains_ide/index.md#download-the-extension).
- [Set up the extension for VS Code](../../editor_extensions/visual_studio_code/index.md).
- [Set up the extension for JetBrains](../../editor_extensions/jetbrains_ide/index.md).
- [Set up the extension for Visual Studio](../../editor_extensions/visual_studio/index.md).
- [Set up the extension for Neovim](../../editor_extensions/neovim/index.md#install-the-extension).
- [Set up the extension for Neovim](../../editor_extensions/neovim/index.md).
## Step 5: Turn on Code Suggestions in your IDE

View File

@ -107,9 +107,10 @@ If you have selected code in the editor, this selection is sent along with your
Prerequisites:
- [Install the Workflow extension for VS Code and authenticate with GitLab](../../editor_extensions/visual_studio_code/index.md#set-up-the-gitlab-workflow-extension).
- You've [installed the VS Code extension](../../editor_extensions/visual_studio_code/index.md#install-the-extension).
- You've [authenticated the extension with GitLab](../../editor_extensions/visual_studio_code/index.md#authenticate-with-gitlab).
- [Confirm that GitLab Duo is on](../../editor_extensions/visual_studio_code/index.md#confirm-that-gitlab-duo-is-on).
To use GitLab Duo Chat in GitLab Workflow extension for VS Code:
1. In VS Code, open a file. The file does not need to be a file in a Git repository.
@ -167,7 +168,7 @@ If you have selected code in the editor, this selection is sent along with your
To use GitLab Duo Chat in the GitLab Duo plugin for JetBrains IDEs:
1. Install and set up the GitLab Duo plugin for JetBrains IDEs:
1. In the JetBrains marketplace, download and install the [GitLab Duo plugin](../../editor_extensions/jetbrains_ide/index.md#download-the-extension).
1. In the JetBrains marketplace, download and install the [GitLab Duo plugin](../../editor_extensions/jetbrains_ide/index.md#install-the-extension).
1. Configure the [GitLab Duo plugin](../../editor_extensions/jetbrains_ide/index.md#configure-the-extension).
1. In a JetBrains IDE, open a project.
1. Open GitLab Duo Chat in either a chat window or an editor window:

View File

@ -50,8 +50,7 @@ This project provides you with:
## Register the agent
FLAG:
A [flag](../../../../administration/feature_flags.md) named `certificate_based_clusters` changed the **Actions** menu to focus on the agent rather than certificates. The flag is [enabled on GitLab.com, GitLab Dedicated, and self-managed](https://gitlab.com/groups/gitlab-org/configure/-/epics/8).
- [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81054) in GitLab 14.9: A [flag](../../../../administration/feature_flags.md) named `certificate_based_clusters` changed the **Actions** menu to focus on the agent rather than certificates. Disabled by default.
To create a GitLab agent for Kubernetes:

View File

@ -57,8 +57,7 @@ This project provides you with:
## Register the agent
FLAG:
A [flag](../../../../administration/feature_flags.md) named `certificate_based_clusters` changed the **Actions** menu to focus on the agent rather than certificates. The flag is [enabled on GitLab.com, GitLab Dedicated, and self-managed](https://gitlab.com/groups/gitlab-org/configure/-/epics/8).
- [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81054) in GitLab 14.9: A [flag](../../../../administration/feature_flags.md) named `certificate_based_clusters` changed the **Actions** menu to focus on the agent rather than certificates. Disabled by default.
To create a GitLab agent for Kubernetes:

View File

@ -334,11 +334,7 @@ You can install from source by pulling the Git repository directly. To do so, ei
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119739) in GitLab 16.4 [with a flag](../../../administration/feature_flags.md) named `composer_use_ssh_source_urls`. Disabled by default.
> - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/329246) GitLab 16.5.
FLAG:
On self-managed GitLab, by default this feature is available. To hide the feature per project, an administrator can
[disable the feature flag](../../../administration/feature_flags.md) named `composer_use_ssh_source_urls`.
On GitLab.com and GitLab Dedicated, this feature is available.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135467) in GitLab 16.6. Feature flag `composer_use_ssh_source_urls` removed.
When you install from source, the `composer` configures an
access to the project's Git repository.

View File

@ -858,11 +858,12 @@ Your changes are automatically saved.
### Request forwarding to Maven Central
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85299) in GitLab 15.4 [with a flag](../../../administration/feature_flags.md) named `maven_central_request_forwarding`. Disabled by default.
> - Required role [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/370471) from Maintainer to Owner in GitLab 17.0.
FLAG:
By default this feature is not available for self-managed. To make it available, an administrator can [enable the feature flag](../../../administration/feature_flags.md) named `maven_central_request_forwarding`.
This feature is not available for GitLab.com or GitLab Dedicated users.
The availability of this feature is controlled by a feature flag.
For more information, see the history.
When a Maven package is not found in the package registry, the request is forwarded
to [Maven Central](https://search.maven.org/).

View File

@ -186,7 +186,8 @@ For a quick overview, items that are exported include:
- Design management files and data
- LFS objects
- Issue boards
- CI/CD pipelines and pipeline schedules
- CI/CD pipelines
- Pipeline schedules (inactive and assigned to the user who initiated the import)
- Protected branches and tags
- Push rules
- Emoji reactions
@ -217,6 +218,7 @@ Items that are **not** exported include:
- Security policies associated with your project
- Links between issues and linked items
- Links to related merge requests
- Pipeline schedule variables
Migrating projects with file exports uses the same export and import mechanisms as creating projects from templates at the [group](../../group/custom_project_templates.md) and
[instance](../../../administration/custom_project_templates.md) levels. Therefore, the list of exported items is the same.

View File

@ -8,6 +8,12 @@ keywords = ["glpat"]
regex = "\\bglpat-[0-9a-zA-Z_\\-]{20}\\b"
tags = ["gitlab", "revocation_type", "gitlab_blocking"]
[[rules]]
description = "GitLab Personal Access Token (routable)"
id = "gitlab_personal_access_token_routable"
keywords = ["glpat"]
regex = "\\bglpat-(?<base64_payload>[0-9a-zA-Z_\\-]{27,300})\\.(?<base64_payload_length>[0-9a-z]{2})(?<crc32>[0-9a-z]{7})\\b"
tags = ["gitlab", "revocation_type", "gitlab_blocking"]
[[rules]]
description = "GitLab Pipeline Trigger Token"
id = "gitlab_pipeline_trigger_token"
keywords = ["glptt"]

View File

@ -270,6 +270,15 @@ module Gitlab
if try(:namespace_inheritable, :authentication)
access_token_from_namespace_inheritable
elsif Feature.enabled?(:auth_finder_no_token_length_detection, :current_request)
# The token can be a PAT or an OAuth (doorkeeper) token
begin
find_oauth_access_token
rescue UnauthorizedError
# It is also possible that a PAT is encapsulated in a `Bearer` OAuth token
# (e.g. NPM client registry auth). In that case, we rescue UnauthorizedError
# and try to find a personal access token.
end || find_personal_access_token
else
# The token can be a PAT or an OAuth (doorkeeper) token
# It is also possible that a PAT is encapsulated in a `Bearer` OAuth token
@ -296,7 +305,8 @@ module Gitlab
return unless token
# PATs with OAuth headers are not handled by OauthAccessToken
return if matches_personal_access_token_length?(token)
return if Feature.disabled?(:auth_finder_no_token_length_detection, :current_request) &&
matches_personal_access_token_length?(token)
# Expiration, revocation and scopes are verified in `validate_access_token!`
oauth_token = OauthAccessToken.by_token(token)

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
class BackfillCiBuildPendingStatesProjectId < BackfillDesiredShardingKeyPartitionJob
operation_name :backfill_ci_build_pending_states_project_id
feature_category :continuous_integration
end
end
end

View File

@ -129,6 +129,8 @@ module Gitlab
#
# Stores the index information in the postgres_async_indexes table to be removed later. The
# index will be always be removed CONCURRENTLY, so that option does not need to be given.
# Except for partitioned tables where indexes cannot be dropped using this option.
# https://www.postgresql.org/docs/current/sql-dropindex.html
#
# If the requested index has already been removed, it is not stored in the table for
# asynchronous destruction.
@ -141,7 +143,11 @@ module Gitlab
return
end
definition = "DROP INDEX CONCURRENTLY #{quote_column_name(index_name)}"
definition = if table_partitioned?(table_name)
"DROP INDEX #{quote_column_name(index_name)}"
else
"DROP INDEX CONCURRENTLY #{quote_column_name(index_name)}"
end
async_index = PostgresAsyncIndex.find_or_create_by!(name: index_name) do |rec|
rec.table_name = table_name

View File

@ -17362,6 +17362,9 @@ msgstr ""
msgid "DastConfig|Not enabled"
msgstr ""
msgid "DastProfiles| Customize the behavior of DAST using additional variables. For a full list of available variables, refer to the %{linkStart}DAST documentation%{linkEnd}."
msgstr ""
msgid "DastProfiles|%{linkStart}Headers may appear in vulnerability reports%{linkEnd}."
msgstr ""
@ -17386,9 +17389,15 @@ msgstr ""
msgid "DastProfiles|Active"
msgstr ""
msgid "DastProfiles|Add variable"
msgstr ""
msgid "DastProfiles|Additional request headers (optional)"
msgstr ""
msgid "DastProfiles|Additional variables"
msgstr ""
msgid "DastProfiles|Are you sure you want to delete this profile?"
msgstr ""
@ -23593,6 +23602,9 @@ msgstr ""
msgid "FindingsDrawer|SAST Finding"
msgstr ""
msgid "FindingsDrawer|View code flow"
msgstr ""
msgid "Fingerprint (MD5)"
msgstr ""
@ -49136,6 +49148,9 @@ msgstr ""
msgid "Secrets|Add secret"
msgstr ""
msgid "Secrets|An error occurred while fetching the Secret manager status. Please try again."
msgstr ""
msgid "Secrets|Created"
msgstr ""
@ -49178,6 +49193,12 @@ msgstr ""
msgid "Secrets|New secret"
msgstr ""
msgid "Secrets|Please wait while the Secrets manager is provisioned. It is safe to refresh this page."
msgstr ""
msgid "Secrets|Provisioning in progress"
msgstr ""
msgid "Secrets|Revoke"
msgstr ""
@ -49196,7 +49217,7 @@ msgstr ""
msgid "Secrets|Secrets Manager has been provisioned for this project."
msgstr ""
msgid "Secrets|Secrets represent sensitive information your CI job needs to complete work. This sensitive information can be items like API tokens, database credentials, or private keys. Unlike CI/CD variables, which are always presented to a job, secrets must be explicitly required by a job."
msgid "Secrets|Secrets can be items like API tokens, database credentials, or private keys. Unlike CI/CD variables, secrets must be explicitly requested by a job."
msgstr ""
msgid "Secrets|Select a reminder interval"

View File

@ -96,6 +96,7 @@ RSpec.describe 'Database schema',
auto_canceled_by_partition_id execution_config_id upstream_pipeline_partition_id],
ci_builds_metadata: %w[partition_id project_id build_id],
ci_build_needs: %w[project_id],
ci_build_pending_states: %w[project_id],
ci_builds_runner_session: %w[project_id],
ci_daily_build_group_report_results: %w[partition_id],
ci_deleted_objects: %w[project_id],

View File

@ -11,8 +11,6 @@ FactoryBot.define do
scopes { ['api'] }
impersonation { false }
after(:build) { |personal_access_token| personal_access_token.ensure_token }
trait :impersonation do
impersonation { true }
end

View File

@ -1,6 +1,22 @@
import { serialize, serializeWithOptions, builders } from '../../serialization_utils';
import {
serialize,
serializeWithOptions,
builders,
sourceTag,
source,
} from '../../serialization_utils';
const { paragraph, blockquote, hardBreak, codeBlock, bold } = builders;
const {
paragraph,
blockquote,
hardBreak,
codeBlock,
bold,
table,
tableRow,
tableCell,
tableHeader,
} = builders;
it('correctly serializes blockquotes with hard breaks', () => {
expect(serialize(blockquote('some text', hardBreak(), hardBreak(), 'new line'))).toBe(
@ -72,3 +88,178 @@ var y = 10;
`.trim(),
);
});
it('serializes multiple levels of nested multiline blockquotes', () => {
expect(
serialize(
blockquote(
{ multiline: true },
paragraph('some paragraph with ', bold('bold')),
blockquote(
{ multiline: true },
paragraph('some paragraph with ', bold('bold')),
blockquote({ multiline: true }, paragraph('some paragraph with ', bold('bold'))),
),
),
),
).toBe(`>>>>>
some paragraph with **bold**
>>>>
some paragraph with **bold**
>>>
some paragraph with **bold**
>>>
>>>>
>>>>>`);
});
it('serializes a text-only blockquote with an HTML tag as inline', () => {
expect(serialize(blockquote(sourceTag('blockquote'), paragraph('hello')))).toBe(
'<blockquote>hello</blockquote>',
);
});
it('serializes a blockquote with an HTML tag containing markdown as block', () => {
expect(
serialize(
blockquote(
sourceTag('blockquote'),
paragraph('Some ', bold('bold'), ' text'),
codeBlock('const x = 42;'),
),
),
).toBe(
`<blockquote>
Some **bold** text
\`\`\`
const x = 42;
\`\`\`
</blockquote>`,
);
});
describe('blockquote sourcemap preservation', () => {
it('preserves sourcemap for the outer blockquote', () => {
const originalContent = '> This is a blockquote\n> with multiple lines';
const result = serializeWithOptions(
{
pristineDoc: blockquote(
source(originalContent),
paragraph('This is a blockquote', hardBreak(), 'with multiple lines'),
),
},
blockquote(
source(originalContent),
paragraph('This is a modified blockquote', hardBreak(), 'with multiple lines'),
),
);
expect(result).toBe('> This is a modified blockquote\\\n> with multiple lines');
});
it('ignores sourcemaps for nested blockquotes', () => {
const outerSource = '> Outer\n>> Inner';
const innerSource = '>> Inner';
const innerBlockquote = blockquote(source(innerSource), paragraph('Inner'));
const result = serializeWithOptions(
{
pristineDoc: blockquote(source(outerSource), paragraph('Outer'), innerBlockquote),
},
blockquote(source(outerSource), paragraph('Outer'), innerBlockquote),
);
expect(result).toBe(`> Outer
>
> > Inner`);
});
it('ignores sourcemaps for blockquote children', () => {
const blockquoteSource = `> table:
>
> | header |
> | ------ |
> | cell |`;
// incorrect source
const tableSource = `| header |
> | ------ |
> | cell |`;
const tableElement = table(
source(tableSource),
tableRow(tableHeader(paragraph('header'))),
tableRow(tableCell(paragraph('cell'))),
);
const result = serializeWithOptions(
{
pristineDoc: blockquote(source(blockquoteSource), paragraph('table:'), tableElement),
},
blockquote(source(blockquoteSource), paragraph('modified table:'), tableElement),
);
// table source is ignored
expect(result).toBe(`> modified table:
>
> | header |
> |--------|
> | cell |
>
`);
});
it('preserves sourcemap for children of multiline blockquotes', () => {
const blockquoteSource = `>>>
table:
| header |
| ------ |
| cell |
>>>`;
const tableSource = `| header |
| ------ |
| cell |`;
const tableElement = table(
source(tableSource),
tableRow(tableHeader(paragraph('header'))),
tableRow(tableCell(paragraph('cell'))),
);
const result = serializeWithOptions(
{
pristineDoc: blockquote(
{ ...source(blockquoteSource), multiline: true },
paragraph('table:'),
tableElement,
),
},
blockquote(
{ ...source(blockquoteSource), multiline: true },
paragraph('modified table:'),
tableElement,
),
);
expect(result).toBe(`>>>
modified table:
| header |
| ------ |
| cell |
>>>`);
});
});

View File

@ -1,7 +1,16 @@
import { serialize, builders } from '../../serialization_utils';
import { serialize, builders, source, sourceTag } from '../../serialization_utils';
const { paragraph, hardBreak } = builders;
it('correctly serializes a line break', () => {
expect(serialize(paragraph('hello', hardBreak(), 'world'))).toBe('hello\\\nworld');
});
it('correctly serializes a line break with a sourcemap', () => {
expect(serialize(paragraph('hello', hardBreak(source('\\')), 'world'))).toBe('hello\\\nworld');
expect(serialize(paragraph('hello', hardBreak(source('')), 'world'))).toBe('hello \nworld');
});
it('correctly serializes a line break with a source tag', () => {
expect(serialize(paragraph('hello', hardBreak(sourceTag('br')), 'world'))).toBe('hello<br>world');
});

View File

@ -1,6 +1,6 @@
import { serialize, serializeWithOptions, builders } from '../../serialization_utils';
import { serialize, serializeWithOptions, builders, sourceTag } from '../../serialization_utils';
const { heading } = builders;
const { heading, bold } = builders;
it('correctly serializes headings', () => {
expect(
@ -42,3 +42,53 @@ it('skips serializing an empty heading if skipEmptyNodes=true', () => {
),
).toBe('');
});
it('serializes a text-only heading with an HTML tag as inline', () => {
expect(
serialize(
heading({ level: 1, ...sourceTag('h1') }, 'hello'),
heading({ level: 2, ...sourceTag('h2') }, 'hello'),
heading({ level: 3, ...sourceTag('h3') }, 'hello'),
heading({ level: 4, ...sourceTag('h4') }, 'hello'),
heading({ level: 5, ...sourceTag('h5') }, 'hello'),
heading({ level: 6, ...sourceTag('h6') }, 'hello'),
),
).toBe(`<h1>hello</h1>
<h2>hello</h2>
<h3>hello</h3>
<h4>hello</h4>
<h5>hello</h5>
<h6>hello</h6>
`);
});
it('serializes a heading with an HTML tag containing markdown as markdown', () => {
// HTML heading tags by definition cannot contain any markdown tags,
// so we serialize it to markdown despite being defined in source markdown as an HTML tag
expect(
serialize(
heading({ level: 1, ...sourceTag('h1') }, 'Some ', bold('bold'), ' text'),
heading({ level: 2, ...sourceTag('h2') }, 'Some ', bold('bold'), ' text'),
heading({ level: 3, ...sourceTag('h3') }, 'Some ', bold('bold'), ' text'),
heading({ level: 4, ...sourceTag('h4') }, 'Some ', bold('bold'), ' text'),
heading({ level: 5, ...sourceTag('h5') }, 'Some ', bold('bold'), ' text'),
heading({ level: 6, ...sourceTag('h6') }, 'Some ', bold('bold'), ' text'),
),
).toBe(`# Some **bold** text
## Some **bold** text
### Some **bold** text
#### Some **bold** text
##### Some **bold** text
###### Some **bold** text`);
});

View File

@ -1,4 +1,4 @@
import { serialize, serializeWithOptions, builders } from '../../serialization_utils';
import { serialize, serializeWithOptions, builders, sourceTag } from '../../serialization_utils';
const { paragraph, image } = builders;
@ -97,3 +97,17 @@ it('does not escape url in an image', () => {
serialize(paragraph(image({ src: 'https://example.com/image__1_.png', alt: 'image' }))),
).toBe('![image](https://example.com/image__1_.png)');
});
it('serializes image as an HTML tag if sourceTagName is defined', () => {
const imageAttrs = { src: 'img.jpg', alt: 'image', ...sourceTag('img') };
expect(serialize(paragraph(image(imageAttrs)))).toBe('<img src="img.jpg" alt="image">');
expect(serialize(paragraph(image({ ...imageAttrs, width: 300, height: 300 })))).toBe(
'<img src="img.jpg" alt="image" width="300" height="300">',
);
expect(serialize(paragraph(image({ ...imageAttrs, title: 'image title' })))).toBe(
'<img src="img.jpg" alt="image" title="image title">',
);
});

View File

@ -1,6 +1,13 @@
import { serialize, builders, source, text, serializeWithOptions } from '../../serialization_utils';
import {
serialize,
builders,
source,
text,
serializeWithOptions,
sourceTag,
} from '../../serialization_utils';
const { paragraph, link } = builders;
const { paragraph, link, bold } = builders;
it('escapes < and > in a paragraph', () => {
expect(
@ -71,4 +78,28 @@ describe('when a paragraph contains a link', () => {
);
});
});
it('serializes a text-only paragraph with an HTML tag as inline', () => {
expect(
serialize(
paragraph(sourceTag('p'), 'hello world'),
paragraph(sourceTag('p'), 'A quick brown fox jumps over the lazy dog'),
),
).toBe(`<p>hello world</p>
<p>A quick brown fox jumps over the lazy dog</p>
`);
});
it('serializes a paragraph with an HTML tag containing markdown as markdown', () => {
// HTML paragraph tags by definition cannot contain any markdown tags,
// so we serialize it to markdown despite being defined in source markdown as an HTML tag
expect(
serialize(
paragraph(sourceTag('p'), 'some', bold('bold'), 'text'),
paragraph(sourceTag('p'), 'A quick ', link({ href: '#' }, 'link'), ' to the docs'),
),
).toBe(`some**bold**text\n\nA quick [link](#) to the docs`);
});
});

View File

@ -140,9 +140,7 @@ it('correctly renders a table with checkboxes', () => {
`
<table>
<tr>
<th>
</th>
<th></th>
<th>Item</th>
<th>Description</th>
</tr>
@ -386,6 +384,38 @@ it('correctly renders content after a markdown table', () => {
);
});
it('correctly renders a table with a wiki link (with a pipe) in one of the cells', () => {
expect(
serialize(
table(
tableRow(tableHeader(paragraph('Header')), tableHeader(paragraph('Content'))),
tableRow(
tableCell(paragraph('Wiki Link')),
tableCell(
paragraph(
link(
{
isGollumLink: true,
isWikiPage: true,
href: '/gitlab-org/gitlab-test/-/wikis/link/to/some/wiki/page',
canonicalSrc: 'docs/changelog',
},
'Changelog',
),
),
),
),
),
).trim(),
).toBe(
`
| Header | Content |
|--------|---------|
| Wiki Link | [[Changelog\\|docs/changelog]] |
`.trim(),
);
});
it('correctly renders content after an html table', () => {
expect(
serialize(

View File

@ -7,3 +7,11 @@ it('correctly serializes video', () => {
`![video](video.mov)`,
);
});
it('serializes video with width and height', () => {
expect(
serialize(
paragraph(video({ alt: 'video', canonicalSrc: 'video.mov', width: 400, height: 300 })),
),
).toBe(`![video](video.mov){width=400 height=300}`);
});

View File

@ -20,6 +20,13 @@ export const mockFindingDismissed = {
url: 'https://semgrep.dev/r/gitlab.eslint.detect-disable-mustache-escape',
},
],
details: [
{
name: 'code_flows',
type: 'VulnerabilityDetailCodeFlows',
items: [],
},
],
};
export const mockFindingDetected = {
@ -73,3 +80,36 @@ export const mockFindingsMultiple = [
],
},
];
export const mockFindingDetails = [
{
name: 'code_flows',
type: 'VulnerabilityDetailCodeFlows',
items: [
{
nodeType: 'SOURCE',
fileLocation: {
fileName: 'app/app.py',
lineStart: 8,
lineEnd: 8,
},
},
{
nodeType: 'PROPAGATION',
fileLocation: {
fileName: 'app/app.py',
lineStart: 8,
lineEnd: 8,
},
},
{
nodeType: 'SINK',
fileLocation: {
fileName: 'app/utils.py',
lineStart: 5,
lineEnd: 5,
},
},
],
},
];

View File

@ -9,6 +9,7 @@ RSpec.describe Authn::AgnosticTokenIdentifier, feature_category: :system_access
let_it_be(:deploy_token) { create(:deploy_token).token }
let_it_be(:feed_token) { user.feed_token }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user).token }
let_it_be(:impersonation_token) { create(:personal_access_token, :impersonation, user: user).token }
let_it_be(:oauth_application_secret) { create(:oauth_application).plaintext_secret }
let_it_be(:cluster_agent_token) { create(:cluster_agent_token, token_encrypted: nil).token }
let_it_be(:runner_authentication_token) { create(:ci_runner, registration_type: :authenticated_user).token }
@ -18,6 +19,7 @@ RSpec.describe Authn::AgnosticTokenIdentifier, feature_category: :system_access
context 'with supported token types' do
where(:plaintext, :token_type) do
ref(:personal_access_token) | ::Authn::Tokens::PersonalAccessToken
ref(:impersonation_token) | ::Authn::Tokens::PersonalAccessToken
ref(:feed_token) | ::Authn::Tokens::FeedToken
ref(:deploy_token) | ::Authn::Tokens::DeployToken
ref(:oauth_application_secret) | ::Authn::Tokens::OauthApplicationSecret

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::BackfillCiBuildPendingStatesProjectId,
feature_category: :continuous_integration,
schema: 20241126151234,
migration: :gitlab_ci do
include_examples 'desired sharding key backfill job' do
let(:batch_table) { :ci_build_pending_states }
let(:backfill_column) { :project_id }
let(:backfill_via_table) { :p_ci_builds }
let(:backfill_via_column) { :project_id }
let(:backfill_via_foreign_key) { :build_id }
let(:partition_column) { :partition_id }
end
end

View File

@ -360,7 +360,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build, feature_category: :pipeline_co
}
end
it { is_expected.to include(options: { cache: [a_hash_including( key: 'something-default' )] }) }
it { is_expected.to include(options: { cache: [a_hash_including(key: 'something-default')] }) }
end
context 'with cache:key:files and prefix' do

View File

@ -297,5 +297,47 @@ RSpec.describe Gitlab::Database::AsyncIndexes::MigrationHelpers, feature_categor
end.not_to change { index_model.where(name: index_name).count }
end
end
context 'when targeting a partitioned table' do
let(:table_name) { '_test_partitioned_table' }
let(:index_name) { '_test_partitioning_index_name' }
let(:column_name) { 'created_at' }
let(:partition_schema) { 'gitlab_partitions_dynamic' }
let(:partition1_identifier) { "#{partition_schema}.#{table_name}_202001" }
let(:partition2_identifier) { "#{partition_schema}.#{table_name}_202002" }
before do
connection.execute(<<~SQL)
DROP TABLE IF EXISTS #{table_name};
CREATE TABLE #{table_name} (
id serial NOT NULL,
created_at timestamptz NOT NULL,
updated_at timestamptz NOT NULL,
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);
DROP TABLE IF EXISTS #{partition1_identifier};
CREATE TABLE #{partition1_identifier} PARTITION OF #{table_name}
FOR VALUES FROM ('2020-01-01') TO ('2020-02-01');
DROP TABLE IF EXISTS #{partition2_identifier};
CREATE TABLE #{partition2_identifier} PARTITION OF #{table_name}
FOR VALUES FROM ('2020-02-01') TO ('2020-03-01');
CREATE INDEX #{index_name} ON #{table_name}(#{column_name});
SQL
end
it 'creates the record for the async index removal' do
expect do
migration.prepare_async_index_removal(table_name, column_name, name: index_name)
end.to change { index_model.where(name: index_name).count }.by(1)
record = index_model.find_by(name: index_name)
expect(record.table_name).to eq(table_name)
expect(record.definition).to match(/DROP INDEX "#{index_name}"/)
end
end
end
end

View File

@ -0,0 +1,34 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueBackfillCiBuildPendingStatesProjectId, migration: :gitlab_ci, feature_category: :continuous_integration do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
table_name: :ci_build_pending_states,
column_name: :id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE,
gitlab_schema: :gitlab_ci,
job_arguments: [
:project_id,
:p_ci_builds,
:project_id,
:build_id,
:partition_id
]
)
}
end
end
end

View File

@ -65,4 +65,19 @@ RSpec.describe Ci::BuildPendingState, feature_category: :continuous_integration
end
end
end
describe '#set_project_id' do
it 'sets the project_id before validation' do
pending_state = create(:ci_build_pending_state)
expect(pending_state.project_id).to eq(pending_state.build.project_id)
end
it 'does not override the project_id if set' do
existing_project = create(:project)
pending_state = create(:ci_build_pending_state, project_id: existing_project.id)
expect(pending_state.project_id).to eq(existing_project.id)
end
end
end

View File

@ -82,10 +82,23 @@ RSpec.describe GroupDescendant do
expect(described_class.build_hierarchy(groups)).to eq(expected_hierarchy)
end
it 'tracks the exception when a parent was not preloaded' do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).and_call_original
context 'when parent is not preloaded' do
it 'tracks the exception' do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).and_call_original
expect { described_class.build_hierarchy([subsub_group]) }.to raise_error(ArgumentError)
expect { described_class.build_hierarchy([subsub_group]) }.to raise_error(ArgumentError)
end
it 'includes the backtrace' do
allow(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
described_class.build_hierarchy([subsub_group])
expect(Gitlab::ErrorTracking).to have_received(:track_and_raise_for_dev_exception)
.at_least(:once) do |exception, _|
expect(exception.backtrace).to be_present
end
end
end
it 'recovers if a parent was not reloaded by querying for the parent' do

View File

@ -177,6 +177,19 @@ RSpec.describe GroupGroupLink, feature_category: :groups_and_projects do
expect(described_class.for_shared_with_groups(link.shared_with_group)).to contain_exactly(link)
end
end
describe '.with_at_least_group_access' do
let_it_be(:group_link_dev) { create(:group_group_link, group_access: Gitlab::Access::DEVELOPER) }
let_it_be(:group_link_guest) { create(:group_group_link, group_access: Gitlab::Access::GUEST) }
let_it_be(:group_link_maintainer) { create(:group_group_link, group_access: Gitlab::Access::MAINTAINER) }
it 'filters group links with at least the specified group access' do
results = described_class.with_at_least_group_access(Gitlab::Access::DEVELOPER)
expect(results).to include(group_link_dev, group_link_maintainer)
expect(results).not_to include(group_link_guest)
end
end
end
describe '#human_access' do

View File

@ -573,6 +573,15 @@ RSpec.describe Member, feature_category: :groups_and_projects do
end
end
describe '.with_at_least_access_level' do
it 'filters members with the at least the specified access level' do
results = described_class.with_at_least_access_level(::Gitlab::Access::MAINTAINER)
expect(results).to include(@owner, @maintainer)
expect(results).not_to include(@developer)
end
end
describe '.developers' do
subject { described_class.developers.to_a }

View File

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe PersonalAccessToken, feature_category: :system_access do
subject { described_class }
include ::TokenAuthenticatableMatchers
describe 'default values' do
subject(:personal_access_token) { described_class.new }
@ -654,25 +654,82 @@ RSpec.describe PersonalAccessToken, feature_category: :system_access do
end
end
describe 'token format' do
let(:personal_access_token) { described_class.new }
it_behaves_like 'TokenAuthenticatable' do
let(:token_field) { :token }
end
it 'generates a token' do
expect { personal_access_token.ensure_token }
.to change { personal_access_token.token }.from(nil).to(a_string_starting_with(described_class.token_prefix))
describe '#token' do
let(:random_bytes) { 'a' * TokenAuthenticatableStrategies::RoutableTokenGenerator::RANDOM_BYTES_LENGTH }
let(:devise_token) { 'devise-token' }
let_it_be(:user) { create(:user) }
let_it_be(:organization) { create(:organization) }
let(:token_digest) { nil }
let(:token_owner_record) do
create(:personal_access_token,
organization: organization,
user: user,
scopes: [:api],
token_digest: token_digest)
end
context 'when there is an existing token' do
let(:token) { 'an_existing_secret_token' }
subject(:token) { token_owner_record.token }
before do
allow(TokenAuthenticatableStrategies::RoutableTokenGenerator)
.to receive(:random_bytes).with(TokenAuthenticatableStrategies::RoutableTokenGenerator::RANDOM_BYTES_LENGTH)
.and_return(random_bytes)
allow(Devise).to receive(:friendly_token).and_return(devise_token)
allow(Settings).to receive(:cell).and_return({ id: 1 })
end
context 'when :routable_pat feature flag is disabled' do
before do
personal_access_token.set_token(token)
stub_feature_flags(routable_pat: false)
end
it 'does not change the existing token' do
expect { personal_access_token.ensure_token }
.not_to change { personal_access_token.token }.from(token)
it_behaves_like 'a digested token' do
let(:expected_token) { token }
let(:expected_token_payload) { devise_token }
let(:expected_token_prefix) { described_class.token_prefix }
let(:expected_token_digest) { token_owner_record.token_digest }
end
end
shared_examples 'a routable token' do
context 'when token_digest is not set yet' do
it_behaves_like 'a digested routable token' do
let(:expected_token) { token }
let(:expected_routing_payload) { "c:1\no:#{organization.id.to_s(36)}\nu:#{user.id.to_s(36)}" }
let(:expected_random_bytes) { random_bytes }
let(:expected_token_prefix) { described_class.token_prefix }
let(:expected_token_digest) { token_owner_record.token_digest }
end
end
context 'with token_digest already generated' do
let(:token_digest) { 's3cr3t' }
it 'does not change the token' do
expect(token).to be_nil
expect(token_owner_record.token_digest).to eq(token_digest)
end
end
end
context 'when :routable_pat feature flag is enabled for the user' do
before do
stub_feature_flags(routable_pat: token_owner_record.user)
end
it_behaves_like 'a routable token'
end
context 'when :routable_pat feature flag is enabled globally' do
before do
stub_feature_flags(routable_pat: true)
end
it_behaves_like 'a routable token'
end
end
end

View File

@ -13,6 +13,7 @@ RSpec.describe API::Admin::Token, :aggregate_failures, feature_category: :system
let_it_be(:oauth_application) { create(:oauth_application) }
let_it_be(:cluster_agent_token) { create(:cluster_agent_token, token_encrypted: nil) }
let_it_be(:runner_authentication_token) { create(:ci_runner, registration_type: :authenticated_user) }
let_it_be(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) }
let(:plaintext) { nil }
let(:params) { { token: plaintext } }
@ -29,7 +30,8 @@ RSpec.describe API::Admin::Token, :aggregate_failures, feature_category: :system
[ref(:user), lazy { user.feed_token }],
[ref(:oauth_application), lazy { oauth_application.plaintext_secret }],
[ref(:cluster_agent_token), lazy { cluster_agent_token.token }],
[ref(:runner_authentication_token), lazy { runner_authentication_token.token }]
[ref(:runner_authentication_token), lazy { runner_authentication_token.token }],
[ref(:impersonation_token), lazy { impersonation_token.token }]
]
end

View File

@ -210,8 +210,6 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
it 'returns a token with expiry when it receives a valid expires_at parameter' do
freeze_time do
token_size = (PersonalAccessToken.token_prefix || '').size + 20
post api('/internal/personal_access_token'),
params: {
key_id: key.id,
@ -222,7 +220,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
headers: gitlab_shell_internal_api_request_header
expect(json_response['success']).to be_truthy
expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
expect(json_response['token']).to start_with(PersonalAccessToken.token_prefix)
expect(json_response['scopes']).to match_array(%w[read_api read_repository])
expect(json_response['expires_at']).to eq(max_pat_access_token_lifetime.iso8601)
end
@ -230,8 +228,6 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
it 'returns token with expiry as PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS' do
freeze_time do
token_size = (PersonalAccessToken.token_prefix || '').size + 20
post api('/internal/personal_access_token'),
params: {
key_id: key.id,
@ -242,7 +238,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
headers: gitlab_shell_internal_api_request_header
expect(json_response['success']).to be_truthy
expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
expect(json_response['token']).to start_with(PersonalAccessToken.token_prefix)
expect(json_response['scopes']).to match_array(%w[read_api read_repository])
expect(json_response['expires_at']).to eq(max_pat_access_token_lifetime.iso8601)
end

View File

@ -335,14 +335,6 @@ RSpec.configure do |config|
# Disable suspending ClickHouse data ingestion workers
stub_feature_flags(suspend_click_house_data_ingestion: false)
# Disable license requirement for duo chat, which is subject to change.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/457090
stub_feature_flags(duo_chat_requires_licensed_seat: false)
# Disable license requirement for duo chat (self managed), which is subject to change.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/457283
stub_feature_flags(duo_chat_requires_licensed_seat_sm: false)
# Experimental merge request dashboard
stub_feature_flags(merge_request_dashboard: false)

View File

@ -97,17 +97,11 @@ supported in Markdown and will be converted to HTML."
<th>header</th>
</tr>
<tr>
<td>
</td>
<td>
</td>
<td></td>
<td></td>
</tr>
<tr>
<td>
</td>
<td></td>
<td>
* list item