Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a9b8a31684
commit
93326697e0
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
f90c47160a2055dd8578774b5836533c2224d35f
|
||||
4064e55eea5f1bb415cb57e8aa23630b14d7e387
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
45d5c77a77939f051b23b2052bb171d6314bb8e5
|
||||
db58d685d85c5616bc90cdb806e9ee67cfc1c398
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 })"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
fragment AllowlistEntry on CiJobTokenScopeAllowlistEntry {
|
||||
defaultPermissions
|
||||
jobTokenPolicies
|
||||
autopopulated
|
||||
target {
|
||||
... on Group {
|
||||
id
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ query inboundGetGroupsAndProjectsWithCIJobTokenScope($fullPath: ID!) {
|
|||
webUrl
|
||||
}
|
||||
}
|
||||
groupAllowlistAutopopulatedIds
|
||||
inboundAllowlistAutopopulatedIds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ class WorkItem < Issue
|
|||
end
|
||||
|
||||
def non_widgets
|
||||
[:related_vulnerabilities]
|
||||
[:related_vulnerabilities, :pending_escalations]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
dff289034006ffbf12d45275cf91d3dc3843c80894cea79738ffec3149edbf76
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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`. |
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||

|
||||

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

|
||||

|
||||
|
||||
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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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 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>.
|
||||
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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
```
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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)
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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**.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}" }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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('');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
{},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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') }
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue