Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-01-31 18:13:03 +00:00
parent a9b8a31684
commit 93326697e0
92 changed files with 1709 additions and 1223 deletions

View File

@ -5,7 +5,6 @@ Migration/BatchedMigrationBaseClass:
- 'lib/gitlab/background_migration/backfill_issue_search_data.rb'
- 'lib/gitlab/background_migration/backfill_iteration_cadence_id_for_boards.rb'
- 'lib/gitlab/background_migration/backfill_member_namespace_for_group_members.rb'
- 'lib/gitlab/background_migration/backfill_namespace_id_for_project_route.rb'
- 'lib/gitlab/background_migration/backfill_note_discussion_id.rb'
- 'lib/gitlab/background_migration/backfill_project_repositories.rb'
- 'lib/gitlab/background_migration/backfill_project_settings.rb'

View File

@ -1 +1 @@
f90c47160a2055dd8578774b5836533c2224d35f
4064e55eea5f1bb415cb57e8aa23630b14d7e387

View File

@ -1 +1 @@
45d5c77a77939f051b23b2052bb171d6314bb8e5
db58d685d85c5616bc90cdb806e9ee67cfc1c398

View File

@ -1,5 +1,12 @@
<script>
import { GlBadge, GlButton, GlButtonGroup, GlLink, GlPopover } from '@gitlab/ui';
import {
GlBadge,
GlButton,
GlButtonGroup,
GlLink,
GlPopover,
GlTooltipDirective,
} from '@gitlab/ui';
import { s__ } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
@ -20,6 +27,7 @@ export default {
keepText: s__('Job|Keep'),
downloadText: s__('Job|Download'),
browseText: s__('Job|Browse'),
sastTooltipText: s__('Job|This artifact contains SAST scan results in JSON format.'),
},
artifactsHelpPath: helpPagePath('ci/jobs/job_artifacts'),
components: {
@ -31,6 +39,9 @@ export default {
TimeagoTooltip,
HelpIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [timeagoMixin],
props: {
artifact: {
@ -57,8 +68,18 @@ export default {
willExpire() {
return this.artifact?.expired === false && !this.isLocked;
},
hasReports() {
return this.reports.length > 0;
sastReport() {
return this.reports.find((report) => report.file_type === 'sast');
},
dastReport() {
return this.reports.find((report) => report.file_type === 'dast');
},
hasArtifactPaths() {
return (
Boolean(this.artifact.keepPath) ||
Boolean(this.artifact.downloadPath) ||
Boolean(this.artifact.browsePath)
);
},
},
};
@ -79,9 +100,14 @@ export default {
{{ $options.i18n.artifactsHelpText }}
</gl-popover>
</div>
<span v-if="hasReports" class="gl-ml-2">
<gl-badge v-for="(report, index) in reports" :key="index" class="gl-mr-2">
{{ report.file_type }}
<span v-if="sastReport" class="gl-ml-3">
<gl-badge v-gl-tooltip :title="$options.i18n.sastTooltipText">
{{ sastReport.file_type }}
</gl-badge>
</span>
<span v-if="dastReport" class="gl-ml-3">
<gl-badge>
{{ dastReport.file_type }}
</gl-badge>
</span>
</div>
@ -110,7 +136,11 @@ export default {
{{ $options.i18n.lockedText }}
</span>
</p>
<gl-button-group class="gl-mt-3 gl-flex">
<gl-button-group
v-if="hasArtifactPaths"
class="gl-mt-3 gl-flex"
:class="{ 'gl-mb-3': sastReport }"
>
<gl-button
v-if="artifact.keepPath"
:href="artifact.keepPath"
@ -133,5 +163,15 @@ export default {
>{{ $options.i18n.browseText }}</gl-button
>
</gl-button-group>
<div class="gl-mt-2">
<gl-link
v-if="sastReport"
:href="sastReport.download_path"
class="!gl-text-link gl-underline"
data-testid="download-sast-report-link"
>
{{ s__('Job|Download SAST report') }}
</gl-link>
</div>
</div>
</template>

View File

@ -1,4 +1,6 @@
<script>
import { n__ } from '~/locale';
export default {
props: {
file: {
@ -6,13 +8,25 @@ export default {
required: true,
},
},
computed: {
addedLinesLabel() {
return n__('%d line added', '%d lines added', this.file.addedLines);
},
removedLinesLabel() {
return n__('%d line removed', '%d lines removed', this.file.removedLines);
},
},
};
</script>
<template>
<span class="file-row-stats">
<span data-testid="file-added-lines" class="gl-text-success"> +{{ file.addedLines }} </span>
<span data-testid="file-removed-lines" class="gl-text-danger"> -{{ file.removedLines }} </span>
<span data-testid="file-added-lines" class="gl-text-success" :aria-label="addedLinesLabel">
+{{ file.addedLines }}
</span>
<span data-testid="file-removed-lines" class="gl-text-danger" :aria-label="removedLinesLabel">
-{{ file.removedLines }}
</span>
</span>
</template>

View File

@ -232,7 +232,8 @@ export default {
:current-diff-file-id="currentDiffFileId"
:style="{ '--level': item.level }"
:class="{ 'tree-list-parent': item.level > 0 }"
class="gl-relative"
:tabindex="0"
class="gl-relative !gl-m-1"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="(path) => goToFile({ path })"
/>

View File

@ -176,38 +176,6 @@ export default {
@token="rotateInstanceId()"
/>
<div :class="topAreaBaseClasses">
<div class="gl-flex gl-flex-col md:!gl-hidden">
<gl-button
v-if="userListPath"
:href="userListPath"
variant="confirm"
category="tertiary"
class="gl-mb-3"
>
{{ s__('FeatureFlags|View user lists') }}
</gl-button>
<gl-button
v-if="canUserConfigure"
v-gl-modal="'configure-feature-flags'"
variant="confirm"
category="secondary"
data-testid="ff-configure-button"
class="gl-mb-3"
>
{{ s__('FeatureFlags|Configure') }}
</gl-button>
<gl-button
v-if="hasNewPath"
:href="newFeatureFlagPath"
:disabled="featureFlagsLimitExceeded"
variant="confirm"
data-testid="ff-new-button"
>
{{ s__('FeatureFlags|New feature flag') }}
</gl-button>
</div>
<page-heading>
<template #heading>
<span>

View File

@ -1,5 +1,13 @@
<script>
import { GlBadge, GlButton, GlTooltipDirective, GlModal, GlToggle } from '@gitlab/ui';
import {
GlBadge,
GlButton,
GlButtonGroup,
GlTooltipDirective,
GlModal,
GlToggle,
GlTableLite,
} from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale';
import { labelForStrategy } from '../utils';
@ -14,8 +22,10 @@ export default {
components: {
GlBadge,
GlButton,
GlButtonGroup,
GlModal,
GlToggle,
GlTableLite,
StrategyLabel,
},
directives: {
@ -35,6 +45,32 @@ export default {
};
},
computed: {
tableFields() {
return [
{
key: 'id',
label: s__('FeatureFlags|ID'),
},
{
key: 'status',
label: s__('FeatureFlags|Status'),
},
{
key: 'name',
label: s__('FeatureFlags|Feature flag'),
},
{
key: 'env_specs',
label: s__('FeatureFlags|Environment Specs'),
thClass: 'gl-w-1/2',
},
{
key: 'actions',
label: __('Actions'),
thClass: 'gl-w-1/12',
},
];
},
modalTitle() {
return sprintf(s__('FeatureFlags|Delete %{name}?'), {
name: this.deleteFeatureFlagName,
@ -99,125 +135,124 @@ export default {
};
</script>
<template>
<div class="table-holder js-feature-flag-table">
<div class="gl-responsive-table-row table-row-header" role="row">
<div class="table-section section-10">{{ s__('FeatureFlags|ID') }}</div>
<div class="table-section section-10" role="columnheader">
{{ s__('FeatureFlags|Status') }}
</div>
<div class="table-section section-20" role="columnheader">
{{ s__('FeatureFlags|Feature flag') }}
</div>
<div class="table-section section-40" role="columnheader">
{{ s__('FeatureFlags|Environment Specs') }}
</div>
</div>
<template v-for="featureFlag in featureFlags">
<div
:key="featureFlag.id"
:data-testid="featureFlag.id"
class="gl-responsive-table-row"
role="row"
>
<div class="table-section section-10" role="gridcell">
<div class="table-mobile-header" role="rowheader">{{ s__('FeatureFlags|ID') }}</div>
<div class="table-mobile-content js-feature-flag-id gl-text-left">
{{ featureFlagIidText(featureFlag) }}
</div>
</div>
<div class="table-section section-10" role="gridcell">
<div class="table-mobile-header" role="rowheader">{{ s__('FeatureFlags|Status') }}</div>
<div class="table-mobile-content gl-text-left">
<gl-toggle
v-if="featureFlag.update_path"
:value="featureFlag.active"
:label="$options.i18n.toggleLabel"
label-position="hidden"
data-testid="feature-flag-status-toggle"
data-track-action="click_button"
data-track-label="feature_flag_toggle"
@change="toggleFeatureFlag(featureFlag)"
/>
<gl-badge
v-else-if="featureFlag.active"
variant="success"
data-testid="feature-flag-status-badge"
>{{ s__('FeatureFlags|Active') }}</gl-badge
>
<gl-badge v-else variant="danger">{{ s__('FeatureFlags|Inactive') }}</gl-badge>
</div>
<div>
<gl-table-lite :fields="tableFields" :items="featureFlags" stacked="md">
<template #cell(id)="{ item = {} }">
<div class="!gl-text-left" data-testid="feature-flag-id">
{{ featureFlagIidText(item) }}
</div>
</template>
<div class="table-section section-20" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Feature flag') }}
</div>
<div
class="table-mobile-content flex-column js-feature-flag-title gl-mr-5 gl-flex gl-text-left"
>
<div class="gl-flex gl-items-center">
<div class="feature-flag-name text-monospace text-wrap gl-break-anywhere">
{{ featureFlag.name }}
</div>
<div class="feature-flag-description">
<gl-button
v-if="featureFlag.description"
v-gl-tooltip.hover="featureFlag.description"
:aria-label="featureFlag.description"
class="gl-mx-3 !gl-p-0"
category="tertiary"
size="small"
icon="information-o"
/>
</div>
<template #cell(status)="{ item = {} }">
<gl-toggle
v-if="item.update_path"
:value="item.active"
:label="$options.i18n.toggleLabel"
label-position="hidden"
data-testid="feature-flag-status-toggle"
data-track-action="click_button"
data-track-label="feature_flag_toggle"
@change="toggleFeatureFlag(item)"
/>
<gl-badge
v-else-if="item.active"
variant="success"
data-testid="feature-flag-status-badge"
>{{ s__('FeatureFlags|Active') }}</gl-badge
>
<gl-badge v-else variant="danger">{{ s__('FeatureFlags|Inactive') }}</gl-badge>
</template>
<template #cell(name)="{ item = {} }">
<div class="gl-flex" data-testid="feature-flag-title">
<div class="gl-flex gl-items-center">
<div class="feature-flag-name text-monospace text-wrap gl-break-anywhere">
{{ item.name }}
</div>
<div :data-testid="`feature-flag-description-${item.id}`">
<gl-button
v-if="item.description"
v-gl-tooltip.hover="item.description"
:aria-label="item.description"
class="gl-mx-3 !gl-p-0"
category="tertiary"
size="small"
icon="information-o"
/>
</div>
</div>
</div>
</template>
<div class="table-section section-40" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Environment Specs') }}
</div>
<div
class="table-mobile-content justify-content-end justify-content-md-start js-feature-flag-environments gl-flex gl-flex-wrap gl-text-left"
>
<strategy-label
v-for="strategy in featureFlag.strategies"
:key="strategy.id"
data-testid="strategy-label"
class="gl-mr-3 gl-mt-2 gl-w-full gl-whitespace-normal gl-text-left"
v-bind="strategyBadgeText(strategy)"
<template #cell(env_specs)="{ item = {} }">
<div class="gl-flex gl-flex-wrap" data-testid="feature-flag-environments">
<strategy-label
v-for="strategy in item.strategies"
:key="strategy.id"
data-testid="strategy-label"
class="gl-mr-3 gl-mt-2 gl-w-full gl-whitespace-normal !gl-text-left"
v-bind="strategyBadgeText(strategy)"
/>
</div>
</template>
<template #cell(actions)="{ item = {} }">
<gl-button-group
class="gl-hidden md:gl-inline-flex"
data-testid="flags-table-action-buttons"
>
<template v-if="item.edit_path">
<gl-button
v-gl-tooltip.hover.bottom="$options.i18n.editLabel"
data-testid="feature-flag-edit-button"
class="gl-flex-grow"
icon="pencil"
:aria-label="$options.i18n.editLabel"
:href="item.edit_path"
/>
</div>
</div>
</template>
<template v-if="item.destroy_path">
<gl-button
v-gl-tooltip.hover.bottom="$options.i18n.deleteLabel"
class="gl-flex-grow"
variant="danger"
icon="remove"
:disabled="!canDeleteFlag(item)"
:aria-label="$options.i18n.deleteLabel"
@click="setDeleteModalData(item)"
/>
</template>
</gl-button-group>
<div class="table-section section-20 table-button-footer" role="gridcell">
<div class="table-action-buttons btn-group">
<template v-if="featureFlag.edit_path">
<gl-button
v-gl-tooltip.hover.bottom="$options.i18n.editLabel"
class="js-feature-flag-edit-button"
icon="pencil"
:aria-label="$options.i18n.editLabel"
:href="featureFlag.edit_path"
/>
</template>
<template v-if="featureFlag.destroy_path">
<gl-button
v-gl-tooltip.hover.bottom="$options.i18n.deleteLabel"
class="js-feature-flag-delete-button"
variant="danger"
icon="remove"
:disabled="!canDeleteFlag(featureFlag)"
:aria-label="$options.i18n.deleteLabel"
@click="setDeleteModalData(featureFlag)"
/>
</template>
</div>
<div
class="gl-flex gl-gap-4 md:gl-hidden md:gl-gap-0"
data-testid="flags-table-action-buttons"
>
<template v-if="item.edit_path">
<gl-button
v-gl-tooltip.hover.bottom="$options.i18n.editLabel"
data-testid="feature-flag-edit-button"
class="gl-flex-grow"
icon="pencil"
:aria-label="$options.i18n.editLabel"
:href="item.edit_path"
/>
</template>
<template v-if="item.destroy_path">
<gl-button
v-gl-tooltip.hover.bottom="$options.i18n.deleteLabel"
data-testid="feature-flag-delete-button"
class="gl-flex-grow"
variant="danger"
icon="remove"
:disabled="!canDeleteFlag(item)"
:aria-label="$options.i18n.deleteLabel"
@click="setDeleteModalData(item)"
/>
</template>
</div>
</div>
</template>
</template>
</gl-table-lite>
<gl-modal
:ref="modalId"

View File

@ -132,6 +132,13 @@ export default {
} else {
projects = project?.ciJobTokenScope?.inboundAllowlist?.nodes ?? [];
groups = project?.ciJobTokenScope?.groupsAllowlist?.nodes ?? [];
const groupAllowlistAutopopulatedIds =
project?.ciJobTokenScope?.groupAllowlistAutopopulatedIds ?? [];
const inboundAllowlistAutopopulatedIds =
project?.ciJobTokenScope?.inboundAllowlistAutopopulatedIds ?? [];
projects = this.addAutopopulatedAttribute(projects, inboundAllowlistAutopopulatedIds);
groups = this.addAutopopulatedAttribute(groups, groupAllowlistAutopopulatedIds);
}
return { projects, groups };
@ -201,6 +208,7 @@ export default {
...node.target,
defaultPermissions: node.defaultPermissions,
jobTokenPolicies: node.jobTokenPolicies,
autopopulated: node.autopopulated,
}));
},
async updateCIJobTokenScope() {
@ -269,6 +277,12 @@ export default {
this.namespaceToEdit = namespace;
showFormFn();
},
addAutopopulatedAttribute(collection, idList) {
return collection.map((item) => ({
...item,
autopopulated: idList.includes(item.id),
}));
},
},
};
</script>

View File

@ -1,5 +1,13 @@
<script>
import { GlButton, GlIcon, GlLink, GlTable, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import {
GlButton,
GlIcon,
GlLink,
GlTable,
GlLoadingIcon,
GlSprintf,
GlTooltipDirective,
} from '@gitlab/ui';
import { TYPENAME_GROUP } from '~/graphql_shared/constants';
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
import { s__, __ } from '~/locale';
@ -15,6 +23,12 @@ export default {
GlSprintf,
ProjectAvatar,
},
directives: {
GlTooltip: GlTooltipDirective,
},
i18n: {
autopopulated: s__('CICD|Added from log.'),
},
inject: ['fullPath'],
props: {
items: {
@ -101,6 +115,15 @@ export default {
<gl-link :href="item.webUrl" data-testid="token-access-name">
{{ item.fullPath }}
</gl-link>
<gl-icon
v-if="item.autopopulated"
v-gl-tooltip
:title="$options.i18n.autopopulated"
:aria-label="$options.i18n.autopopulated"
name="log"
class="gl-ml-3 gl-shrink-0"
data-testid="autopopulated-icon"
/>
</div>
</template>

View File

@ -1,6 +1,7 @@
fragment AllowlistEntry on CiJobTokenScopeAllowlistEntry {
defaultPermissions
jobTokenPolicies
autopopulated
target {
... on Group {
id

View File

@ -20,6 +20,8 @@ query inboundGetGroupsAndProjectsWithCIJobTokenScope($fullPath: ID!) {
webUrl
}
}
groupAllowlistAutopopulatedIds
inboundAllowlistAutopopulatedIds
}
}
}

View File

@ -1,10 +1,11 @@
<script>
import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import getCommitIconMap from '~/ide/commit_icon';
import { __ } from '~/locale';
export default {
components: {
GlButton,
GlIcon,
},
directives: {
@ -77,14 +78,18 @@ export default {
</script>
<template>
<span
<gl-button
v-if="showIcon"
v-gl-tooltip.right
category="tertiary"
size="small"
:title="tooltipTitle"
:class="{ 'ml-auto': isCentered }"
class="file-changed-icon gl-inline-block"
:aria-label="tooltipTitle"
class="file-changed-icon !gl-min-h-0 !gl-min-w-0 !gl-bg-transparent !gl-p-0"
>
<gl-icon v-if="showIcon" :name="changedIcon" :size="size" :class="changedIconClass" />
</span>
<gl-icon :name="changedIcon" :size="size" :class="changedIconClass" />
</gl-button>
</template>
<style>

View File

@ -39,7 +39,11 @@ export default {
<div class="gl-mt-7" data-testid="multi-step-form-content">
<slot name="form"></slot>
</div>
<div class="gl-mt-6 gl-flex gl-justify-center gl-gap-3" data-testid="multi-step-form-action">
<div
v-if="$scopedSlots.back || $scopedSlots.next"
class="gl-mt-6 gl-flex gl-justify-center gl-gap-3"
data-testid="multi-step-form-action"
>
<slot name="back"></slot>
<slot name="next"></slot>
</div>

View File

@ -1,5 +1,4 @@
-# TODO: add file size
-# TODO: add file toggle
-# TODO: add comment button
-# TODO: add viewed toggle
-# TODO: add raw\rendered toggle
@ -18,11 +17,11 @@
= render Pajamas::ButtonComponent.new(category: :tertiary, size: :small, icon: 'chevron-right', button_options: { data: { closed: '', click: 'toggleFile' }, aria: { label: _('Show file contents') } })
.rd-diff-file-title
- if @diff_file.submodule?
%span
%span{ data: { testid: 'rd-diff-file-header-submodule' } }
= helpers.sprite_icon('folder-git', file_icon: true)
%strong
= helpers.submodule_link(@diff_file.blob, @diff_file.content_sha, @diff_file.repository)
-# TODO: add copy button
= copy_path_button
- else
-# TODO: add icons for file types
- if @diff_file.renamed_file?
@ -37,7 +36,7 @@
= @diff_file.file_path
- if @diff_file.deleted_file?
= _("deleted")
-# TODO: add copy button
= copy_path_button
- if @diff_file.mode_changed?
%small #{@diff_file.a_mode} → #{@diff_file.b_mode}
- if @diff_file.stored_externally? && @diff_file.external_storage == :lfs

View File

@ -2,8 +2,21 @@
module RapidDiffs
class DiffFileHeaderComponent < ViewComponent::Base
include ButtonHelper
def initialize(diff_file:)
@diff_file = diff_file
end
def copy_path_button
clipboard_button(
text: @diff_file.file_path,
gfm: "`#{@diff_file.file_path}`",
title: _("Copy file path"),
placement: "top",
boundary: "viewport",
testid: "rd-diff-file-copy-clipboard"
)
end
end
end

View File

@ -46,6 +46,11 @@ module Types
null: false,
description: 'When the entry was created.'
field :autopopulated,
GraphQL::Types::Boolean,
null: true,
description: 'Indicates whether the entry is created by the autopopulation process.'
def source_project
Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.source_project_id).find
end

View File

@ -53,6 +53,20 @@ module Types
"by authenticating with a CI/CD job token. " \
"The count does not include subgroups.",
method: :groups_count
field :group_allowlist_autopopulated_ids,
[::Types::GlobalIDType[::Group]],
null: false,
description: 'List of IDs of groups which have been created by the ' \
'autopopulation process.',
method: :autopopulated_group_ids
field :inbound_allowlist_autopopulated_ids,
[::Types::GlobalIDType[::Project]],
null: false,
description: 'List of IDs of projects which have been created by the ' \
'autopopulation process.',
method: :autopopulated_inbound_project_ids
end
end
# rubocop: enable Graphql/AuthorizeTypes

View File

@ -69,6 +69,14 @@ module Ci
group_links.includes(:target_group).map { |g| g.target_group.traversal_ids }
end
def autopopulated_project_global_ids
project_links.autopopulated.map { |link| link.target_project.to_global_id }.uniq
end
def autopopulated_group_global_ids
group_links.autopopulated.map { |link| link.target_group.to_global_id }.uniq
end
def project_links
Ci::JobToken::ProjectScopeLink
.with_source(@source_project)

View File

@ -62,6 +62,14 @@ module Ci
groups.count
end
def autopopulated_group_ids
inbound_allowlist.autopopulated_group_global_ids
end
def autopopulated_inbound_project_ids
inbound_allowlist.autopopulated_project_global_ids
end
def self_referential?(accessed_project)
current_project.id == accessed_project.id
end

View File

@ -57,16 +57,6 @@ module UseSqlFunctionForPrimaryKeyLookups
return unless verification_arel.ast == arel.ast
if table_name == "namespaces" && Feature.enabled?(:log_sql_function_namespace_lookups, Feature.current_request)
using_primary = Gitlab::Database::LoadBalancing::SessionMap.current(load_balancer).use_primary?
Gitlab::AppLogger.info(
message: "Namespaces lookup using function",
backtrace: caller,
using_primary: using_primary,
primary_key_value: pk_value
)
end
function_call = Arel::Nodes::NamedFunction.new("find_#{table_name}_by_id", [pk_value_attribute]).as(table_name)
filter_empty_row = "#{quoted_table_name}.#{connection.quote_column_name(primary_key)} IS NOT NULL"

View File

@ -6,13 +6,5 @@ module Wikis
belongs_to :wiki_page_meta, class_name: 'WikiPage::Meta', optional: false
belongs_to :note, optional: false
before_validation :set_namespace_id_from_note, on: :create
private
def set_namespace_id_from_note
self.namespace_id ||= note&.namespace_id
end
end
end

View File

@ -125,7 +125,7 @@ class WorkItem < Issue
end
def non_widgets
[:related_vulnerabilities]
[:related_vulnerabilities, :pending_escalations]
end
end

View File

@ -1,8 +0,0 @@
---
name: log_sql_function_namespace_lookups
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162642
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/478870
milestone: '17.4'
type: gitlab_com_derisk
group: group::database
default_enabled: false

View File

@ -0,0 +1,9 @@
---
migration_job_name: BackfillMissingNamespaceIdOnNotes
description: Backfills missing namespace_id values to support sharding
feature_category: code_review_workflow
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/163687
milestone: '17.9'
queued_migration_version: 20240822220027
finalize_after: "2025-03-31"
finalized_by: # version of the migration that finalized this BBM

View File

@ -2,97 +2,7 @@ name: gitlab_sec
description: Schema for all Cell-local Security features.
allow_cross_joins:
- gitlab_shared
- gitlab_main_cell:
specific_tables:
- security_findings
- security_scans
- vulnerabilities
- users_security_dashboard_projects
- vulnerability_exports
- vulnerability_external_issue_links
- vulnerability_feedback
- vulnerability_finding_evidences
- vulnerability_finding_links
- vulnerability_finding_signatures
- vulnerability_findings_remediations
- vulnerability_flags
- vulnerability_historical_statistics
- vulnerability_namespace_historical_statistics
- vulnerability_identifiers
- vulnerability_issue_links
- vulnerability_merge_request_links
- vulnerability_occurrence_identifiers
- vulnerability_occurrences
- vulnerability_reads
- vulnerability_remediations
- vulnerability_scanners
- vulnerability_state_transitions
- vulnerability_statistics
- vulnerability_user_mentions
- dependency_list_exports
- sbom_component_versions
- sbom_components
- sbom_occurrences
- sbom_occurrences_vulnerabilities
- sbom_sources
- dast_profile_schedules
- dast_profiles
- dast_profiles_pipelines
- dast_profiles_tags
- dast_scanner_profiles
- dast_scanner_profiles_builds
- dast_site_profile_secret_variables
- dast_site_profiles
- dast_site_profiles_builds
- dast_site_tokens
- dast_site_validations
- dast_sites
allow_cross_transactions:
- gitlab_internal
- gitlab_shared
- gitlab_main_cell:
specific_tables:
- security_findings
- security_scans
- vulnerabilities
- users_security_dashboard_projects
- vulnerability_exports
- vulnerability_external_issue_links
- vulnerability_feedback
- vulnerability_finding_evidences
- vulnerability_finding_links
- vulnerability_finding_signatures
- vulnerability_findings_remediations
- vulnerability_flags
- vulnerability_historical_statistics
- vulnerability_namespace_historical_statistics
- vulnerability_identifiers
- vulnerability_issue_links
- vulnerability_merge_request_links
- vulnerability_occurrence_identifiers
- vulnerability_occurrences
- vulnerability_reads
- vulnerability_remediations
- vulnerability_scanners
- vulnerability_state_transitions
- vulnerability_statistics
- vulnerability_user_mentions
- dependency_list_exports
- sbom_component_versions
- sbom_components
- sbom_occurrences
- sbom_occurrences_vulnerabilities
- sbom_sources
- dast_profile_schedules
- dast_profiles
- dast_profiles_pipelines
- dast_profiles_tags
- dast_scanner_profiles
- dast_scanner_profiles_builds
- dast_site_profile_secret_variables
- dast_site_profiles
- dast_site_profiles_builds
- dast_site_tokens
- dast_site_validations
- dast_sites
require_sharding_key: true

View File

@ -0,0 +1,49 @@
# frozen_string_literal: true
# See https://docs.gitlab.com/ee/development/database/batched_background_migrations.html
# for more information on when/how to queue batched background migrations
# Update below commented lines with appropriate values.
class QueueBackfillMissingNamespaceIdOnNotes < Gitlab::Database::Migration[2.2]
milestone '17.5'
restrict_gitlab_migration gitlab_schema: :gitlab_main
MIGRATION = "BackfillMissingNamespaceIdOnNotes"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 1000
SUB_BATCH_SIZE = 100
GITLAB_OPTIMIZED_BATCH_SIZE = 75_000
GITLAB_OPTIMIZED_SUB_BATCH_SIZE = 250
def up
queue_batched_background_migration(
MIGRATION,
:notes,
:id,
job_interval: DELAY_INTERVAL,
**batch_sizes
)
end
def down
delete_batched_background_migration(MIGRATION, :notes, :id, [])
end
private
def batch_sizes
if Gitlab.com_except_jh?
{
batch_size: GITLAB_OPTIMIZED_BATCH_SIZE,
sub_batch_size: GITLAB_OPTIMIZED_SUB_BATCH_SIZE
}
else
{
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
}
end
end
end

View File

@ -0,0 +1 @@
dff289034006ffbf12d45275cf91d3dc3843c80894cea79738ffec3149edbf76

View File

@ -22,6 +22,7 @@ swap:
(?<!right-)click(?!-through): "select"
cancelled: "canceled"
cancelling: "canceling"
cherry pick: "cherry-pick"
code base: "codebase"
config: "configuration"
confirmation box: "confirmation dialog"

View File

@ -17,13 +17,13 @@ To gain read only access to the S3 bucket with your application logs:
1. Open a [support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650) with the title `Customer Log Access`.
1. In the body of the ticket, include a list of IAM Principal Amazon Resource Names (ARNs) that require access to the logs from the S3 bucket. The ARNs can be for users or roles.
NOTE:
Specify the full ARN path without wildcards (`*`). Wildcard characters are not supported. GitLab team members can read more about the proposed feature to add wildcard support in this confidential issue: [7010](https://gitlab.com/gitlab-com/gl-infra/gitlab-dedicated/team/-/issues/7010).
GitLab provides the name of the S3 bucket. Your authorized users or roles can then access all objects in the bucket. To verify access, you can use the [AWS CLI](https://aws.amazon.com/cli/).
## Find your S3 bucket name
## Find your S3 bucket name
To find your S3 bucket name:

View File

@ -960,7 +960,7 @@ should be:
fetches, and probably the entire server, slows down.
WARNING:
All existing data in the specified directory will be removed.
All existing data in the specified directory will be removed.
Take care not to use a directory with existing data.
By default, the cache storage directory is set to a subdirectory of the first Gitaly storage

View File

@ -586,15 +586,15 @@ associated internal load balancer), three PostgreSQL servers, and one
application node.
In this setup, all servers share the same `10.6.0.0/16` private network range.
The servers communicate freely over these addresses.
The servers communicate freely over these addresses.
While you can use a different networking setup, it's recommended to ensure that it allows
for synchronous replication to occur across the cluster.
As a general rule, a latency of less than 2 ms ensures replication operations to be performant.
GitLab [reference architectures](../reference_architectures/index.md) are sized to
assume that application database queries are shared by all three nodes.
Communication latency higher than 2 ms can lead to database locks and
GitLab [reference architectures](../reference_architectures/index.md) are sized to
assume that application database queries are shared by all three nodes.
Communication latency higher than 2 ms can lead to database locks and
impact the replica's ability to serve read-only queries in a timely fashion.
- `10.6.0.22`: PgBouncer 2

View File

@ -87,7 +87,7 @@ Logging in the `application.json`, `production_json.log`, and `production.log` f
### Feature Flag and Logging Control
**Feature Flag Dependency**: You can control a subset of these logs by enabling or disabling the `expanded_ai_logging` feature flag. Disabling the feature flag disables logging for specific operations. For more information, see the [Feature Flag section under Privacy Considerations](../../development/ai_features/index.md#privacy-considerations).
**Feature Flag Dependency**: You can control a subset of these logs by enabling or disabling the `expanded_ai_logging` feature flag. Disabling the feature flag disables logging for specific operations. For more information, see the [Feature Flag section under Privacy Considerations](../../development/ai_features/logging.md#privacy-considerations).
### The `llm.log` file
@ -294,7 +294,7 @@ The `:expanded_ai_logging` feature flag controls whether additional debugging in
- **GitLab Self-Managed and self-hosted AI gateway**: The feature flag enables detailed logging to `llm.log` on the self-hosted instance, capturing inputs and outputs for AI models.
- **GitLab Self-Managed and GitLab-managed AI gateway**: The feature flag enables logging on your GitLab Self-Managed instance. However, the flag does **not** activate expanded logging for the GitLab-managed AI gateway side. Logging remains disabled for the cloud-connected AI gateway to protect sensitive data.
For more information, see the [Feature Flag section under Privacy Considerations](../../development/ai_features/index.md#privacy-considerations) documentation.
For more information, see the [Feature Flag section under Privacy Considerations](../../development/ai_features/logging.md#privacy-considerations) documentation.
### Logging in cloud-connected AI gateways

View File

@ -77,14 +77,14 @@ You should at least a `a2-highgpu-4g` machine on GCP or equivalent (4x Nvidia A1
```
1. Rename the token config:
```shell
cd <path-to-model>/Mixtral-8x7B-Instruct-v0.1
cp tokenizer.model tokenizer.model.v3
```
1. Run the model:
```shell
vllm serve <path-to-model>/Mixtral-8x7B-Instruct-v0.1 \
--tensor_parallel_size 4 \

View File

@ -36,7 +36,7 @@ Install one of the following GitLab-approved LLM models:
| GPT | [GPT-4o](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=python-secure#gpt-4o-and-gpt-4-turbo) | [Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/overview) | Generally available | 🟢 Green | 🟢 Green | 🟢 Green |
| GPT | [GPT-4o-mini](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=python-secure#gpt-4o-and-gpt-4-turbo) | [Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/overview) | Generally available | 🟢 Green | 🟢 Green | 🟡 Amber |
Legend:
Legend:
- 🟢 Green - Strongly recommended. The model can handle the feature without any loss of quality.
- 🟡 Amber - Recommended. The model supports the feature, but there might be minor compromises or limitations.

View File

@ -104,7 +104,7 @@ To turn on restricted access:
1. On the left sidebar, select **Settings > General**.
1. Expand **Sign-up restrictions**.
1. Under **Seat controls**, select **Restricted access**.
1. Under **Seat control**, select **Restricted access**.
### Known issues
@ -283,7 +283,7 @@ To turn on approvals for role promotions:
1. On the left sidebar, at the bottom, select **Admin**.
1. Select **Settings > General**.
1. Expand **Sign-up restrictions**.
1. In the **Seat controls** section, select **Approve role promotions**.
1. In the **Seat control** section, select **Approve role promotions**.
### Known issues

View File

@ -251,7 +251,7 @@ The relative path is displayed.
## Restoring Terraform state files from backups
To restore Terraform state files from backups, you must have access to the encrypted state files and the GitLab database.
To restore Terraform state files from backups, you must have access to the encrypted state files and the GitLab database.
### Database tables
@ -263,7 +263,7 @@ The following database table helps trace the S3 path back to specific projects:
The state files are stored in a specific directory structure, where:
- The first three segments of the path are derived from the SHA-2 hash value of the project ID.
- The first three segments of the path are derived from the SHA-2 hash value of the project ID.
- Each state has a UUID stored on the `terraform_states` database table that forms part of the path.
For example, for a project where the:
@ -293,6 +293,6 @@ The state files are encrypted using Lockbox and require the following informatio
The encryption key is derived from both the `db_key_base` and the project ID. If you can't access `db_key_base`, decryption is not possible.
To learn how to manually decrypt files, see the documentation from [Lockbox](https://github.com/ankane/lockbox).
To learn how to manually decrypt files, see the documentation from [Lockbox](https://github.com/ankane/lockbox).
To view the encryption key generation process, see the [state uploader code](https://gitlab.com/gitlab-org/gitlab/-/blob/e0137111fbbd28316f38da30075aba641e702b98/app/uploaders/terraform/state_uploader.rb#L43).

View File

@ -21223,6 +21223,7 @@ Represents an allowlist entry for the CI_JOB_TOKEN.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cijobtokenscopeallowlistentryaddedby"></a>`addedBy` | [`UserCore`](#usercore) | User that added the entry. |
| <a id="cijobtokenscopeallowlistentryautopopulated"></a>`autopopulated` | [`Boolean`](#boolean) | Indicates whether the entry is created by the autopopulation process. |
| <a id="cijobtokenscopeallowlistentrycreatedat"></a>`createdAt` | [`Time!`](#time) | When the entry was created. |
| <a id="cijobtokenscopeallowlistentrydefaultpermissions"></a>`defaultPermissions` | [`Boolean`](#boolean) | Indicates whether default permissions are enabled (true) or fine-grained permissions are enabled (false). |
| <a id="cijobtokenscopeallowlistentrydirection"></a>`direction` | [`String`](#string) | Direction of access. Defaults to INBOUND. |
@ -21236,9 +21237,11 @@ Represents an allowlist entry for the CI_JOB_TOKEN.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cijobtokenscopetypegroupallowlistautopopulatedids"></a>`groupAllowlistAutopopulatedIds` | [`[GroupID!]!`](#groupid) | List of IDs of groups which have been created by the autopopulation process. |
| <a id="cijobtokenscopetypegroupsallowlist"></a>`groupsAllowlist` | [`GroupConnection!`](#groupconnection) | Allowlist of groups that can access the current project by authenticating with a CI/CD job token. (see [Connections](#connections)) |
| <a id="cijobtokenscopetypegroupsallowlistcount"></a>`groupsAllowlistCount` | [`Int!`](#int) | Count of groups that can access the current project by authenticating with a CI/CD job token. The count does not include subgroups. |
| <a id="cijobtokenscopetypeinboundallowlist"></a>`inboundAllowlist` | [`ProjectConnection!`](#projectconnection) | Allowlist of projects that can access the current project by authenticating with a CI/CD job token. (see [Connections](#connections)) |
| <a id="cijobtokenscopetypeinboundallowlistautopopulatedids"></a>`inboundAllowlistAutopopulatedIds` | [`[ProjectID!]!`](#projectid) | List of IDs of projects which have been created by the autopopulation process. |
| <a id="cijobtokenscopetypeinboundallowlistcount"></a>`inboundAllowlistCount` | [`Int!`](#int) | Count of projects that can access the current project by authenticating with a CI/CD job token. The count does not include nested projects. |
| <a id="cijobtokenscopetypeoutboundallowlist"></a>`outboundAllowlist` | [`ProjectConnection!`](#projectconnection) | Allow list of projects that are accessible using the current project's CI Job tokens. (see [Connections](#connections)) |
| <a id="cijobtokenscopetypeprojects"></a>`projects` **{warning-solid}** | [`ProjectConnection!`](#projectconnection) | **Deprecated** in GitLab 15.9. The `projects` attribute is being deprecated. Use `outbound_allowlist`. |

View File

@ -12,11 +12,11 @@ DETAILS:
Use this API to interact with group access tokens. For more information, see [Group access tokens](../user/group/settings/group_access_tokens.md).
## List group access tokens
## List all group access tokens
> - `state` attribute [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/462217) in GitLab 17.2.
Get a list of [group access tokens](../user/group/settings/group_access_tokens.md).
Lists all group access tokens for a group.
In GitLab 17.2 and later, you can use the `state` attribute to limit the response to group access tokens with a specified state.
@ -25,13 +25,15 @@ GET /groups/:id/access_tokens
GET /groups/:id/access_tokens?state=inactive
```
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer or string | yes | ID or [URL-encoded path of the group](rest/index.md#namespaced-paths) |
| `state` | string | No | Limit results to tokens with specified state. Valid values are `active` and `inactive`. By default both states are returned. |
| Attribute | Type | required | Description |
| --------- | ----------------- | -------- | ----------- |
| `id` | integer or string | yes | ID or [URL-encoded path](rest/index.md#namespaced-paths) of a group. |
| `state` | string | No | If defined, only returns tokens with the specified state. Possible values: `active` and `inactive`. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/<group_id>/access_tokens"
curl --request GET \
--header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/<group_id>/access_tokens"
```
```json
@ -67,21 +69,23 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a
]
```
## Get a group access token
## Get details on a group access token
Get a [group access token](../user/group/settings/group_access_tokens.md) by ID.
Gets details on a group access token. You can reference a specific group access token, or use the keyword `self` to return details on the authenticating group access token.
```plaintext
GET /groups/:id/access_tokens/:token_id
```
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer or string | yes | ID or [URL-encoded path of the group](rest/index.md#namespaced-paths) |
| `token_id` | integer | yes | ID of the group access token |
| Attribute | Type | required | Description |
| ---------- | ----------------- | -------- | ----------- |
| `id` | integer or string | yes | ID or [URL-encoded path](rest/index.md#namespaced-paths) of a group. |
| `token_id` | integer or string | yes | ID of a group access token or the keyword `self`. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/<group_id>/access_tokens/<token_id>"
curl --request GET \
--header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/<group_id>/access_tokens/<token_id>"
```
```json
@ -104,26 +108,30 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a
> - The `expires_at` attribute default was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120213) in GitLab 16.0.
Create a [group access token](../user/group/settings/group_access_tokens.md). You must have the Owner role for the
group to create group access tokens.
Creates a group access token for a specified group.
Prerequisites:
- You must have the Owner role for the group.
```plaintext
POST /groups/:id/access_tokens
```
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer or string | yes | ID or [URL-encoded path of the group](rest/index.md#namespaced-paths) |
| `name` | String | yes | Name of the group access token |
| `scopes` | `Array[String]` | yes | [List of scopes](../user/group/settings/group_access_tokens.md#scopes-for-a-group-access-token) |
| `access_level` | Integer | no | Access level. Valid values are `10` (Guest), `15` (Planner), `20` (Reporter), `30` (Developer), `40` (Maintainer), and `50` (Owner). |
| `expires_at` | Date | yes | Expiration date of the access token in ISO format (`YYYY-MM-DD`). If undefined, the date is set to the [maximum allowable lifetime limit](../user/profile/personal_access_tokens.md#access-token-expiration). |
| Attribute | Type | required | Description |
| -------------- | ----------------- | -------- | ----------- |
| `id` | integer or string | yes | ID or [URL-encoded path](rest/index.md#namespaced-paths) of a group. |
| `name` | String | yes | Name of the token. |
| `scopes` | `Array[String]` | yes | List of [scopes](../user/group/settings/group_access_tokens.md#scopes-for-a-group-access-token) available to the token. |
| `access_level` | Integer | no | [Access level](../development/permissions/predefined_roles.md#members) for the token. Possible values: `10` (Guest), `15` (Planner), `20` (Reporter), `30` (Developer), `40` (Maintainer), and `50` (Owner). Default value: `40`. |
| `expires_at` | date | no | Expiration date of the access token in ISO format (`YYYY-MM-DD`). The date must be one year or less from the rotation date. If undefined, the date is set to the [maximum allowable lifetime limit](../user/profile/personal_access_tokens.md#access-token-expiration). |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
--header "Content-Type:application/json" \
--data '{ "name":"test_token", "scopes":["api", "read_repository"], "expires_at":"2021-01-31", "access_level": 30 }' \
"https://gitlab.example.com/api/v4/groups/<group_id>/access_tokens"
curl --request POST \
--header "PRIVATE-TOKEN: <your_access_token>" \
--header "Content-Type:application/json" \
--data '{ "name":"test_token", "scopes":["api", "read_repository"], "expires_at":"2021-01-31", "access_level": 30 }' \
--url "https://gitlab.example.com/api/v4/groups/<group_id>/access_tokens"
```
```json
@ -146,36 +154,31 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
## Rotate a group access token
Rotate a group access token. Revokes the previous token and creates a new token that expires in one week.
You can either:
- Use the group access token ID.
- In GitLab 17.9 and later, pass the group access token to the API in a request header.
### Use a group access token ID
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/403042) in GitLab 16.0
> - `expires_at` attribute [added](https://gitlab.com/gitlab-org/gitlab/-/issues/416795) in GitLab 16.6.
Rotates a group access token. This immediately revokes the previous token and creates a new token. Generally, this endpoint rotates a specific group access token by authenticating with a personal access token. You can also use a group access token to rotate itself. For more information, see [Self-rotation](#self-rotation).
If you attempt to use the revoked token later, GitLab immediately revokes the new token. For more information, see [Automatic reuse detection](personal_access_tokens.md#automatic-reuse-detection).
Prerequisites:
- You must have a [personal access token with the `api` scope](../user/profile/personal_access_tokens.md#personal-access-token-scopes).
In GitLab 16.6 and later, you can use the `expires_at` parameter to set a different expiry date. This non-default expiry date can be up to a maximum of one year from the rotation date.
- A personal access token with the [`api` scope](../user/profile/personal_access_tokens.md#personal-access-token-scopes) or a group access token with the [`api` or `self_rotate` scope](../user/profile/personal_access_tokens.md#personal-access-token-scopes). See [Self-rotation](#self-rotation).
```plaintext
POST /groups/:id/access_tokens/:token_id/rotate
```
| Attribute | Type | required | Description |
|-----------|------------|----------|---------------------|
| `id` | integer or string | yes | ID or [URL-encoded path of the group](rest/index.md#namespaced-paths) |
| `token_id` | integer | yes | ID of the access token |
| `expires_at` | date | no | Expiration date of the access token in ISO format (`YYYY-MM-DD`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/416795) in GitLab 16.6. If undefined, the token expires after one week. |
| Attribute | Type | required | Description |
| ------------ | ----------------- | -------- | ----------- |
| `id` | integer or string | yes | ID or [URL-encoded path](rest/index.md#namespaced-paths) of a group. |
| `token_id` | integer or string | yes | ID of a group access token or the keyword `self`. |
| `expires_at` | date | no | Expiration date of the access token in ISO format (`YYYY-MM-DD`). The date must be one year or less from the rotation date. If undefined, the token expires after one week. |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
"https://gitlab.example.com/api/v4/groups/<group_id>/access_tokens/<token_id>/rotate"
curl --request POST \
--header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/<group_id>/access_tokens/<token_id>/rotate"
```
Example response:
@ -197,92 +200,58 @@ Example response:
}
```
#### Responses
If successful, returns `200: OK`.
- `200: OK` if existing token is successfully revoked and the new token is created.
- `400: Bad Request` if not rotated successfully.
- `401: Unauthorized` if either the:
- User does not have access to the token with the specified ID.
- Token with the specified ID does not exist.
- `401: Unauthorized` if any of the following conditions are true:
- You do not have access to the specified token.
- The specified token does not exist.
- You're authenticating with a group access token. Use [`/groups/:id/access_tokens/self/rotate`](#use-a-request-header). instead.
- `404: Not Found` if the user is an administrator but the token with the specified ID does not exist.
Other possible responses:
### Use a request header
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/178111) in GitLab 17.9
Requires:
- `api` or `self_rotate` scope.
In GitLab 16.6 and later, you can use the `expires_at` parameter to set a different expiry date. This non-default expiry date is subject to the [maximum allowable lifetime limits](../user/profile/personal_access_tokens.md#access-token-expiration).
```plaintext
POST /groups/:id/access_tokens/self/rotate
```
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_group_access_token>" \
"https://gitlab.example.com/api/v4/groups/<group_id>/access_tokens/self/rotate"
```
Example response:
```json
{
"id": 42,
"name": "Rotated Token",
"revoked": false,
"created_at": "2025-01-19T15:00:00.000Z",
"description": "Test group access token",
"scopes": ["read_api","self_rotate"],
"user_id": 1337,
"last_used_at": null,
"active": true,
"expires_at": "2025-01-26",
"access_level": 30,
"token": "s3cr3t"
}
```
#### Responses
- `200: OK` if the existing group access token is successfully revoked and the new token successfully created.
- `400: Bad Request` if not rotated successfully.
- `401: Unauthorized` if any of the following conditions are true:
- The token does not exist.
- The token has expired.
- The token was revoked.
- The token is not a group access token associated with the specified group.
- You do not have access to the specified token.
- You're using a group access token to rotate another group access token. See [Self-rotate a project access token](#self-rotation) instead.
- `403: Forbidden` if the token is not allowed to rotate itself.
- `404: Not Found` if the user is an administrator but the token with the specified ID does not exist.
- `405: Method Not Allowed` if the token is not an access token.
### Automatic reuse detection
### Self-rotation
Refer to [automatic reuse detection for personal access tokens](personal_access_tokens.md#automatic-reuse-detection)
for more information.
Instead of rotating a specific group access token, you can instead rotate the same group access token you used to authenticate the request. To self-rotate a group access token, you must:
- Rotate a group access token with the [`api` or `self_rotate` scope](../user/profile/personal_access_tokens.md#personal-access-token-scopes).
- Use the `self` keyword in the request URL.
Example request:
```shell
curl --request POST \
--header "PRIVATE-TOKEN: <your_group_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/<group_id>/access_tokens/self/rotate"
```
## Revoke a group access token
Revoke a [group access token](../user/group/settings/group_access_tokens.md).
Revokes a specified group access token.
```plaintext
DELETE /groups/:id/access_tokens/:token_id
```
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer or string | yes | ID or [URL-encoded path of the group](rest/index.md#namespaced-paths) |
| `token_id` | integer | yes | ID of the group access token |
| Attribute | Type | required | Description |
| ---------- | ----------------- | -------- | ----------- |
| `id` | integer or string | yes | ID or [URL-encoded path](rest/index.md#namespaced-paths) of a group. |
| `token_id` | integer | yes | ID of a group access token. |
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/<group_id>/access_tokens/<token_id>"
curl --request DELETE
--header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/<group_id>/access_tokens/<token_id>"
```
### Responses
If successful, returns `204 No content`.
- `204: No Content` if successfully revoked.
- `400 Bad Request` or `404 Not Found` if not revoked successfully.
Other possible responses:
- `400 Bad Request`: Token was not revoked.
- `404 Not Found`: Token can not be found.

View File

@ -50,7 +50,7 @@ The Service Ping JSON payload includes `schema_inconsistencies_metric`. Database
This metric is designed only for troubleshooting ongoing issues, and shouldn't be used as a regular health check. The metric should only be interpreted with
the guidance of GitLab Support. The metric reports the same database schema inconsistencies as the
[database schema checker Rake task](../administration/raketasks/maintenance.md#check-the-database-for-schema-inconsistencies).
[database schema checker Rake task](../administration/raketasks/maintenance.md#check-the-database-for-schema-inconsistencies).
For more information, see [issue 467544](https://gitlab.com/gitlab-org/gitlab/-/issues/467544).

View File

@ -20,7 +20,7 @@ Use the dashboard for Kubernetes to understand the status of your clusters with
The dashboard works with every connected Kubernetes cluster, whether you deployed them
with CI/CD or GitOps.
![Kubernetes summary UI](img/kubernetes_summary_ui_v17_2.png)
![Dashboard showing the status of Kubernetes pods and services.](img/kubernetes_summary_ui_v17_2.png)
## Configure a dashboard
@ -200,7 +200,7 @@ Each dashboard displays a list of resources with their statuses, namespaces, and
You can select a resource to open a drawer with more information, including labels
and YAML-formatted status, annotations, and spec.
![Kubernetes dashboard UI](img/kubernetes_dashboard_deployments_v16_9.png)
![Dashboard with detailed information about the connected cluster.](img/kubernetes_dashboard_deployments_v16_9.png)
Because of the focus shift described in [this issue](https://gitlab.com/gitlab-org/ci-cd/deploy-stage/environments-group/general/-/issues/53#note_1720060812), work on the detailed dashboard is paused.

View File

@ -171,9 +171,9 @@ Maintainers can:
- Unprotect a protected environment by selecting the **Unprotect** button for that environment.
After an environment is unprotected, all access entries are deleted and must
be re-entered if the environment is re-protected.
be re-entered if the environment is re-protected.
After an approval rule is deleted, previously approved deployments do not show who approved the deployment.
After an approval rule is deleted, previously approved deployments do not show who approved the deployment.
Information on who approved a deployment is still available in the [project audit events](../../user/compliance/audit_events.md#project-audit-events).
If a new rule is added, previous deployments show the new rules without the option to approve the deployment. [Issue 506687](https://gitlab.com/gitlab-org/gitlab/-/issues/506687) proposes to show the full approval history of deployments, even if an approval rule is deleted.

View File

@ -45,36 +45,34 @@ for complete control over the build environment.
## Configure code signing with fastlane
To set up code signing for iOS:
To set up code signing for iOS, upload signed certificates to GitLab by using fastlane:
1. Install fastlane locally to upload signed certificates to GitLab:
1. Initialize fastlane:
1. Initialize fastlane:
```shell
fastlane init
```
```shell
fastlane init
```
1. Generate a `Matchfile` with the configuration:
1. Generate a `Matchfile` with the configuration:
```shell
fastlane match init
```
```shell
fastlane match init
```
1. Generate certificates and profiles in the Apple Developer portal and upload those files to GitLab:
1. Generate certificates and profiles in the Apple Developer portal and upload those files to GitLab:
```shell
PRIVATE_TOKEN=YOUR-TOKEN bundle exec fastlane match development
```
```shell
PRIVATE_TOKEN=YOUR-TOKEN bundle exec fastlane match development
```
1. Optional. If you have already created signing certificates and provisioning profiles for your project, use `fastlane match import` to load your existing files into GitLab:
1. Optional. If you have already created signing certificates and provisioning profiles for your project, use `fastlane match import` to load your existing files into GitLab:
```shell
PRIVATE_TOKEN=YOUR-TOKEN bundle exec fastlane match import
```
```shell
PRIVATE_TOKEN=YOUR-TOKEN bundle exec fastlane match import
```
You are prompted to input the path to your files. After you provide those details, your files are uploaded and visible in your projects CI/CD settings.
If prompted for the `git_url` during the import, it is safe to leave it blank and press <kbd>enter</kbd>.
You are prompted to input the path to your files. After you provide those details, your files are uploaded and visible in your project's CI/CD settings.
If prompted for the `git_url` during the import, it is safe to leave it blank and press <kbd>enter</kbd>.
The following are sample `fastlane/Fastfile` and `.gitlab-ci.yml` files with this configuration:

View File

@ -43,7 +43,7 @@ GitLab offers the following machine type for hosted runners on Linux Arm64.
NOTE:
Users can experience network connectivity issues when they use Docker-in-Docker with hosted runners on Linux
Arm. This issue occurs when the maximum transmission unit (MTU) value in Google Cloud and Docker don't match.
To resolve this issue, set `--mtu=1400` in the client side Docker configuration.
To resolve this issue, set `--mtu=1400` in the client side Docker configuration.
For more details, see [issue 473739](https://gitlab.com/gitlab-org/gitlab/-/issues/473739#workaround).
## Container images

View File

@ -0,0 +1,337 @@
---
stage: AI-powered
group: AI Framework
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
---
# AI actions
This page includes how to implement actions and migrate them to the AI Gateway.
## How to implement a new action
Implementing a new AI action will require changes across different components.
We'll use the example of wanting to implement an action that allows users to rewrite issue descriptions according to
a given prompt.
### 1. Add your action to the Cloud Connector feature list
The Cloud Connector configuration stores the permissions needed to access your service, as well as additional metadata.
If there's no entry for your feature, [add the feature as a Cloud Connector unit primitive](../cloud_connector/index.md#register-new-feature-for-self-managed-dedicated-and-gitlabcom-customers):
For more information, see [Cloud Connector: Configuration](../cloud_connector/configuration.md).
### 2. Create a prompt definition in the AI gateway
In [the AI gateway project](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist), create a
new prompt definition under `ai_gateway/prompts/definitions`. Create a new subfolder corresponding to the name of your
AI action, and a new YAML file for your prompt. Specify the model and provider you wish to use, and the prompts that
will be fed to the model. You can specify inputs to be plugged into the prompt by using `{}`.
```yaml
# ai_gateway/prompts/definitions/rewrite_description/base.yml
name: Description rewriter
model:
name: claude-3-sonnet-20240229
params:
model_class_provider: anthropic
prompt_template:
system: |
You are a helpful assistant that rewrites the description of resources. You'll be given the current description, and a prompt on how you should rewrite it. Reply only with your rewritten description.
<description>{description}</description>
<prompt>{prompt}</prompt>
```
If your AI action is part of a broader feature, the definitions can be organized in a tree structure:
```yaml
# ai_gateway/prompts/definitions/code_suggestions/generations/base.yml
name: Code generations
model:
name: claude-3-sonnet-20240229
params:
model_class_provider: anthropic
...
```
To specify prompts for multiple models, use the name of the model as the filename for the definition:
```yaml
# ai_gateway/prompts/definitions/code_suggestions/generations/mistral.yml
name: Code generations
model:
name: mistral
params:
model_class_provider: litellm
...
```
### 3. Create a Completion class
1. Create a new completion under `ee/lib/gitlab/llm/ai_gateway/completions/` and inherit it from the `Base`
AI gateway Completion.
```ruby
# ee/lib/gitlab/llm/ai_gateway/completions/rewrite_description.rb
module Gitlab
module Llm
module AiGateway
module Completions
class RewriteDescription < Base
def inputs
{ description: resource.description, prompt: prompt_message.content }
end
end
end
end
end
end
```
### 4. Create a Service
1. Create a new service under `ee/app/services/llm/` and inherit it from the `BaseService`.
1. The `resource` is the object we want to act on. It can be any object that includes the `Ai::Model` concern. For example it could be a `Project`, `MergeRequest`, or `Issue`.
```ruby
# ee/app/services/llm/rewrite_description_service.rb
module Llm
class RewriteDescriptionService < BaseService
extend ::Gitlab::Utils::Override
override :valid
def valid?
super &&
# You can restrict which type of resources your service applies to
resource.to_ability_name == "issue" &&
# Always check that the user is allowed to perform this action on the resource
Ability.allowed?(user, :rewrite_description, resource)
end
private
def perform
schedule_completion_worker
end
end
end
```
### 5. Register the feature in the catalogue
Go to `Gitlab::Llm::Utils::AiFeaturesCatalogue` and add a new entry for your AI action.
```ruby
class AiFeaturesCatalogue
LIST = {
# ...
rewrite_description: {
service_class: ::Gitlab::Llm::AiGateway::Completions::RewriteDescription,
feature_category: :ai_abstraction_layer,
execute_method: ::Llm::RewriteDescriptionService,
maturity: :experimental,
self_managed: false,
internal: false
}
}.freeze
```
## How to migrate an existing action to the AI gateway
AI actions were initially implemented inside the GitLab monolith. As part of our
[AI gateway as the Sole Access Point for Monolith to Access Models Epic](https://gitlab.com/groups/gitlab-org/-/epics/13024)
we're migrating prompts, model selection and model parameters into the AI gateway. This will increase the speed at which
we can deliver improvements to users on GitLab Self-Managed, by decoupling prompt and model changes from monolith releases. To
migrate an existing action:
1. Follow steps 1 through 3 on [How to implement a new action](#how-to-implement-a-new-action).
1. Modify the entry for your AI action in the catalogue to list the new completion class as the `aigw_service_class`.
```ruby
class AiFeaturesCatalogue
LIST = {
# ...
generate_description: {
service_class: ::Gitlab::Llm::Anthropic::Completions::GenerateDescription,
aigw_service_class: ::Gitlab::Llm::AiGateway::Completions::GenerateDescription,
prompt_class: ::Gitlab::Llm::Templates::GenerateDescription,
feature_category: :ai_abstraction_layer,
execute_method: ::Llm::GenerateDescriptionService,
maturity: :experimental,
self_managed: false,
internal: false
},
# ...
}.freeze
```
1. Create `prompt_migration_#{feature_name}` feature flag (e.g `prompt_migration_generate_description`)
When the feature flag is enabled, the `aigw_service_class` will be used to process the AI action.
Once you've validated the correct functioning of your action, you can remove the `aigw_service_class` key and replace
the `service_class` with the new `AiGateway::Completions` class to make it the permanent provider.
For a complete example of the changes needed to migrate an AI action, see the following MRs:
- [Changes to the GitLab Rails monolith](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/152429)
- [Changes to the AI gateway](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/merge_requests/921)
### Authorization in GitLab-Rails
We recommend to use [policies](../policies.md) to deal with authorization for a feature. Currently we need to make sure to cover the following checks:
Some basic authorization is included in the Abstraction Layer classes that are base classes for more specialized classes.
What needs to be included in the code:
1. Check for feature flag compatibility: `Gitlab::Llm::Utils::FlagChecker.flag_enabled_for_feature?(ai_action)` - included in the `Llm::BaseService` class.
1. Check if resource is authorized: `Gitlab::Llm::Utils::Authorizer.resource(resource: resource, user: user).allowed?` - also included in the `Llm::BaseService` class.
1. Both of those checks are included in the `::Gitlab::Llm::FeatureAuthorizer.new(container: subject_container, feature_name: action_name).allowed?`
1. Access to AI features depend on several factors, such as: their maturity, if they are enabled on self-managed, if they are bundled within an add-on etc.
- [Example](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/policies/ee/global_policy.rb#L222-222) of policy not connected to the particular resource.
- [Example](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/policies/ee/issue_policy.rb#L25-25) of policy connected to the particular resource.
NOTE:
For more information, see [the GitLab AI gateway documentation](https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/main/doc/howto/gitlab_ai_gateway.md#optional-enable-authentication-and-authorization-in-ai-gateway) about authentication and authorization in AI gateway.
If your Duo feature involves an autonomous agent, you should use
[composite identity](composite_identity.md) authorization.
### Pairing requests with responses
Because multiple users' requests can be processed in parallel, when receiving responses,
it can be difficult to pair a response with its original request. The `requestId`
field can be used for this purpose, because both the request and response are assured
to have the same `requestId` UUID.
### Caching
AI requests and responses can be cached. Cached conversation is being used to
display user interaction with AI features. In the current implementation, this cache
is not used to skip consecutive calls to the AI service when a user repeats
their requests.
```graphql
query {
aiMessages {
nodes {
id
requestId
content
role
errors
timestamp
}
}
}
```
This cache is used for chat functionality. For other services, caching is
disabled. You can enable this for a service by using the `cache_response: true`
option.
Caching has following limitations:
- Messages are stored in Redis stream.
- There is a single stream of messages per user. This means that all services
currently share the same cache. If needed, this could be extended to multiple
streams per user (after checking with the infrastructure team that Redis can handle
the estimated amount of messages).
- Only the last 50 messages (requests + responses) are kept.
- Expiration time of the stream is 3 days since adding last message.
- User can access only their own messages. There is no authorization on the caching
level, and any authorization (if accessed by not current user) is expected on
the service layer.
### Check if feature is allowed for this resource based on namespace settings
There is one setting allowed on root namespace level that restrict the use of AI features:
- `experiment_features_enabled`
To check if that feature is allowed for a given namespace, call:
```ruby
Gitlab::Llm::StageCheck.available?(namespace, :name_of_the_feature)
```
Add the name of the feature to the `Gitlab::Llm::StageCheck` class. There are
arrays there that differentiate between experimental and beta features.
This way we are ready for the following different cases:
- If the feature is not in any array, the check will return `true`. For example, the feature is generally available.
To move the feature from the experimental phase to the beta phase, move the name of the feature from the `EXPERIMENTAL_FEATURES` array to the `BETA_FEATURES` array.
### Implement calls to AI APIs and the prompts
The `CompletionWorker` will call the `Completions::Factory` which will initialize the Service and execute the actual call to the API.
In our example, we will use VertexAI and implement two new classes:
```ruby
# /ee/lib/gitlab/llm/vertex_ai/completions/rewrite_description.rb
module Gitlab
module Llm
module VertexAi
module Completions
class AmazingNewAiFeature < Gitlab::Llm::Completions::Base
def execute
prompt = ai_prompt_class.new(options[:user_input]).to_prompt
response = Gitlab::Llm::VertexAi::Client.new(user, unit_primitive: 'amazing_feature').text(content: prompt)
response_modifier = ::Gitlab::Llm::VertexAi::ResponseModifiers::Predictions.new(response)
::Gitlab::Llm::GraphqlSubscriptionResponseService.new(
user, nil, response_modifier, options: response_options
).execute
end
end
end
end
end
end
```
```ruby
# /ee/lib/gitlab/llm/vertex_ai/templates/rewrite_description.rb
module Gitlab
module Llm
module VertexAi
module Templates
class AmazingNewAiFeature
def initialize(user_input)
@user_input = user_input
end
def to_prompt
<<~PROMPT
You are an assistant that writes code for the following context:
context: #{user_input}
PROMPT
end
end
end
end
end
end
```
Because we support multiple AI providers, you may also use those providers for
the same example:
```ruby
Gitlab::Llm::VertexAi::Client.new(user, unit_primitive: 'your_feature')
Gitlab::Llm::Anthropic::Client.new(user, unit_primitive: 'your_feature')
```

View File

@ -506,7 +506,7 @@ Please, see the video ([internal link](https://drive.google.com/file/d/1X6CARf0g
### (Deprecated) Issue and epic experiments
NOTE:
This section is deprecated in favor of the [development seed file](index.md#seed-project-and-group-resources-for-testing-and-evaluation).
This section is deprecated in favor of the [development seed file](model_migration.md#seed-project-and-group-resources-for-testing-and-evaluation).
If you would like to use the evaluation framework (as described [here](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/prompt-library/-/blob/main/doc/how-to/run_duo_chat_eval.md?ref_type=heads#evaluation-on-issueepic))
you can import the required groups and projects using this Rake task:
@ -524,7 +524,7 @@ desired.
#### (Deprecated) Epic and issue fixtures
NOTE:
This section is deprecated in favor of the [development seed file](index.md#seed-project-and-group-resources-for-testing-and-evaluation).
This section is deprecated in favor of the [development seed file](model_migration.md#seed-project-and-group-resources-for-testing-and-evaluation).
The fixtures are the replicas of the _public_ issues and epics from projects and groups _owned by_ GitLab.
The internal notes were excluded when they were sampled. The fixtures have been committed into the canonical `gitlab` repository.

View File

@ -386,141 +386,6 @@ I --> J[GraphqlTriggers.ai_completion_response]
J --> K[::GitlabSchema.subscriptions.trigger]
```
## How to implement a new action
Implementing a new AI action will require changes across different components.
We'll use the example of wanting to implement an action that allows users to rewrite issue descriptions according to
a given prompt.
### 1. Add your action to the Cloud Connector feature list
The Cloud Connector configuration stores the permissions needed to access your service, as well as additional metadata.
If there's no entry for your feature, [add the feature as a Cloud Connector unit primitive](../cloud_connector/index.md#register-new-feature-for-self-managed-dedicated-and-gitlabcom-customers):
For more information, see [Cloud Connector: Configuration](../cloud_connector/configuration.md).
### 2. Create a prompt definition in the AI gateway
In [the AI gateway project](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist), create a
new prompt definition under `ai_gateway/prompts/definitions`. Create a new subfolder corresponding to the name of your
AI action, and a new YAML file for your prompt. Specify the model and provider you wish to use, and the prompts that
will be fed to the model. You can specify inputs to be plugged into the prompt by using `{}`.
```yaml
# ai_gateway/prompts/definitions/rewrite_description/base.yml
name: Description rewriter
model:
name: claude-3-sonnet-20240229
params:
model_class_provider: anthropic
prompt_template:
system: |
You are a helpful assistant that rewrites the description of resources. You'll be given the current description, and a prompt on how you should rewrite it. Reply only with your rewritten description.
<description>{description}</description>
<prompt>{prompt}</prompt>
```
If your AI action is part of a broader feature, the definitions can be organized in a tree structure:
```yaml
# ai_gateway/prompts/definitions/code_suggestions/generations/base.yml
name: Code generations
model:
name: claude-3-sonnet-20240229
params:
model_class_provider: anthropic
...
```
To specify prompts for multiple models, use the name of the model as the filename for the definition:
```yaml
# ai_gateway/prompts/definitions/code_suggestions/generations/mistral.yml
name: Code generations
model:
name: mistral
params:
model_class_provider: litellm
...
```
### 3. Create a Completion class
1. Create a new completion under `ee/lib/gitlab/llm/ai_gateway/completions/` and inherit it from the `Base`
AI gateway Completion.
```ruby
# ee/lib/gitlab/llm/ai_gateway/completions/rewrite_description.rb
module Gitlab
module Llm
module AiGateway
module Completions
class RewriteDescription < Base
def inputs
{ description: resource.description, prompt: prompt_message.content }
end
end
end
end
end
end
```
### 4. Create a Service
1. Create a new service under `ee/app/services/llm/` and inherit it from the `BaseService`.
1. The `resource` is the object we want to act on. It can be any object that includes the `Ai::Model` concern. For example it could be a `Project`, `MergeRequest`, or `Issue`.
```ruby
# ee/app/services/llm/rewrite_description_service.rb
module Llm
class RewriteDescriptionService < BaseService
extend ::Gitlab::Utils::Override
override :valid
def valid?
super &&
# You can restrict which type of resources your service applies to
resource.to_ability_name == "issue" &&
# Always check that the user is allowed to perform this action on the resource
Ability.allowed?(user, :rewrite_description, resource)
end
private
def perform
schedule_completion_worker
end
end
end
```
### 5. Register the feature in the catalogue
Go to `Gitlab::Llm::Utils::AiFeaturesCatalogue` and add a new entry for your AI action.
```ruby
class AiFeaturesCatalogue
LIST = {
# ...
rewrite_description: {
service_class: ::Gitlab::Llm::AiGateway::Completions::RewriteDescription,
feature_category: :ai_abstraction_layer,
execute_method: ::Llm::RewriteDescriptionService,
maturity: :experimental,
self_managed: false,
internal: false
}
}.freeze
```
## Reuse the existing AI components for multiple models
We thrive optimizing AI components, such as prompt, input/output parser, tools/function-calling, for each LLM,
@ -534,199 +399,6 @@ Here are the rules of thumbs:
An [example](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/issues/713) of this case is that we can apply Claude specific CoT optimization to the other models such as Mixtral as long as it doesn't cause a quality degradation.
## How to migrate an existing action to the AI gateway
AI actions were initially implemented inside the GitLab monolith. As part of our
[AI gateway as the Sole Access Point for Monolith to Access Models Epic](https://gitlab.com/groups/gitlab-org/-/epics/13024)
we're migrating prompts, model selection and model parameters into the AI gateway. This will increase the speed at which
we can deliver improvements to users on GitLab Self-Managed, by decoupling prompt and model changes from monolith releases. To
migrate an existing action:
1. Follow steps 1 through 3 on [How to implement a new action](#how-to-implement-a-new-action).
1. Modify the entry for your AI action in the catalogue to list the new completion class as the `aigw_service_class`.
```ruby
class AiFeaturesCatalogue
LIST = {
# ...
generate_description: {
service_class: ::Gitlab::Llm::Anthropic::Completions::GenerateDescription,
aigw_service_class: ::Gitlab::Llm::AiGateway::Completions::GenerateDescription,
prompt_class: ::Gitlab::Llm::Templates::GenerateDescription,
feature_category: :ai_abstraction_layer,
execute_method: ::Llm::GenerateDescriptionService,
maturity: :experimental,
self_managed: false,
internal: false
},
# ...
}.freeze
```
1. Create `prompt_migration_#{feature_name}` feature flag (e.g `prompt_migration_generate_description`)
When the feature flag is enabled, the `aigw_service_class` will be used to process the AI action.
Once you've validated the correct functioning of your action, you can remove the `aigw_service_class` key and replace
the `service_class` with the new `AiGateway::Completions` class to make it the permanent provider.
For a complete example of the changes needed to migrate an AI action, see the following MRs:
- [Changes to the GitLab Rails monolith](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/152429)
- [Changes to the AI gateway](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/merge_requests/921)
### Authorization in GitLab-Rails
We recommend to use [policies](../policies.md) to deal with authorization for a feature. Currently we need to make sure to cover the following checks:
Some basic authorization is included in the Abstraction Layer classes that are base classes for more specialized classes.
What needs to be included in the code:
1. Check for feature flag compatibility: `Gitlab::Llm::Utils::FlagChecker.flag_enabled_for_feature?(ai_action)` - included in the `Llm::BaseService` class.
1. Check if resource is authorized: `Gitlab::Llm::Utils::Authorizer.resource(resource: resource, user: user).allowed?` - also included in the `Llm::BaseService` class.
1. Both of those checks are included in the `::Gitlab::Llm::FeatureAuthorizer.new(container: subject_container, feature_name: action_name).allowed?`
1. Access to AI features depend on several factors, such as: their maturity, if they are enabled on self-managed, if they are bundled within an add-on etc.
- [Example](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/policies/ee/global_policy.rb#L222-222) of policy not connected to the particular resource.
- [Example](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/policies/ee/issue_policy.rb#L25-25) of policy connected to the particular resource.
NOTE:
For more information, see [the GitLab AI gateway documentation](https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/main/doc/howto/gitlab_ai_gateway.md#optional-enable-authentication-and-authorization-in-ai-gateway) about authentication and authorization in AI gateway.
If your Duo feature involves an autonomous agent, you should use
[composite identity](composite_identity.md) authorization.
### Pairing requests with responses
Because multiple users' requests can be processed in parallel, when receiving responses,
it can be difficult to pair a response with its original request. The `requestId`
field can be used for this purpose, because both the request and response are assured
to have the same `requestId` UUID.
### Caching
AI requests and responses can be cached. Cached conversation is being used to
display user interaction with AI features. In the current implementation, this cache
is not used to skip consecutive calls to the AI service when a user repeats
their requests.
```graphql
query {
aiMessages {
nodes {
id
requestId
content
role
errors
timestamp
}
}
}
```
This cache is used for chat functionality. For other services, caching is
disabled. You can enable this for a service by using the `cache_response: true`
option.
Caching has following limitations:
- Messages are stored in Redis stream.
- There is a single stream of messages per user. This means that all services
currently share the same cache. If needed, this could be extended to multiple
streams per user (after checking with the infrastructure team that Redis can handle
the estimated amount of messages).
- Only the last 50 messages (requests + responses) are kept.
- Expiration time of the stream is 3 days since adding last message.
- User can access only their own messages. There is no authorization on the caching
level, and any authorization (if accessed by not current user) is expected on
the service layer.
### Check if feature is allowed for this resource based on namespace settings
There is one setting allowed on root namespace level that restrict the use of AI features:
- `experiment_features_enabled`
To check if that feature is allowed for a given namespace, call:
```ruby
Gitlab::Llm::StageCheck.available?(namespace, :name_of_the_feature)
```
Add the name of the feature to the `Gitlab::Llm::StageCheck` class. There are
arrays there that differentiate between experimental and beta features.
This way we are ready for the following different cases:
- If the feature is not in any array, the check will return `true`. For example, the feature is generally available.
To move the feature from the experimental phase to the beta phase, move the name of the feature from the `EXPERIMENTAL_FEATURES` array to the `BETA_FEATURES` array.
### Implement calls to AI APIs and the prompts
The `CompletionWorker` will call the `Completions::Factory` which will initialize the Service and execute the actual call to the API.
In our example, we will use VertexAI and implement two new classes:
```ruby
# /ee/lib/gitlab/llm/vertex_ai/completions/rewrite_description.rb
module Gitlab
module Llm
module VertexAi
module Completions
class AmazingNewAiFeature < Gitlab::Llm::Completions::Base
def execute
prompt = ai_prompt_class.new(options[:user_input]).to_prompt
response = Gitlab::Llm::VertexAi::Client.new(user, unit_primitive: 'amazing_feature').text(content: prompt)
response_modifier = ::Gitlab::Llm::VertexAi::ResponseModifiers::Predictions.new(response)
::Gitlab::Llm::GraphqlSubscriptionResponseService.new(
user, nil, response_modifier, options: response_options
).execute
end
end
end
end
end
end
```
```ruby
# /ee/lib/gitlab/llm/vertex_ai/templates/rewrite_description.rb
module Gitlab
module Llm
module VertexAi
module Templates
class AmazingNewAiFeature
def initialize(user_input)
@user_input = user_input
end
def to_prompt
<<~PROMPT
You are an assistant that writes code for the following context:
context: #{user_input}
PROMPT
end
end
end
end
end
end
```
Because we support multiple AI providers, you may also use those providers for
the same example:
```ruby
Gitlab::Llm::VertexAi::Client.new(user, unit_primitive: 'your_feature')
Gitlab::Llm::Anthropic::Client.new(user, unit_primitive: 'your_feature')
```
## Monitoring
- Error ratio and response latency apdex for each Ai action can be found on [Sidekiq Service dashboard](https://dashboards.gitlab.net/d/sidekiq-main/sidekiq-overview?orgId=1) under **SLI Detail: `llm_completion`**.
@ -735,270 +407,6 @@ Gitlab::Llm::Anthropic::Client.new(user, unit_primitive: 'your_feature')
- [AI gateway metrics](https://dashboards.gitlab.net/d/ai-gateway-main/ai-gateway3a-overview?orgId=1).
- [Feature usage dashboard via proxy](https://log.gprd.gitlab.net/app/r/s/egybF).
## Logs
### Overview
In addition to standard logging in the GitLab Rails Monolith instance, specialized logging is available for features based on large language models (LLMs).
### Logged events
Currently logged events are documented [here](logged_events.md).
### Implementation
#### Logger Class
To implement LLM-specific logging, use the `Gitlab::Llm::Logger` class.
#### Privacy Considerations
**Important**: User inputs and complete prompts containing user data must not be logged unless explicitly permitted.
### Feature Flag
A feature flag named `expanded_ai_logging` controls the logging of sensitive data.
Use the `conditional_info` helper method for conditional logging based on the feature flag status:
- If the feature flag is enabled for the current user, it logs the information on `info` level (logs are accessible in Kibana).
- If the feature flag is disabled for the current user, it logs the information on `info` level, but without optional parameters (logs are accessible in Kibana, but only obligatory fields).
### Best Practices
When implementing logging for LLM features, consider the following:
- Identify critical information for debugging purposes.
- Ensure compliance with privacy requirements by not logging sensitive user data without proper authorization.
- Use the `conditional_info` helper method to respect the `expanded_ai_logging` feature flag.
- Structure your logs to provide meaningful insights for troubleshooting and analysis.
### Example Usage
```ruby
# including concern that handles logging
include Gitlab::Llm::Concerns::Logger
# Logging potentially sensitive information
log_conditional_info(user, message:"User prompt processed", event_name: 'ai_event', ai_component: 'abstraction_layer', prompt: sanitized_prompt)
# Logging application error information
log_error(user, message: "System application error", event_name: 'ai_event', ai_component: 'abstraction_layer', error_message: sanitized_error_message)
```
**Important**: Please familiarize yourself with our [Data Retention Policy](../../user/gitlab_duo/data_usage.md#data-retention) and remember
to make sure we are not logging user input and LLM-generated output.
## Security
Refer to the [secure coding guidelines for Artificial Intelligence (AI) features](../secure_coding_guidelines.md#artificial-intelligence-ai-features).
## Model Migration Process
### Introduction
LLM models are constantly evolving, and GitLab needs to regularly update our AI features to support newer models. This guide provides a structured approach for migrating AI features to new models while maintaining stability and reliability.
### Purpose
Provide a comprehensive guide for migrating AI models within GitLab.
#### Expected Duration
Model migrations typically follow these general timelines:
- **Simple Model Updates (Same Provider):** 2-3 weeks
- Example: Upgrading from Claude Sonnet 3.5 to 3.6
- Involves model validation, testing, and staged rollout
- Primary focus on maintaining stability and performance
- Can sometimes be expedited when urgent, but 2 weeks is standard
- **Complex Migrations:** 1-2 months (full milestone or longer)
- Example: Adding support for a new provider like AWS Bedrock
- Example: Major version upgrades with breaking changes (e.g., Claude 2 to 3)
- Requires significant API integration work
- May need infrastructure changes
- Extensive testing and validation required
#### Timeline Factors
Several factors can impact migration timelines:
- Current system stability and recent incidents
- Resource availability and competing priorities
- Complexity of behavioral changes in new model
- Scale of testing required
- Feature flag rollout strategy
#### Best Practices
- Always err on the side of caution with initial timeline estimates
- Use feature flags for gradual rollouts to minimize risk
- Plan for buffer time to handle unexpected issues
- Communicate conservative timelines externally while working to deliver faster
- Prioritize system stability over speed of deployment
NOTE:
While some migrations can technically be completed quickly, we typically plan for longer timelines to ensure proper testing and staged rollouts. This approach helps maintain system stability and reliability.
### Scope
Applicable to all AI model-related teams at GitLab. We currently only support using Anthropic and Google Vertex models, with plans to support AWS Bedrock models in the [future](https://gitlab.com/gitlab-org/gitlab/-/issues/498119).
### Prerequisites
Before starting a model migration:
- Create an issue under the [AI Model Version Migration Initiative epic](https://gitlab.com/groups/gitlab-org/-/epics/15650) with the following:
- Label with `group::ai framework`
- Document any known behavioral changes or improvements in the new model
- Include any breaking changes or compatibility issues
- Reference any model provider documentation about the changes
- Verify the new model is supported in our current AI-Gateway API specification by:
- Check model definitions in AI gateway:
- For LiteLLM models: `ai_gateway/models/v2/container.py`
- For Anthropic models: `ai_gateway/models/anthropic.py`
- For new providers: Create a new model definition file in `ai_gateway/models/`
- Verify model configurations:
- Model enum definitions
- Stop tokens
- Timeout settings
- Completion type (text or chat)
- Max token limits
- Testing the model locally in AI gateway:
- Set up the [AI gateway development environment](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist#how-to-run-the-server-locally)
- Configure the necessary API keys in your `.env` file
- Test the model using the Swagger UI at `http://localhost:5052/docs`
- If the model isn't supported, create an issue in the [AI gateway repository](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist) to add support
- Review the provider's API documentation for any breaking changes:
- [Anthropic API Documentation](https://docs.anthropic.com/claude/reference/versions)
- [Google Vertex AI Documentation](https://cloud.google.com/vertex-ai/docs/reference)
- Ensure you have access to testing environments and monitoring tools
- Complete model evaluation using the [Prompt Library](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/prompt-library/-/blob/main/doc/how-to/run_duo_chat_eval.md)
NOTE:
Documentation of model changes is crucial for tracking the impact of migrations and helping with future troubleshooting. Always create an issue to track these changes before beginning the migration process.
### Migration Tasks
#### Migration Tasks for Anthropic Model
- **Optional** - Investigate if the new model is supported within our current AI-Gateway API specification. This step can usually be skipped. However, sometimes to support a newer model, we may need to accommodate a new API format.
- Add the new model to our [available models list](https://gitlab.com/gitlab-org/gitlab/-/blob/32fa9eaa3c8589ee7f448ae683710ec7bd82f36c/ee/lib/gitlab/llm/concerns/available_models.rb#L5-10).
- Change the default model in our [AI-Gateway client](https://gitlab.com/gitlab-org/gitlab/-/blob/41361629b302f2c55e35701d2c0a73cff32f9013/ee/lib/gitlab/llm/chain/requests/ai_gateway.rb#L63-67). Please place the change around a feature flag. We may need to quickly rollback the change.
- Update the model definitions in AI gateway following the [prompt definition guidelines](#2-create-a-prompt-definition-in-the-ai-gateway)
Note: While we're moving toward AI gateway holding the prompts, feature flag implementation still requires a GitLab release.
#### Migration Tasks for Vertex Models
**Work in Progress**
### Feature Flag Process
#### Implementation Steps
For implementing feature flags, refer to our [Feature Flags Development Guidelines](../feature_flags/index.md).
NOTE:
Feature flag implementations will affect self-hosted cloud-connected customers. These customers won't receive the model upgrade until the feature flag is removed from the AI gateway codebase, as they won't have access to the new GitLab release.
#### Model Selection Implementation
The model selection logic should be implemented in:
- AI gateway client (`ee/lib/gitlab/llm/chain/requests/ai_gateway.rb`)
- Model definitions in AI gateway
- Any custom implementations in specific features that override the default model
#### Rollout Strategy
- Enable the feature flag for a small percentage of users/groups initially
- Monitor performance metrics and error rates using:
- [Sidekiq Service dashboard](https://dashboards.gitlab.net/d/sidekiq-main/sidekiq-overview) for error ratios and response latency
- [AI gateway metrics dashboard](https://dashboards.gitlab.net/d/ai-gateway-main/ai-gateway3a-overview?orgId=1) for gateway-specific metrics
- [AI gateway logs](https://log.gprd.gitlab.net/app/r/s/zKEel) for detailed error investigation
- [Feature usage dashboard](https://log.gprd.gitlab.net/app/r/s/egybF) for adoption metrics
- [Periscope dashboard](https://app.periscopedata.com/app/gitlab/1137231/Ai-Features) for token usage and feature statistics
- Gradually increase the rollout percentage
- If issues arise, quickly disable the feature flag to rollback to the previous model
- Once stability is confirmed, remove the feature flag and make the migration permanent
For more details on monitoring during migrations, see the [Monitoring and Metrics](#monitoring-and-metrics) section below.
### Scope of Work
#### AI Features to Migrate
- **Duo Chat Tools:**
- `ci_editor_assistant/prompts/anthropic.rb` - CI Editor
- `gitlab_documentation/executor.rb` - GitLab Documentation
- `epic_reader/prompts/anthropic.rb` - Epic Reader
- `issue_reader/prompts/anthropic.rb` - Issue Reader
- `merge_request_reader/prompts/anthropic.rb` - Merge Request Reader
- **Chat Slash Commands:**
- `refactor_code/prompts/anthropic.rb` - Refactor
- `write_tests/prompts/anthropic.rb` - Write Tests
- `explain_code/prompts/anthropic.rb` - Explain Code
- `explain_vulnerability/executor.rb` - Explain Vulnerability
- **Experimental Tools:**
- Summarize Comments Chat
- Fill MR Description
### Testing and Validation
#### Model Evaluation
The `ai-model-validation` team created the following library to evaluate the performance of prompt changes as well as model changes. The [Prompt Library README.MD](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/prompt-library/-/blob/main/doc/how-to/run_duo_chat_eval.md) provides details on how to evaluate the performance of AI features.
> Another use-case for running chat evaluation is during feature development cycle. The purpose is to verify how the changes to the code base and prompts affect the quality of chat responses before the code reaches the production environment.
For evaluation in merge request pipelines, we use:
- One click [Duo Chat evaluation](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/evaluation-runner)
- Automated evaluation in [merge request pipelines](https://gitlab.com/gitlab-org/gitlab/-/issues/495410)
#### Seed project and group resources for testing and evaluation
To seed project and group resources for testing and evaluation, run the following command:
```shell
SEED_GITLAB_DUO=1 FILTER=gitlab_duo bundle exec rake db:seed_fu
```
This command executes the [development seed file](../development_seed_files.md) for GitLab Duo, which creates `gitlab-duo` group in your GDK.
This command is responsible for seeding group and project resources for testing GitLab Duo features.
It's mainly used by the following scenarios:
- Developers or UX designers have a local GDK but don't know how to set up the group and project resources to test a feature in UI.
- Evaluators (e.g. CEF) have input dataset that refers to a group or project resource e.g. (`Summarize issue #123` requires a corresponding issue record in PosstgreSQL)
Currently, the input dataset of evaluators and this development seed file are managed separately.
To ensure that the integration keeps working, this seeder has to create the **same** group/project resources every time.
For example, ID and IID of the inserted PostgreSQL records must be the same every time we run this seeding process.
These fixtures are depended by the following projects:
- [Central Evaluation Framework](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/prompt-library)
- [Evaluation Runner](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/evaluation-runner)
See [this architecture doc](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/evaluation-runner/-/blob/main/docs/architecture.md) for more information.
#### Local Development
A valuable tool for local development to ensure the changes are correct outside of unit tests is to use [LangSmith](duo_chat.md#tracing-with-langsmith) for tracing. The tool allows you to trace LLM calls within Duo Chat to verify the LLM tool is using the correct model.
To prevent regressions, we also have CI jobs to make sure our tools are working correctly. For more details, see the [Duo Chat testing section](duo_chat.md#prevent-regressions-in-your-merge-request).
### Monitoring and Metrics
Monitor the following during migration:
- **Performance Metrics:**
- Error ratio and response latency apdex for each AI action on [Sidekiq Service dashboard](https://dashboards.gitlab.net/d/sidekiq-main/sidekiq-overview)
- Spent tokens, usage of each AI feature and other statistics on [periscope dashboard](https://app.periscopedata.com/app/gitlab/1137231/Ai-Features)
- [AI gateway logs](https://log.gprd.gitlab.net/app/r/s/zKEel)
- [AI gateway metrics](https://dashboards.gitlab.net/d/ai-gateway-main/ai-gateway3a-overview?orgId=1)
- [Feature usage dashboard via proxy](https://log.gprd.gitlab.net/app/r/s/egybF)

View File

@ -0,0 +1,56 @@
---
stage: AI-powered
group: AI Framework
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
---
# LLM logging
In addition to standard logging in the GitLab Rails Monolith instance, specialized logging is available for features based on large language models (LLMs).
## Logged events
Currently logged events are documented [here](logged_events.md).
## Implementation
### Logger Class
To implement LLM-specific logging, use the `Gitlab::Llm::Logger` class.
### Privacy Considerations
**Important**: User inputs and complete prompts containing user data must not be logged unless explicitly permitted.
## Feature Flag
A feature flag named `expanded_ai_logging` controls the logging of sensitive data.
Use the `conditional_info` helper method for conditional logging based on the feature flag status:
- If the feature flag is enabled for the current user, it logs the information on `info` level (logs are accessible in Kibana).
- If the feature flag is disabled for the current user, it logs the information on `info` level, but without optional parameters (logs are accessible in Kibana, but only obligatory fields).
## Best Practices
When implementing logging for LLM features, consider the following:
- Identify critical information for debugging purposes.
- Ensure compliance with privacy requirements by not logging sensitive user data without proper authorization.
- Use the `conditional_info` helper method to respect the `expanded_ai_logging` feature flag.
- Structure your logs to provide meaningful insights for troubleshooting and analysis.
## Example Usage
```ruby
# including concern that handles logging
include Gitlab::Llm::Concerns::Logger
# Logging potentially sensitive information
log_conditional_info(user, message:"User prompt processed", event_name: 'ai_event', ai_component: 'abstraction_layer', prompt: sanitized_prompt)
# Logging application error information
log_error(user, message: "System application error", event_name: 'ai_event', ai_component: 'abstraction_layer', error_message: sanitized_error_message)
```
**Important**: Please familiarize yourself with our [Data Retention Policy](../../user/gitlab_duo/data_usage.md#data-retention) and remember
to make sure we are not logging user input and LLM-generated output.

View File

@ -0,0 +1,216 @@
---
stage: AI-powered
group: AI Framework
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
---
# Model Migration Process
## Introduction
LLM models are constantly evolving, and GitLab needs to regularly update our AI features to support newer models. This guide provides a structured approach for migrating AI features to new models while maintaining stability and reliability.
## Purpose
Provide a comprehensive guide for migrating AI models within GitLab.
### Expected Duration
Model migrations typically follow these general timelines:
- **Simple Model Updates (Same Provider):** 2-3 weeks
- Example: Upgrading from Claude Sonnet 3.5 to 3.6
- Involves model validation, testing, and staged rollout
- Primary focus on maintaining stability and performance
- Can sometimes be expedited when urgent, but 2 weeks is standard
- **Complex Migrations:** 1-2 months (full milestone or longer)
- Example: Adding support for a new provider like AWS Bedrock
- Example: Major version upgrades with breaking changes (e.g., Claude 2 to 3)
- Requires significant API integration work
- May need infrastructure changes
- Extensive testing and validation required
### Timeline Factors
Several factors can impact migration timelines:
- Current system stability and recent incidents
- Resource availability and competing priorities
- Complexity of behavioral changes in new model
- Scale of testing required
- Feature flag rollout strategy
### Best Practices
- Always err on the side of caution with initial timeline estimates
- Use feature flags for gradual rollouts to minimize risk
- Plan for buffer time to handle unexpected issues
- Communicate conservative timelines externally while working to deliver faster
- Prioritize system stability over speed of deployment
NOTE:
While some migrations can technically be completed quickly, we typically plan for longer timelines to ensure proper testing and staged rollouts. This approach helps maintain system stability and reliability.
## Scope
Applicable to all AI model-related teams at GitLab. We currently support using Anthropic and Google Vertex models. Support for AWS Bedrock models is proposed in [issue 498119](https://gitlab.com/gitlab-org/gitlab/-/issues/498119).
## Prerequisites
Before starting a model migration:
- Create an issue under the [AI Model Version Migration Initiative epic](https://gitlab.com/groups/gitlab-org/-/epics/15650) with the following:
- Label with `group::ai framework`
- Document any known behavioral changes or improvements in the new model
- Include any breaking changes or compatibility issues
- Reference any model provider documentation about the changes
- Verify the new model is supported in our current AI-Gateway API specification by:
- Check model definitions in AI gateway:
- For LiteLLM models: `ai_gateway/models/v2/container.py`
- For Anthropic models: `ai_gateway/models/anthropic.py`
- For new providers: Create a new model definition file in `ai_gateway/models/`
- Verify model configurations:
- Model enum definitions
- Stop tokens
- Timeout settings
- Completion type (text or chat)
- Max token limits
- Testing the model locally in AI gateway:
- Set up the [AI gateway development environment](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist#how-to-run-the-server-locally)
- Configure the necessary API keys in your `.env` file
- Test the model using the Swagger UI at `http://localhost:5052/docs`
- If the model isn't supported, create an issue in the [AI gateway repository](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist) to add support
- Review the provider's API documentation for any breaking changes:
- [Anthropic API Documentation](https://docs.anthropic.com/claude/reference/versions)
- [Google Vertex AI Documentation](https://cloud.google.com/vertex-ai/docs/reference)
- Ensure you have access to testing environments and monitoring tools
- Complete model evaluation using the [Prompt Library](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/prompt-library/-/blob/main/doc/how-to/run_duo_chat_eval.md)
NOTE:
Documentation of model changes is crucial for tracking the impact of migrations and helping with future troubleshooting. Always create an issue to track these changes before beginning the migration process.
## Migration Tasks
### Migration Tasks for Anthropic Model
- **Optional** - Investigate if the new model is supported within our current AI-Gateway API specification. This step can usually be skipped. However, sometimes to support a newer model, we may need to accommodate a new API format.
- Add the new model to our [available models list](https://gitlab.com/gitlab-org/gitlab/-/blob/32fa9eaa3c8589ee7f448ae683710ec7bd82f36c/ee/lib/gitlab/llm/concerns/available_models.rb#L5-10).
- Change the default model in our [AI-Gateway client](https://gitlab.com/gitlab-org/gitlab/-/blob/41361629b302f2c55e35701d2c0a73cff32f9013/ee/lib/gitlab/llm/chain/requests/ai_gateway.rb#L63-67). Please place the change around a feature flag. We may need to quickly rollback the change.
- Update the model definitions in AI gateway following the [prompt definition guidelines](actions.md#2-create-a-prompt-definition-in-the-ai-gateway)
Note: While we're moving toward AI gateway holding the prompts, feature flag implementation still requires a GitLab release.
### Migration Tasks for Vertex Models
**Work in Progress**
## Feature Flag Process
### Implementation Steps
For implementing feature flags, refer to our [Feature Flags Development Guidelines](../feature_flags/index.md).
NOTE:
Feature flag implementations will affect self-hosted cloud-connected customers. These customers won't receive the model upgrade until the feature flag is removed from the AI gateway codebase, as they won't have access to the new GitLab release.
### Model Selection Implementation
The model selection logic should be implemented in:
- AI gateway client (`ee/lib/gitlab/llm/chain/requests/ai_gateway.rb`)
- Model definitions in AI gateway
- Any custom implementations in specific features that override the default model
### Rollout Strategy
- Enable the feature flag for a small percentage of users/groups initially
- Monitor performance metrics and error rates using:
- [Sidekiq Service dashboard](https://dashboards.gitlab.net/d/sidekiq-main/sidekiq-overview) for error ratios and response latency
- [AI gateway metrics dashboard](https://dashboards.gitlab.net/d/ai-gateway-main/ai-gateway3a-overview?orgId=1) for gateway-specific metrics
- [AI gateway logs](https://log.gprd.gitlab.net/app/r/s/zKEel) for detailed error investigation
- [Feature usage dashboard](https://log.gprd.gitlab.net/app/r/s/egybF) for adoption metrics
- [Periscope dashboard](https://app.periscopedata.com/app/gitlab/1137231/Ai-Features) for token usage and feature statistics
- Gradually increase the rollout percentage
- If issues arise, quickly disable the feature flag to rollback to the previous model
- Once stability is confirmed, remove the feature flag and make the migration permanent
For more details on monitoring during migrations, see the [Monitoring and Metrics](#monitoring-and-metrics) section below.
## Scope of Work
### AI Features to Migrate
- **Duo Chat Tools:**
- `ci_editor_assistant/prompts/anthropic.rb` - CI Editor
- `gitlab_documentation/executor.rb` - GitLab Documentation
- `epic_reader/prompts/anthropic.rb` - Epic Reader
- `issue_reader/prompts/anthropic.rb` - Issue Reader
- `merge_request_reader/prompts/anthropic.rb` - Merge Request Reader
- **Chat Slash Commands:**
- `refactor_code/prompts/anthropic.rb` - Refactor
- `write_tests/prompts/anthropic.rb` - Write Tests
- `explain_code/prompts/anthropic.rb` - Explain Code
- `explain_vulnerability/executor.rb` - Explain Vulnerability
- **Experimental Tools:**
- Summarize Comments Chat
- Fill MR Description
## Testing and Validation
### Model Evaluation
The `ai-model-validation` team created the following library to evaluate the performance of prompt changes as well as model changes. The [Prompt Library README.MD](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/prompt-library/-/blob/main/doc/how-to/run_duo_chat_eval.md) provides details on how to evaluate the performance of AI features.
> Another use-case for running chat evaluation is during feature development cycle. The purpose is to verify how the changes to the code base and prompts affect the quality of chat responses before the code reaches the production environment.
For evaluation in merge request pipelines, we use:
- One click [Duo Chat evaluation](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/evaluation-runner)
- Automated evaluation in [merge request pipelines](https://gitlab.com/gitlab-org/gitlab/-/issues/495410)
### Seed project and group resources for testing and evaluation
To seed project and group resources for testing and evaluation, run the following command:
```shell
SEED_GITLAB_DUO=1 FILTER=gitlab_duo bundle exec rake db:seed_fu
```
This command executes the [development seed file](../development_seed_files.md) for GitLab Duo, which creates `gitlab-duo` group in your GDK.
This command is responsible for seeding group and project resources for testing GitLab Duo features.
It's mainly used by the following scenarios:
- Developers or UX designers have a local GDK but don't know how to set up the group and project resources to test a feature in UI.
- Evaluators (e.g. CEF) have input dataset that refers to a group or project resource e.g. (`Summarize issue #123` requires a corresponding issue record in PosstgreSQL)
Currently, the input dataset of evaluators and this development seed file are managed separately.
To ensure that the integration keeps working, this seeder has to create the **same** group/project resources every time.
For example, ID and IID of the inserted PostgreSQL records must be the same every time we run this seeding process.
These fixtures are depended by the following projects:
- [Central Evaluation Framework](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/prompt-library)
- [Evaluation Runner](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/evaluation-runner)
See [this architecture doc](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/evaluation-runner/-/blob/main/docs/architecture.md) for more information.
### Local Development
A valuable tool for local development to ensure the changes are correct outside of unit tests is to use [LangSmith](duo_chat.md#tracing-with-langsmith) for tracing. The tool allows you to trace LLM calls within Duo Chat to verify the LLM tool is using the correct model.
To prevent regressions, we also have CI jobs to make sure our tools are working correctly. For more details, see the [Duo Chat testing section](duo_chat.md#prevent-regressions-in-your-merge-request).
## Monitoring and Metrics
Monitor the following during migration:
- **Performance Metrics:**
- Error ratio and response latency apdex for each AI action on [Sidekiq Service dashboard](https://dashboards.gitlab.net/d/sidekiq-main/sidekiq-overview)
- Spent tokens, usage of each AI feature and other statistics on [periscope dashboard](https://app.periscopedata.com/app/gitlab/1137231/Ai-Features)
- [AI gateway logs](https://log.gprd.gitlab.net/app/r/s/zKEel)
- [AI gateway metrics](https://dashboards.gitlab.net/d/ai-gateway-main/ai-gateway3a-overview?orgId=1)
- [Feature usage dashboard via proxy](https://log.gprd.gitlab.net/app/r/s/egybF)

View File

@ -88,5 +88,5 @@ This a required multi-milestone process that involves:
1. Ignoring the column.
1. Dropping the column.
1. Removing the ignore rule.
Dropping the original column before ignoring it in the model can cause problems with zero-downtime migrations.

View File

@ -22,7 +22,7 @@ If a deprecation or breaking change is unavoidable, then take the following step
1. Review the [deprecation guidelines in our documentation](#minimize-the-impact-of-breaking-changes)
1. Review the [breaking changes best practices](https://docs.google.com/document/d/1ByVZEhGJfjb6XTwiDeaSDRVwUiF6dsEQI01TW4BJA0k/edit?tab=t.0#heading=h.vxhro51h5zxn) (internal link)
1. Review the [release post process for announcing breaking changes](https://handbook.gitlab.com/handbook/marketing/blog/release-posts/#deprecations-removals-and-breaking-changes)
1. **(Required)** Create a [deprecation issue ticket](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Deprecations.md) and begin following the steps documented there
1. **(Required)** Create a [deprecation issue ticket](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Deprecations.md) and begin following the steps documented there
The [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Deprecations.md) includes checklist that ensure each breaking change has sufficiently planned for:

View File

@ -264,7 +264,7 @@ You can review the new release process [here](https://gitlab.com/gitlab-org/tech
The [Docs project maintenance tasks rotation](https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments) will pause when we launch on Hugo.
For February 2025, run the checks for broken external links and `start_remove` content before Wednesday, February 12. Other tasks are fine to skip for now. From March onwards, the monthly maintenance task will be on hold until further notice.
For February 2025, run the checks for broken external links and `start_remove` content before Wednesday, February 12. Other tasks are fine to skip for now. From March onwards, the monthly maintenance task will be on hold until further notice.
NOTE:
This does not impact the release post [structural check](https://handbook.gitlab.com/handbook/marketing/blog/release-posts/#structural-check) or [monthly documentation release](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/doc/releases.md) tasks. The assigned Technical Writer should continue to do these tasks as previously scheduled.

View File

@ -439,6 +439,10 @@ Use **check out** as a verb. For the Git command, use `checkout`.
- Use `git checkout` to check out a branch locally.
- Check out the files you want to edit.
## cherry-pick, cherry pick
Use the hyphenated version of **cherry-pick**. Do not use **cherry pick**.
## CI, CD
When talking about GitLab features, use **CI/CD**. Do not use **CI** or **CD** alone.

View File

@ -40,7 +40,7 @@ deploy:
### Runway deployment for .com
Services for GitLab.com, GitLab Dedicated and self-hosted customers using CloudConnect are deployed using [Runway](https://docs.runway.gitlab.com/welcome/onboarding/).
Services for GitLab.com, GitLab Dedicated and self-hosted customers using CloudConnect are deployed using [Runway](https://docs.runway.gitlab.com/welcome/onboarding/).
Please refer to the project documentation on how to add or manage Runway services.
### Deploying in self-hosted environments

View File

@ -16,12 +16,12 @@ source: /doc/user/search/exact_code_search.md
| `"class foo"` | `"class foo"` | `class foo` |
| `class foo` | `class foo` | `class` and `foo` |
| `foo or bar` | `foo or bar` | `foo` or `bar` |
| `class Foo` | `class Foo` (case insensitive) | `class` (case insensitive) and `Foo` (case sensitive) |
| `class Foo` | `class Foo` (case sensitive) | `class` (case insensitive) and `Foo` (case sensitive) |
| `class Foo case:yes` | `class Foo` (case sensitive) | `class` and `Foo` (both case sensitive) |
| `foo -bar` | `foo -bar` | `foo` but not `bar` |
| `foo file:js` | `foo` in files with names that contain `js` | `foo` in files with names that contain `js` |
| `foo -file:test` | `foo` in files with names that do not contain `test` | `foo` in files with names that do not contain `test` |
| `foo lang:ruby` | `foo` in Ruby source code | `foo` in Ruby source code |
| `foo file:\.js$` | `foo` in files with names that end with `.js` | `foo` in files with names that end with `.js` |
| `foo.*bar` | None | `foo.*bar` (regular expression) |
| `foo.*bar` | `foo.*bar` (literal) | `foo.*bar` (regular expression) |
| `sym:foo` | `foo` in symbols like class, method, and variable names | `foo` in symbols like class, method, and variable names |

View File

@ -11,7 +11,7 @@ DETAILS:
**Offering:** GitLab.com, GitLab Self-Managed, GitLab Dedicated
NOTE:
This feature is not under active development, but [community contributions](https://about.gitlab.com/community/contribute/) are welcome.
This feature is not under active development, but [community contributions](https://about.gitlab.com/community/contribute/) are welcome.
For more information, see [issue 468607](https://gitlab.com/gitlab-org/gitlab/-/issues/468607#note_1967939452).
To determine if the feature meets your needs, see the [open bug issues](https://gitlab.com/gitlab-org/gitlab/-/issues/?sort=updated_desc&state=opened&label_name%5B%5D=Category%3AIncident%20Management&label_name%5B%5D=type%3A%3Abug&first_page_size=20).

View File

@ -74,7 +74,7 @@ To replace the token:
[project access tokens API](../../api/project_access_tokens.md#list-all-project-access-tokens)
to list recently expired tokens.
- For group access tokens, use the
[group access tokens API](../../api/group_access_tokens.md#list-group-access-tokens)
[group access tokens API](../../api/group_access_tokens.md#list-all-group-access-tokens)
to list recently expired tokens.
1. Create a new access token:
- For personal access tokens, [use the UI](../../user/profile/personal_access_tokens.md#create-a-personal-access-token)

View File

@ -55,7 +55,7 @@ To resolve this issue, try the following:
- [FineShift Software Private Limited](https://partners.gitlab.com/English/directory/partner/1737250/fineshift-software-private-limited)
- For cards issued outside of the United States: Ensure your card is enabled for international use, and verify if there are country-specific restrictions.
- Contact your financial institution: Ask for the reason why your transaction was declined, and request that your card is enabled for this type of transaction.
- Contact your financial institution: Ask for the reason why your transaction was declined, and request that your card is enabled for this type of transaction.
## Error: `Attempt_Exceed_Limitation`

View File

@ -89,9 +89,9 @@ For more information about upgrading GitLab Helm Chart, see [the release notes f
Now you should be able to complete the migrations in GitLab 17.1 and finish
the upgrade.
- A [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/476542) in the Git versions shipped with
GitLab 17.0.x and GitLab 17.1.x causes a noticeable increase in CPU usage when under load. The primary cause of
this regression was resolved in the Git versions shipped with GitLab 17.2 so, for systems that see heavy peak loads,
- A [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/476542) in the Git versions shipped with
GitLab 17.0.x and GitLab 17.1.x causes a noticeable increase in CPU usage when under load. The primary cause of
this regression was resolved in the Git versions shipped with GitLab 17.2 so, for systems that see heavy peak loads,
you should upgrade to GitLab 17.2.
### Linux package installations

View File

@ -23,7 +23,7 @@ Use AI impact analytics for:
- Snapshot of GitLab Duo usage: Track the use of seats and features in a project or group over the last 30 days.
To learn how you can optimize your license utilization,
see [GitLab Duo add-ons](../../subscriptions/subscription-add-ons.md).
see [GitLab Duo add-ons](../../subscriptions/subscription-add-ons.md).
For a click-through demo, see the [AI impact analytics product tour](https://gitlab.navattic.com/ai-impact).

View File

@ -115,6 +115,22 @@ For more information, see the history.
| `rules` | `array` of rules | true | List of rules that the policy applies. |
| `actions` | `array` of actions | true | List of actions that the policy enforces. Limited to a maximum of 10 in GitLab 18.0 and later. |
| `policy_scope` | `object` of [`policy_scope`](index.md#scope) | false | Defines the scope of the policy based on the projects, groups, or compliance framework labels you specify. |
| `skip_ci` | `object` of [`skip_ci`](#skip_ci-type) | false | Defines whether users can apply the `skip-ci` directive. |
### `skip_ci` type
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/482952) in GitLab 17.9.
Scan execution policies offer control over who can use the `[skip ci]` directive. You can specify certain users or service accounts that are allowed to use `[skip ci]` while still ensuring critical security and compliance checks are performed.
Use the `skip_ci` keyword to specify whether users are allowed to apply the `skip_ci` directive to skip the pipelines.
When the keyword is not specified, the `skip_ci` directive is ignored, preventing all users
from bypassing the pipeline execution policies.
| Field | Type | Possible values | Description |
|-------------------------|----------|--------------------------|-------------|
| `allowed` | `boolean` | `true`, `false` | Flag to allow (`true`) or prevent (`false`) the use of the `skip-ci` directive for pipelines with enforced pipeline execution policies. |
| `allowlist` | `object` | `users` | Specify users who are always allowed to use `skip-ci` directive, regardless of the `allowed` flag. Use `users:` followed by an array of objects with `id` keys representing user IDs. |
## `pipeline` rule type

View File

@ -100,7 +100,7 @@ You can filter by:
### Filter vulnerabilities
> - Improved filtering [introduced](https://gitlab.com/groups/gitlab-org/-/epics/13339) in GitLab 16.9 [with a flag](../../../administration/feature_flags.md) named `vulnerability_report_advanced_filtering`. Disabled by default.
> - [Enabled on GitLab.com, GitLab Self-Managed, and GitLab Dedicated](https://gitlab.com/gitlab-org/gitlab/-/issues/437128) in GitLab 17.1.
> - [Enabled on GitLab.com, GitLab Self-Managed, and GitLab Dedicated](https://gitlab.com/gitlab-org/gitlab/-/issues/437128) in GitLab 17.1.
> - [Generally available in 17.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/157172). Feature flag `vulnerability_report_advanced_filtering` removed.
Filter the vulnerability report to focus on a subset of vulnerabilities.

View File

@ -245,7 +245,7 @@ For more information, see the history.
GitLab.com customers must contact their Customer Success Manager to enable this feature.
Add repository files to your Duo Chat conversations in VS Code or JetBrains IDEs by
typing `/include` and choosing the files.
typing `/include` and choosing the files.
Prerequisites:

View File

@ -261,7 +261,7 @@ To turn on restricted access:
1. On the left sidebar, select **Settings > General**.
1. Expand **Permissions and group features**.
1. Under **Seat controls**, select **Restricted access**.
1. Under **Seat control**, select **Restricted access**.
### Known issues
@ -308,7 +308,7 @@ To specify a user cap:
You can set a cap on the top-level group only.
1. Select **Settings > General**.
1. Expand **Permissions and group features**.
1. From **Seat controls**, select the **Set user cap** checkbox and enter the number of users in the field.
1. From **Seat control**, select the **Set user cap** checkbox and enter the number of users in the field.
1. Select **Save changes**.
If you already have more users in the group than the user cap value, users
@ -329,7 +329,7 @@ To remove the user cap:
1. On the left sidebar, select **Search or go to** and find your group.
1. Select **Settings > General**.
1. Expand **Permissions and group features**.
1. From **Seat controls**, select **Open access**.
1. From **Seat control**, select **Open access**.
1. Select **Save changes**.
Decreasing the user cap does not approve pending members.

View File

@ -132,7 +132,7 @@ access tokens on the access tokens page.
The inactive group access tokens table displays revoked and expired tokens for 30 days after they became inactive.
Tokens that belong to [an active token family](../../../api/group_access_tokens.md#automatic-reuse-detection) are displayed for 30 days after the latest active token from the family is expired or revoked.
Tokens that belong to [an active token family](../../../api/personal_access_tokens.md#automatic-reuse-detection) are displayed for 30 days after the latest active token from the family is expired or revoked.
### Use the UI

View File

@ -36,7 +36,7 @@ or consider using a separate GitLab instance with no shared points of failure.
For GitLab Self-Managed, before you can use GitLab for your OpenTofu state files:
- An administrator must [set up Terraform/OpenTofu state storage](../../../administration/terraform_state.md).
- You must turn on the **Infrastructure** menu for your project:
- You must turn on the **Infrastructure** menu for your project:
1. Go to **Settings > General**.
1. Expand **Visibility, project features, permissions**.
1. Under **Infrastructure**, turn on the toggle.

View File

@ -122,12 +122,12 @@ To choose your home organization:
> - [Homepage options changed](https://gitlab.com/groups/gitlab-org/-/epics/13066) in GitLab 17.9 [with a flag](../../administration/feature_flags.md) named `your_work_projects_vue`. Disabled by default.
FLAG:
FLAG:
When the `your_work_projects_vue` feature flag is enabled, the **Your Contributed Projects** view becomes the default option, and an additional **Member Projects** option is available in the dropdown list. For more information, see the history.
Control what page you view when you select the GitLab logo (**{tanuki}**). You can set your homepage to be Your Projects (default), Your Groups, Your Activity, and other content.
To choose your homepage view:
To choose your homepage view:
1. On the left sidebar, select your avatar.
1. Select **Preferences**.

View File

@ -93,14 +93,14 @@ This table shows some example queries for exact match and regular expression mod
| `"class foo"` | `"class foo"` | `class foo` |
| `class foo` | `class foo` | `class` and `foo` |
| `foo or bar` | `foo or bar` | `foo` or `bar` |
| `class Foo` | `class Foo` (case insensitive) | `class` (case insensitive) and `Foo` (case sensitive) |
| `class Foo` | `class Foo` (case sensitive) | `class` (case insensitive) and `Foo` (case sensitive) |
| `class Foo case:yes` | `class Foo` (case sensitive) | `class` and `Foo` (both case sensitive) |
| `foo -bar` | `foo -bar` | `foo` but not `bar` |
| `foo file:js` | `foo` in files with names that contain `js` | `foo` in files with names that contain `js` |
| `foo -file:test` | `foo` in files with names that do not contain `test` | `foo` in files with names that do not contain `test` |
| `foo lang:ruby` | `foo` in Ruby source code | `foo` in Ruby source code |
| `foo file:\.js$` | `foo` in files with names that end with `.js` | `foo` in files with names that end with `.js` |
| `foo.*bar` | None | `foo.*bar` (regular expression) |
| `foo.*bar` | `foo.*bar` (literal) | `foo.*bar` (regular expression) |
| `sym:foo` | `foo` in symbols like class, method, and variable names | `foo` in symbols like class, method, and variable names |
## Known issues

View File

@ -0,0 +1,93 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
class BackfillMissingNamespaceIdOnNotes < BatchedMigrationJob
operation_name :backfill_missing_namespace_id_on_notes
feature_category :code_review_workflow
def perform
each_sub_batch do |sub_batch|
Gitlab::Database.allow_cross_joins_across_databases(
url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/163687'
) do
connection.execute(build_query(sub_batch))
end
end
end
private
# rubocop:disable Layout/LineLength -- SQL!
# rubocop:disable Metrics/MethodLength -- I do what I want
def build_query(scope)
records_query = scope.where(namespace_id: nil).select("
id,
(
coalesce(
(case
when exists (select 1 from projects where id = notes.project_id) then (select namespace_id from projects where id = notes.project_id)
when noteable_type = 'AlertManagement::Alert' then (select namespace_id from projects where id = (select project_id from alert_management_alerts where noteable_id = notes.id limit 1) limit 1)
when noteable_type = 'MergeRequest' then (select namespace_id from projects where id = (select project_id from merge_requests where noteable_id = notes.id limit 1) limit 1)
when noteable_type = 'Vulnerability' then (select namespace_id from projects where id = (select project_id from vulnerabilities where noteable_id = notes.id limit 1) limit 1)
-- These 2 need to pull namespace_id from the noteable
when noteable_type = 'DesignManagement::Design' then (select namespace_id from design_management_designs where id = notes.noteable_id limit 1)
when noteable_type = 'Issue' then (select namespace_id from issues where id = notes.noteable_id limit 1)
-- Epics pull in group_id
when noteable_type = 'Epic' then (select group_id from epics where id = notes.noteable_id limit 1)
-- Snippets pull from author
when noteable_type = 'Snippet' then (select id from namespaces where owner_id = (select author_id from notes where id = notes.id limit 1) limit 1)
-- Commits pull namespace_id from the project of the note
when noteable_type = 'Commit' then (select namespace_id from projects where id = notes.project_id limit 1)
else
-1
end
), -1)) as namespace_id_to_set
")
<<~SQL
with records AS (
#{records_query.to_sql}
), updated_rows as (
-- updating records with the located namespace_id_to_set value
update notes set namespace_id = namespace_id_to_set from records where records.id=notes.id and namespace_id_to_set <> -1
), deleted_rows as (
-- deleting the records where we couldn't find the namespace id
delete from notes where id IN (select id from records where namespace_id_to_set = -1)
)
select 1
SQL
end
# rubocop:enable Layout/LineLength
# rubocop:enable Metrics/MethodLength
def backfillable?(note)
note.noteable_type.present?
end
def extract_namespace_id(note)
# Attempt to find namespace_id from the project first.
#
if note.project_id
project = Project.find_by_id(note.project_id)
return project.namespace_id if project
end
# We have to load the noteable here because we don't have access to the
# usual ActiveRecord relationships to do it for us.
#
noteable = note.noteable_type.constantize.find(note.noteable_id)
case note.noteable_type
when "AlertManagement::Alert", "Commit", "MergeRequest", "Vulnerability"
noteable.project.namespace_id
when "DesignManagement::Design", "Epic", "Issue"
noteable.namespace_id
when "Snippet"
noteable.author.namespace_id
end
end
end
end
end

View File

@ -1,62 +0,0 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Backfills the `routes.namespace_id` column, by setting it to project.project_namespace_id
class BackfillNamespaceIdForProjectRoute
include Gitlab::Database::DynamicModelHelpers
def perform(start_id, end_id, batch_table, batch_column, sub_batch_size, pause_ms)
parent_batch_relation = relation_scoped_to_range(batch_table, batch_column, start_id, end_id)
parent_batch_relation.each_batch(column: batch_column, of: sub_batch_size) do |sub_batch|
cleanup_gin_index('routes')
batch_metrics.time_operation(:update_all) do
ApplicationRecord.connection.execute <<~SQL
WITH route_and_ns(route_id, project_namespace_id) AS MATERIALIZED (
#{sub_batch.to_sql}
)
UPDATE routes
SET namespace_id = route_and_ns.project_namespace_id
FROM route_and_ns
WHERE id = route_and_ns.route_id
SQL
end
pause_ms = [0, pause_ms].max
sleep(pause_ms * 0.001)
end
end
def batch_metrics
@batch_metrics ||= Gitlab::Database::BackgroundMigration::BatchMetrics.new
end
private
def cleanup_gin_index(table_name)
sql = <<-SQL
SELECT indexname::text FROM pg_indexes WHERE tablename = '#{table_name}' AND indexdef ILIKE '%using gin%'
SQL
index_names = ApplicationRecord.connection.select_values(sql)
index_names.each do |index_name|
ApplicationRecord.connection.execute("SELECT gin_clean_pending_list('#{index_name}')")
end
end
def relation_scoped_to_range(source_table, source_key_column, start_id, stop_id)
define_batchable_model(source_table, connection: ApplicationRecord.connection)
.joins('INNER JOIN projects ON routes.source_id = projects.id')
.where(source_key_column => start_id..stop_id)
.where(namespace_id: nil)
.where(source_type: 'Project')
.where.not(projects: { project_namespace_id: nil })
.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/421843')
.select("routes.id, projects.project_namespace_id")
end
end
end
end

View File

@ -369,6 +369,16 @@ msgid_plural "%d layers"
msgstr[0] ""
msgstr[1] ""
msgid "%d line added"
msgid_plural "%d lines added"
msgstr[0] ""
msgstr[1] ""
msgid "%d line removed"
msgid_plural "%d lines removed"
msgstr[0] ""
msgstr[1] ""
msgid "%d matching branch"
msgid_plural "%d matching branches"
msgstr[0] ""
@ -7355,7 +7365,7 @@ msgstr ""
msgid "ApplicationSettings|Save changes"
msgstr ""
msgid "ApplicationSettings|Seat controls"
msgid "ApplicationSettings|Seat control"
msgstr ""
msgid "ApplicationSettings|See %{linkStart}password policy guidelines%{linkEnd}."
@ -11053,6 +11063,9 @@ msgstr ""
msgid "CICD|Add an existing project to the scope"
msgstr ""
msgid "CICD|Added from log."
msgstr ""
msgid "CICD|Additional permissions"
msgstr ""
@ -32469,6 +32482,9 @@ msgstr ""
msgid "Job|Download"
msgstr ""
msgid "Job|Download SAST report"
msgstr ""
msgid "Job|Duration"
msgstr ""
@ -32589,6 +32605,9 @@ msgstr ""
msgid "Job|These artifacts are the latest. They will not be deleted (even if expired) until newer artifacts are available."
msgstr ""
msgid "Job|This artifact contains SAST scan results in JSON format."
msgstr ""
msgid "Job|This job failed because the necessary resources were not successfully created."
msgstr ""
@ -50715,7 +50734,7 @@ msgstr ""
msgid "Searching by both author and message is currently not supported."
msgstr ""
msgid "Seat controls"
msgid "Seat control"
msgstr ""
msgid "Seats"

View File

@ -11,13 +11,25 @@ RSpec.describe RapidDiffs::DiffFileHeaderComponent, type: :component, feature_ca
expect(header).to have_text(diff_file.file_path)
end
it "renders copy path button" do
clipboard_text = '{"text":"files/ruby/popen.rb","gfm":"`files/ruby/popen.rb`"}'
button_selector = '[data-testid="rd-diff-file-header"] [data-testid="rd-diff-file-copy-clipboard"]'
icon_selector = "#{button_selector} svg use"
render_component
expect(page.find(button_selector)['data-clipboard-text']).to eq(clipboard_text)
expect(page.find(button_selector)['title']).to eq(_('Copy file path'))
expect(page.find(icon_selector)['href']).to include('copy-to-clipboard')
end
it "renders submodule info", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/489868' do
allow(diff_file).to receive(:submodule?).and_return(true)
allow_next_instance_of(SubmoduleHelper) do |instance|
allow(instance).to receive(:submodule_links).and_return(nil)
end
render_component
expect(page.find('svg use')['href']).to include('folder-git')
expect(page.find('[data-testid="rd-diff-file-header-submodule"] svg use')['href']).to include('folder-git')
expect(page).to have_text(diff_file.blob.name)
expect(page).to have_text(diff_file.blob.id[0..7])
end

View File

@ -47,25 +47,6 @@ FactoryBot.define do
end
end
factory :npm_package_legacy do
sequence(:name) { |n| "@#{project.root_namespace.path}/package-#{n}" }
sequence(:version) { |n| "1.0.#{n}" }
package_type { :npm }
after :create do |package|
create :package_file, :npm, package: package
end
trait :with_build do
after :create do |package|
user = package.project.creator
pipeline = create(:ci_pipeline, user: user)
create(:ci_build, user: user, pipeline: pipeline)
create :package_build_info, package: package, pipeline: pipeline
end
end
end
factory :ml_model_package, class: 'Packages::MlModel::Package' do
sequence(:name) { |n| "mlmodel-package-#{n}" }
sequence(:version) { |n| "1.0.#{n}" }

View File

@ -18,7 +18,7 @@ RSpec.describe 'User deletes feature flag', :js, feature_category: :feature_flag
visit(project_feature_flags_path(project))
find('.js-feature-flag-delete-button').click
click_button('Delete')
click_button('Delete feature flag')
expect(page).to have_current_path(project_feature_flags_path(project))
end

View File

@ -33,7 +33,7 @@ RSpec.describe 'User sees feature flag list', :js, feature_category: :feature_fl
visit(project_feature_flags_path(project))
within_feature_flag_row(1) do
expect(page.find('.js-feature-flag-id')).to have_content('^1')
expect(find_by_testid('feature-flag-id')).to have_content('^1')
expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
expect_status_toggle_button_not_to_be_checked
@ -47,7 +47,7 @@ RSpec.describe 'User sees feature flag list', :js, feature_category: :feature_fl
visit(project_feature_flags_path(project))
within_feature_flag_row(2) do
expect(page.find('.js-feature-flag-id')).to have_content('^2')
expect(find_by_testid('feature-flag-id')).to have_content('^2')
expect(page.find('.feature-flag-name')).to have_content('drop_legacy_artifacts')
expect_status_toggle_button_not_to_be_checked
end
@ -57,7 +57,7 @@ RSpec.describe 'User sees feature flag list', :js, feature_category: :feature_fl
visit(project_feature_flags_path(project))
within_feature_flag_row(3) do
expect(page.find('.js-feature-flag-id')).to have_content('^3')
expect(find_by_testid('feature-flag-id')).to have_content('^3')
expect(page.find('.feature-flag-name')).to have_content('mr_train')
expect_status_toggle_button_to_be_checked

View File

@ -9,7 +9,8 @@ import {
nonExpiredArtifact,
lockedExpiredArtifact,
lockedNonExpiredArtifact,
reports,
sastReport,
dastReport,
} from './constants';
describe('Artifacts block', () => {
@ -32,6 +33,7 @@ describe('Artifacts block', () => {
const findArtifactsHelpLink = () => wrapper.findByTestId('artifacts-help-link');
const findPopover = () => wrapper.findComponent(GlPopover);
const findReportsBadge = () => wrapper.findComponent(GlBadge);
const findDownloadReportLink = () => wrapper.findByTestId('download-sast-report-link');
describe('with expired artifacts that are not locked', () => {
beforeEach(() => {
@ -166,7 +168,7 @@ describe('Artifacts block', () => {
});
});
describe('without reports', () => {
describe('without sast report', () => {
beforeEach(() => {
wrapper = createWrapper({
artifact: nonExpiredArtifact,
@ -176,18 +178,48 @@ describe('Artifacts block', () => {
it('does not display report badge', () => {
expect(findReportsBadge().exists()).toBe(false);
});
it('does not display download sast report link', () => {
expect(findDownloadReportLink().exists()).toBe(false);
});
});
describe('with reports', () => {
beforeEach(() => {
wrapper = createWrapper({
artifact: nonExpiredArtifact,
reports,
describe('sast', () => {
beforeEach(() => {
wrapper = createWrapper({
artifact: nonExpiredArtifact,
reports: sastReport,
});
});
it('displays report badge with tooltip', () => {
expect(findReportsBadge().text()).toBe('sast');
expect(findReportsBadge().attributes('title')).toBe(
'This artifact contains SAST scan results in JSON format.',
);
});
it('displays download sast report link', () => {
expect(findDownloadReportLink().attributes('href')).toBe(sastReport[0].download_path);
});
});
it('does display report badge', () => {
expect(findReportsBadge().text()).toBe('sast');
describe('dast', () => {
beforeEach(() => {
wrapper = createWrapper({
artifact: nonExpiredArtifact,
reports: dastReport,
});
});
it('displays dast report badge', () => {
expect(findReportsBadge().text()).toBe('dast');
});
it('does not display download sast report link', () => {
expect(findDownloadReportLink().exists()).toBe(false);
});
});
});
});

View File

@ -36,7 +36,7 @@ export const lockedNonExpiredArtifact = {
locked: true,
};
export const reports = [
export const sastReport = [
{
file_type: 'sast',
file_format: 'raw',
@ -44,3 +44,12 @@ export const reports = [
download_path: '/root/security-reports/-/jobs/12281/artifacts/download?file_type=sast',
},
];
export const dastReport = [
{
file_type: 'dast',
file_format: 'raw',
size: 10830,
download_path: '/root/security-reports/-/jobs/12273/artifacts/download?file_type=dast',
},
];

View File

@ -1,4 +1,4 @@
import { GlIcon, GlButton, GlToggle } from '@gitlab/ui';
import { GlButton, GlToggle, GlTableLite } from '@gitlab/ui';
import { nextTick } from 'vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { mountExtended } from 'helpers/vue_test_utils_helper';
@ -75,6 +75,8 @@ describe('Feature flag table', () => {
});
};
const findTable = () => wrapper.findComponent(GlTableLite);
beforeEach(() => {
props = getDefaultProps();
createWrapper(props, {
@ -94,16 +96,12 @@ describe('Feature flag table', () => {
});
it('Should render a table', () => {
expect(wrapper.classes('table-holder')).toBe(true);
});
it('Should render rows', () => {
expect(wrapper.find('.gl-responsive-table-row').exists()).toBe(true);
expect(findTable().exists()).toBe(true);
});
it('should render an ID column', () => {
expect(wrapper.find('.js-feature-flag-id').exists()).toBe(true);
expect(trimText(wrapper.find('.js-feature-flag-id').text())).toEqual('^1');
expect(wrapper.findByTestId('feature-flag-id').exists()).toBe(true);
expect(trimText(wrapper.findByTestId('feature-flag-id').text())).toEqual('^1');
});
it('Should render a status column', () => {
@ -114,7 +112,7 @@ describe('Feature flag table', () => {
});
it('Should render a feature flag column', () => {
expect(wrapper.find('.js-feature-flag-title').exists()).toBe(true);
expect(wrapper.findByTestId('feature-flag-title').exists()).toBe(true);
expect(trimText(wrapper.find('.feature-flag-name').text())).toEqual('flag name');
});
@ -125,10 +123,12 @@ describe('Feature flag table', () => {
});
it('should render an actions column', () => {
expect(wrapper.find('.table-action-buttons').exists()).toBe(true);
expect(wrapper.find('.js-feature-flag-delete-button').exists()).toBe(true);
expect(wrapper.find('.js-feature-flag-edit-button').exists()).toBe(true);
expect(wrapper.find('.js-feature-flag-edit-button').attributes('href')).toEqual('edit/path');
expect(wrapper.findByTestId('flags-table-action-buttons').exists()).toBe(true);
expect(wrapper.findByTestId('feature-flag-delete-button').exists()).toBe(true);
expect(wrapper.findByTestId('feature-flag-edit-button').exists()).toBe(true);
expect(wrapper.findByTestId('feature-flag-edit-button').attributes('href')).toEqual(
'edit/path',
);
});
});
@ -142,9 +142,8 @@ describe('Feature flag table', () => {
it(`${haveInfoIcon ? 'displays' : "doesn't display"} an information icon`, () => {
expect(
wrapper
.findByTestId(featureFlag.id)
.find('.feature-flag-description')
.findComponent(GlIcon)
.findByTestId(`feature-flag-description-${featureFlag.id}`)
.findComponent(GlButton)
.exists(),
).toBe(haveInfoIcon);
});
@ -152,8 +151,7 @@ describe('Feature flag table', () => {
if (haveInfoIcon) {
it('includes a tooltip', () => {
const icon = wrapper
.findByTestId(featureFlag.id)
.find('.feature-flag-description')
.findByTestId(`feature-flag-description-${featureFlag.id}`)
.findComponent(GlButton);
const tooltip = getBinding(icon.element, 'gl-tooltip');
@ -229,7 +227,7 @@ describe('Feature flag table', () => {
delete props.featureFlags[0].iid;
createWrapper(props);
expect(wrapper.find('.js-feature-flag-id').exists()).toBe(true);
expect(trimText(wrapper.find('.js-feature-flag-id').text())).toBe('');
expect(wrapper.findByTestId('feature-flag-id').exists()).toBe(true);
expect(trimText(wrapper.findByTestId('feature-flag-id').text())).toBe('');
});
});

View File

@ -78,6 +78,7 @@ export const mockGroups = [
webUrl: 'http://localhost/some-group',
defaultPermissions: false,
jobTokenPolicies: ['READ_JOBS', 'ADMIN_CONTAINERS'],
autopopulated: true,
__typename: 'Group',
},
{
@ -87,6 +88,7 @@ export const mockGroups = [
webUrl: 'http://localhost/another-group',
defaultPermissions: true,
jobTokenPolicies: [],
autopopulated: true,
__typename: 'Group',
},
{
@ -96,6 +98,7 @@ export const mockGroups = [
webUrl: 'http://localhost/a-sub-group',
defaultPermissions: false,
jobTokenPolicies: [],
autopopulated: false,
__typename: 'Group',
},
];
@ -113,6 +116,7 @@ export const mockProjects = [
defaultPermissions: false,
jobTokenPolicies: ['READ_JOBS'],
isLocked: false,
autopopulated: true,
__typename: 'Project',
},
{
@ -127,6 +131,7 @@ export const mockProjects = [
defaultPermissions: true,
jobTokenPolicies: [],
isLocked: true,
autopopulated: false,
__typename: 'Project',
},
];
@ -192,6 +197,8 @@ export const inboundGroupsAndProjectsWithScopeResponse = {
},
],
},
groupAllowlistAutopopulatedIds: ['gid://gitlab/Group/45'],
inboundAllowlistAutopopulatedIds: ['gid://gitlab/Project/23'],
},
},
},

View File

@ -21,6 +21,7 @@ describe('Token access table', () => {
const findProjectAvatar = () => wrapper.findByTestId('token-access-avatar');
const findName = () => wrapper.findByTestId('token-access-name');
const findPolicies = () => findAllTableRows().at(0).findAll('td').at(1);
const findAutopopulatedIcon = () => wrapper.findByTestId('autopopulated-icon');
describe.each`
type | items
@ -124,4 +125,32 @@ describe('Token access table', () => {
expect(findEditButton().exists()).toBe(false);
});
});
describe('group auto-populated icon', () => {
it('shows the icon when the item is auto-populated', () => {
createComponent({ items: [mockGroups[0]] });
expect(findAutopopulatedIcon().exists()).toBe(true);
});
it('does no shows the icon when the item is not auto-populated', () => {
createComponent({ items: [mockGroups[2]] });
expect(findAutopopulatedIcon().exists()).toBe(false);
});
});
describe('project auto-populated icon', () => {
it('shows the icon when the item is auto-populated', () => {
createComponent({ items: [mockProjects[0]] });
expect(findAutopopulatedIcon().exists()).toBe(true);
});
it('does no shows the icon when the item is not auto-populated', () => {
createComponent({ items: [mockProjects[1]] });
expect(findAutopopulatedIcon().exists()).toBe(false);
});
});
});

View File

@ -1,4 +1,4 @@
import { GlIcon } from '@gitlab/ui';
import { GlIcon, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
@ -25,6 +25,7 @@ describe('Changed file icon', () => {
const findIconName = () => findIcon().props('name');
const findIconClasses = () => findIcon().classes();
const findTooltipText = () => wrapper.attributes('title');
const findIconWrapper = () => wrapper.findComponent(GlButton);
it('with isCentered true, adds center class', () => {
factory({
@ -62,6 +63,7 @@ describe('Changed file icon', () => {
});
it('renders icon', () => {
expect(findIconWrapper().exists()).toBe(true);
expect(findIconName()).toBe(iconName);
expect(findIconClasses()).toContain(iconName);
});
@ -78,12 +80,8 @@ describe('Changed file icon', () => {
});
});
it('does not show icon', () => {
expect(findIcon().exists()).toBe(false);
});
it('does not have tooltip text', () => {
expect(findTooltipText()).toBeUndefined();
it('does not show icon and a tooltip associated with it', () => {
expect(findIconWrapper().exists()).toBe(false);
});
});

View File

@ -67,6 +67,12 @@ describe('MultiStepFormTemplate', () => {
expect(findActions().find('button.next').exists()).toBe(true);
});
it('does not render action buttons when no back or next slot is provided', () => {
createComponent();
expect(findActions().exists()).toBe(false);
});
it('renders footer slot content when provided', () => {
createComponent(
{},

View File

@ -14,6 +14,7 @@ RSpec.describe GitlabSchema.types['CiJobTokenScopeAllowlistEntry'], feature_cate
job_token_policies
added_by
created_at
autopopulated
]
expect(described_class).to have_graphql_fields(*expected_fields)

View File

@ -7,7 +7,8 @@ RSpec.describe GitlabSchema.types['CiJobTokenScopeType'], feature_category: :con
it 'has the correct fields' do
expected_fields = [:projects, :inboundAllowlist, :outboundAllowlist,
:groupsAllowlist, :inboundAllowlistCount, :groupsAllowlistCount]
:groupsAllowlist, :inboundAllowlistCount, :groupsAllowlistCount,
:groupAllowlistAutopopulatedIds, :inboundAllowlistAutopopulatedIds]
expect(described_class).to have_graphql_fields(*expected_fields)
end

View File

@ -0,0 +1,219 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::BackfillMissingNamespaceIdOnNotes, feature_category: :code_review_workflow do
let(:namespaces_table) { table(:namespaces) }
let(:notes_table) { table(:notes) }
let(:projects_table) { table(:projects) }
let(:snippets_table) { table(:snippets) }
let(:users_table) { table(:users) }
let(:epics_table) { table(:epics) }
let(:issues_table) { table(:issues) }
let(:work_item_types_table) { table(:work_item_types) }
let(:organizations_table) { table(:organizations) }
let!(:organization) { organizations_table.create!(name: 'organization', path: 'organization') }
let(:namespace_1) do
namespaces_table.create!(
name: 'namespace',
path: 'namespace-path-1',
organization_id: organization.id
)
end
let(:project_namespace_2) do
namespaces_table.create!(
name: 'namespace',
path: 'namespace-path-2',
type: 'Project',
organization_id: organization.id
)
end
let!(:project_1) do
projects_table
.create!(
name: 'project1',
path: 'path1',
namespace_id: namespace_1.id,
project_namespace_id: project_namespace_2.id,
visibility_level: 0,
organization_id: organization.id
)
end
let!(:user_1) { users_table.create!(name: 'bob', email: 'bob@example.com', projects_limit: 1) }
context "when namespace_id is derived from note.project_id" do
let(:alert_management_alert_note) do
notes_table.create!(project_id: project_1.id, noteable_type: "AlertManagement::Alert")
end
let(:commit_note) { notes_table.create!(project_id: project_1.id, noteable_type: "Commit") }
let(:merge_request_note) { notes_table.create!(project_id: project_1.id, noteable_type: "MergeRequest") }
let(:vulnerability_note) { notes_table.create!(project_id: project_1.id, noteable_type: "Vulnerability") }
let(:design_note) { notes_table.create!(project_id: project_1.id, noteable_type: "Design") }
let(:work_item_note) { notes_table.create!(project_id: project_1.id, noteable_type: "WorkItem") }
let(:issue_note) { notes_table.create!(project_id: project_1.id, noteable_type: "Issue") }
it "updates the namespace_id" do
[
alert_management_alert_note,
commit_note,
merge_request_note,
vulnerability_note,
design_note,
work_item_note,
issue_note
].each do |test_note|
expect(test_note.project_id).not_to be_nil
test_note.update_columns(namespace_id: nil)
test_note.reload
expect(test_note.namespace_id).to be_nil
described_class.new(
start_id: test_note.id,
end_id: test_note.id,
batch_table: :notes,
batch_column: :id,
sub_batch_size: 1,
pause_ms: 0,
connection: ActiveRecord::Base.connection
).perform
test_note.reload
expect(test_note.namespace_id).not_to be_nil
expect(test_note.namespace_id).to eq(Project.find(test_note.project_id).namespace_id)
end
end
end
context "when namespace_id is derived from noteable.author.namespace_id" do
let!(:snippet) do
snippets_table.create!(
author_id: user_1.id,
project_id: project_1.id
)
end
let(:personal_snippet_note) do
notes_table.create!(author_id: user_1.id, noteable_type: "Snippet", noteable_id: snippet.id)
end
let(:project_snippet_note) do
notes_table.create!(author_id: user_1.id, noteable_type: "Snippet", noteable_id: snippet.id)
end
let!(:user_namespace) do
namespaces_table.create!(
name: 'namespace',
path: 'user-namespace-path',
type: 'User',
owner_id: user_1.id,
organization_id: organization.id
)
end
it "updates the namespace_id" do
[project_snippet_note, personal_snippet_note].each do |test_note|
test_note.update_columns(namespace_id: nil)
test_note.reload
expect(test_note.namespace_id).to be_nil
described_class.new(
start_id: test_note.id,
end_id: test_note.id,
batch_table: :notes,
batch_column: :id,
sub_batch_size: 1,
pause_ms: 0,
connection: ActiveRecord::Base.connection
).perform
test_note.reload
expect(test_note.namespace_id).not_to be_nil
expect(test_note.namespace_id).to eq(user_namespace.id)
end
end
end
context "when namespace_id is derived from noteable.id" do
let!(:group_namespace) do
namespaces_table.create!(
name: 'namespace',
path: 'group-namespace-path',
type: 'Group',
owner_id: user_1.id,
organization_id: organization.id
)
end
let!(:work_items_type) do
work_item_types_table.create!(
id: Random.random_number(4000),
name: 'User Story',
correct_id: Random.random_number(4000)
)
end
let!(:issue) do
issues_table.create!(
title: "Example Epic",
author_id: user_1.id,
namespace_id: group_namespace.id,
correct_work_item_type_id: work_items_type.correct_id,
work_item_type_id: work_items_type.id
)
end
let!(:epic) do
epics_table.create!(
title: "Example Epic",
group_id: group_namespace.id,
author_id: user_1.id,
iid: Random.random_number(4000),
title_html: "<blink>Example</blink>",
issue_id: issue.id
)
end
let(:epic_note) do
notes_table.create!(
namespace_id: group_namespace.id,
noteable_type: "Epic",
noteable_id: epic.id
)
end
it "updates the namespace_id" do
[epic_note].each do |test_note|
test_note.update_columns(namespace_id: nil)
test_note.reload
expect(test_note.namespace_id).to be_nil
described_class.new(
start_id: test_note.id,
end_id: test_note.id,
batch_table: :notes,
batch_column: :id,
sub_batch_size: 1,
pause_ms: 0,
connection: ActiveRecord::Base.connection
).perform
test_note.reload
expect(test_note.namespace_id).not_to be_nil
expect(test_note.namespace_id).to eq(group_namespace.id)
end
end
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueBackfillMissingNamespaceIdOnNotes, feature_category: :code_review_workflow 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: :notes,
column_name: :id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
}
end
end
end

View File

@ -392,4 +392,48 @@ RSpec.describe Ci::JobToken::Allowlist, feature_category: :continuous_integratio
end
end
end
describe '#autopopulated_project_global_ids' do
let!(:project_link1) do
create(:ci_job_token_project_scope_link, source_project: source_project, autopopulated: true)
end
let!(:project_link2) do
create(:ci_job_token_project_scope_link, source_project: source_project, autopopulated: true)
end
let!(:project_link3) do
create(:ci_job_token_project_scope_link, source_project: source_project)
end
it 'returns an array of autopopulated project global ids only' do
project_global_ids = allowlist.autopopulated_project_global_ids
expect(project_global_ids.size).to eq 2
expect(project_global_ids).to contain_exactly(project_link1.target_project.to_global_id,
project_link2.target_project.to_global_id)
end
end
describe '#autopopulated_group_global_ids' do
let!(:group_link1) do
create(:ci_job_token_group_scope_link, source_project: source_project, autopopulated: true)
end
let!(:group_link2) do
create(:ci_job_token_group_scope_link, source_project: source_project, autopopulated: true)
end
let!(:group_link3) do
create(:ci_job_token_group_scope_link, source_project: source_project)
end
it 'returns an array of autopopulated group global ids only' do
group_global_ids = allowlist.autopopulated_group_global_ids
expect(group_global_ids.size).to eq 2
expect(group_global_ids).to contain_exactly(group_link1.target_group.to_global_id,
group_link2.target_group.to_global_id)
end
end
end

View File

@ -5,7 +5,6 @@ require 'spec_helper'
RSpec.describe UseSqlFunctionForPrimaryKeyLookups, feature_category: :groups_and_projects do
let_it_be(:project) { create(:project) }
let_it_be(:another_project) { create(:project) }
let_it_be(:namespace) { create(:namespace) }
let(:model) do
Class.new(ApplicationRecord) do
@ -15,15 +14,6 @@ RSpec.describe UseSqlFunctionForPrimaryKeyLookups, feature_category: :groups_and
end
end
let(:namespace_model) do
Class.new(ApplicationRecord) do
self.ignored_columns = %i[type] # rubocop: disable Cop/IgnoredColumns -- Throwaway one-off used for testing
self.table_name = :namespaces
include UseSqlFunctionForPrimaryKeyLookups
end
end
context 'when the use_sql_functions_for_primary_key_lookups FF is on' do
before do
stub_feature_flags(use_sql_functions_for_primary_key_lookups: true)
@ -59,28 +49,6 @@ RSpec.describe UseSqlFunctionForPrimaryKeyLookups, feature_category: :groups_and
expect(recorder.cached).to include(query.tr("\n", ''))
end
context 'when the log_sql_function_namespace_lookups FF is on' do
before do
stub_feature_flags(log_sql_function_namespace_lookups: true)
end
context 'when we query the namespaces table' do
it 'logs the info' do
expect(Gitlab::AppLogger).to receive(:info).with(a_hash_including({
message: 'Namespaces lookup using function'
}))
namespace_model.find(namespace.id)
end
end
context 'when we query the projects table' do
it 'does not log the info' do
expect(Gitlab::AppLogger).not_to receive(:info)
model.find(project.id)
end
end
end
context 'when the model has ignored columns' do
around do |example|
model.ignored_columns = %i[path]

View File

@ -18,7 +18,8 @@ RSpec.describe 'package details', feature_category: :package_registry do
let(:depth) { 3 }
let(:excluded) do
%w[metadata apiFuzzingCiConfiguration pipeline packageFiles
runners inboundAllowlistCount groupsAllowlistCount mergeTrains ciJobTokenAuthLogs]
runners inboundAllowlistCount groupsAllowlistCount mergeTrains ciJobTokenAuthLogs
groupAllowlistAutopopulatedIds inboundAllowlistAutopopulatedIds]
end
let(:metadata) { query_graphql_fragment('ComposerMetadata') }

View File

@ -24,11 +24,13 @@ RSpec.describe 'getting merge request information nested in a project', feature_
# we exclude Project.pipeline because it needs arguments,
# codequalityReportsComparer because it is behind a feature flag
# and runners because the user is not an admin and therefore has no access
# and inboundAllowlistCount, groupsAllowlistCount the user has no access
# and inboundAllowlistCount, groupsAllowlistCount, groupAllowlistAutopopulatedIds,
# inboundAllowlistAutopopulatedIds the user has no access
# mergeTrains because it is a licensed feature
let(:excluded) do
%w[jobs pipeline runners codequalityReportsComparer
mlModels inboundAllowlistCount groupsAllowlistCount mergeTrains ciJobTokenAuthLogs mlExperiments]
mlModels inboundAllowlistCount groupsAllowlistCount mergeTrains ciJobTokenAuthLogs mlExperiments
groupAllowlistAutopopulatedIds inboundAllowlistAutopopulatedIds]
end
let(:mr_fields) { all_graphql_fields_for('MergeRequest', excluded: excluded) }

View File

@ -278,8 +278,6 @@ RSpec.configure do |config|
end
config.before do |example|
stub_feature_flags(log_sql_function_namespace_lookups: false)
if example.metadata.fetch(:stub_feature_flags, true)
# The following can be removed when we remove the staged rollout strategy
# and we can just enable it using instance wide settings

View File

@ -31,19 +31,19 @@ module FeatureFlagHelpers
end
def within_feature_flag_row(index)
within ".gl-responsive-table-row:nth-child(#{index + 1})" do
within "tbody tr:nth-child(#{index})" do
yield
end
end
def within_feature_flag_scopes
within '.js-feature-flag-environments' do
within "div[data-testid='feature-flag-environments']" do
yield
end
end
def within_scope_row(index)
within ".gl-responsive-table-row:nth-child(#{index + 1})" do
within "tbody tr:nth-child(#{index + 1})" do
yield
end
end
@ -73,7 +73,7 @@ module FeatureFlagHelpers
end
def edit_feature_flag_button
find('.js-feature-flag-edit-button')
find_link 'Edit'
end
def delete_strategy_button