Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-03-25 12:11:33 +00:00
parent 0102b53b44
commit 307115302b
118 changed files with 1355 additions and 387 deletions

View File

@ -12,7 +12,7 @@ const LINK_TAG_PATTERN = '[{text}](url)';
const STRIKETHROUGH_TAG_PATTERN = '~~';
const QUOTE_TAG_PATTERN = '> ';
const ALLOWED_UNDO_TAGS = [
const EMPHASIS_TAGS = [
BOLD_TAG_PATTERN,
INLINE_CODE_TAG_PATTERN,
ITALIC_TAG_PATTERN,
@ -42,6 +42,34 @@ function selectedText(text, textarea) {
return text.substring(textarea.selectionStart, textarea.selectionEnd);
}
/**
* Expands the textArea selection to surrounding tags so that the undo functionality works
* also when selecting text inside emphasis tags.
*
* @param {HTMLTextAreaElement} textArea - The relevant text area
* @param {String} tag - The Markdown tag we want to enter (Example: `- [ ] ` for lists)
* @returns {String | undefined} - The new selection or undefined if no changes to selection
*/
function selectSurroundingTags(textArea, tag) {
if (!textArea || !tag || !EMPHASIS_TAGS.includes(tag)) {
return;
}
const currentSelection = textArea.value.substring(textArea.selectionStart, textArea.selectionEnd);
if (currentSelection.startsWith(tag) && currentSelection.endsWith(tag)) {
return; // The tag is already part of the selection
}
const start = Math.max(textArea.selectionStart - tag.length, 0);
const end = Math.min(textArea.selectionEnd + tag.length, textArea.value.length);
const surroundingSelection = textArea.value.substring(start, end);
if (surroundingSelection.startsWith(tag) && surroundingSelection.endsWith(tag)) {
textArea.selectionStart = start;
textArea.selectionEnd = end;
}
return textArea.value.substring(textArea.selectionStart, textArea.selectionEnd);
}
function addBlockTags(blockTag, selected) {
return `${blockTag}\n${selected}\n${blockTag}`;
}
@ -349,6 +377,8 @@ function moveCursor({
* we want to remove the tag from the line.
* 4. `removeTagFromLine` in a multiline selection, this is a function that removes
* the tag from the line.
* 5. `shouldPreserveTextAreaSelection` boolean indicating if the selected text should be kept
* selected instead of moving the cursor at the end.
*
* @param {Object} options
* @param {string} selected - the selected bit of the text area which is to be replaced
@ -365,6 +395,7 @@ function prepareInsertMarkdownText({ selected, tag, selectedSplit }) {
shouldUseSelectedWithoutTags: QUOTE_LINE_PATTERN.test(selected),
removeTagFromLine: (line) => line.replace(QUOTE_LINE_PATTERN, ''),
shouldRemoveTagFromLine: () => shouldRemoveQuote,
shouldPreserveTextAreaSelection: EMPHASIS_TAGS.includes(tag) && selected.length > 0,
};
}
@ -374,9 +405,10 @@ function prepareInsertMarkdownText({ selected, tag, selectedSplit }) {
selected.length >= tag.length * 2 &&
selected.startsWith(tag) &&
selected.endsWith(tag) &&
ALLOWED_UNDO_TAGS.includes(tag),
EMPHASIS_TAGS.includes(tag),
removeTagFromLine: (line) => line.replace(tag, ''),
shouldRemoveTagFromLine: (line) => line.indexOf(tag) === 0,
shouldPreserveTextAreaSelection: EMPHASIS_TAGS.includes(tag) && selected.length > 0,
};
}
@ -417,9 +449,14 @@ export function insertMarkdownText({
return;
}
selected = selectSurroundingTags(textArea, tag) ?? selected;
let removedLastNewLine = false;
let removedFirstNewLine = false;
let currentLineEmpty = false;
const selectedLength = selected.length;
const { selectionStart: textAreaSelectionStart, selectionEnd: textAreaSelectionEnd } =
textArea || {};
let editorSelectionStart;
let editorSelectionEnd;
let lastNewLine;
@ -490,6 +527,7 @@ export function insertMarkdownText({
removeTagFromLine,
shouldRemoveTagFromLine,
getSelectedWithoutTags,
shouldPreserveTextAreaSelection,
} = prepareInsertMarkdownText({ selected, tag, selectedSplit });
const getSelectedWithTags = () => `${startChar}${tag}${selected}${wrap ? tag : ''}`;
@ -536,17 +574,22 @@ export function insertMarkdownText({
insertText(textArea, textToUpdate);
}
moveCursor({
textArea,
tag: tag.replace(textPlaceholder, selected),
cursorOffset,
positionBetweenTags: wrap && selected.length === 0,
removedLastNewLine,
select,
editor,
editorSelectionStart,
editorSelectionEnd,
});
if (shouldPreserveTextAreaSelection) {
const offset = textToUpdate.length - selectedLength;
textArea.setSelectionRange(textAreaSelectionStart, textAreaSelectionEnd + offset);
} else {
moveCursor({
textArea,
tag: tag.replace(textPlaceholder, selected),
cursorOffset,
positionBetweenTags: wrap && selected.length === 0,
removedLastNewLine,
select,
editor,
editorSelectionStart,
editorSelectionEnd,
});
}
}
export function updateText({ textArea, tag, cursorOffset, blockTag, wrap, select, tagContent }) {

View File

@ -27,6 +27,7 @@ import {
showSingleFileEditorForkSuggestion,
showWebIdeForkSuggestion,
} from '~/repository/utils/fork_suggestion_utils';
import { showBlameButton, isUsingLfs } from '~/repository/utils/storage_info_utils';
import blobControlsQuery from '~/repository/queries/blob_controls.query.graphql';
import userGitpodInfo from '~/repository/queries/user_gitpod_info.query.graphql';
import applicationInfoQuery from '~/blob/queries/application_info.query.graphql';
@ -150,20 +151,11 @@ export default {
userPermissions() {
return this.project?.userPermissions || DEFAULT_BLOB_INFO.userPermissions;
},
storageInfo() {
const { storedExternally, externalStorage } = this.blobInfo;
return {
isExternallyStored: storedExternally,
storageType: externalStorage,
isLfs: storedExternally && externalStorage === 'lfs',
};
},
showBlameButton() {
const { isExternallyStored, isLfs } = this.storageInfo;
return !isExternallyStored && !isLfs;
return showBlameButton(this.blobInfo);
},
isUsingLfs() {
return this.storageInfo.isLfs;
return isUsingLfs(this.blobInfo);
},
isBinaryFileType() {
return (
@ -269,7 +261,10 @@ export default {
v-gl-tooltip.html="findFileTooltip"
:aria-keyshortcuts="findFileShortcutKey"
data-testid="find"
:class="$options.buttonClassList"
:class="[
$options.buttonClassList,
{ 'gl-hidden sm:gl-inline-flex': glFeatures.blobOverflowMenu },
]"
@click="handleFindFile"
>
{{ $options.i18n.findFile }}
@ -278,7 +273,10 @@ export default {
v-if="showBlameButton"
data-testid="blame"
:href="blobInfo.blamePath"
:class="$options.buttonClassList"
:class="[
$options.buttonClassList,
{ 'gl-hidden sm:gl-inline-flex': glFeatures.blobOverflowMenu },
]"
class="js-blob-blame-link"
>
{{ $options.i18n.blame }}

View File

@ -11,7 +11,7 @@ import BlobButtonGroup from 'ee_else_ce/repository/components/header_area/blob_b
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import BlobDefaultActionsGroup from './blob_default_actions_group.vue';
import BlobDeleteFileGroup from './blob_delete_file_group.vue';
import PermalinkDropdownItem from './permalink_dropdown_item.vue';
import BlobRepositoryActionsGroup from './blob_repository_actions_group.vue';
export const i18n = {
dropdownLabel: __('File actions'),
@ -23,7 +23,7 @@ export default {
name: 'CEBlobOverflowMenu',
i18n,
components: {
PermalinkDropdownItem,
BlobRepositoryActionsGroup,
GlDisclosureDropdown,
BlobDefaultActionsGroup,
BlobButtonGroup,
@ -154,7 +154,7 @@ export default {
class="gl-mr-0"
category="tertiary"
>
<permalink-dropdown-item :permalink-path="blobInfo.permalinkPath" />
<blob-repository-actions-group :permalink-path="blobInfo.permalinkPath" />
<blob-button-group
v-if="isLoggedIn && !blobInfo.archived"
:current-ref="currentRef"

View File

@ -2,12 +2,20 @@
import Vue from 'vue';
import { GlDisclosureDropdownGroup, GlDisclosureDropdownItem, GlToast } from '@gitlab/ui';
import { __ } from '~/locale';
import { keysFor, PROJECT_FILES_GO_TO_PERMALINK } from '~/behaviors/shortcuts/keybindings';
import Shortcuts from '~/behaviors/shortcuts/shortcuts';
import {
keysFor,
PROJECT_FILES_GO_TO_PERMALINK,
START_SEARCH_PROJECT_FILE,
} from '~/behaviors/shortcuts/keybindings';
import { Mousetrap } from '~/lib/mousetrap';
import { shouldDisableShortcuts } from '~/behaviors/shortcuts/shortcuts_toggle';
import { getBaseURL, relativePathToAbsolute } from '~/lib/utils/url_utility';
import { InternalEvents } from '~/tracking';
import { FIND_FILE_BUTTON_CLICK } from '~/tracking/constants';
import { lineState } from '~/blob/state';
import { getPageParamValue, getPageSearchString } from '~/blob/utils';
import { showBlameButton } from '~/repository/utils/storage_info_utils';
Vue.use(GlToast);
@ -16,6 +24,7 @@ export default {
GlDisclosureDropdownGroup,
GlDisclosureDropdownItem,
},
inject: ['blobInfo'],
props: {
permalinkPath: {
type: String,
@ -23,6 +32,9 @@ export default {
},
},
computed: {
findFileShortcutKey() {
return keysFor(START_SEARCH_PROJECT_FILE)[0];
},
permalinkShortcutKey() {
return keysFor(PROJECT_FILES_GO_TO_PERMALINK)[0];
},
@ -38,6 +50,18 @@ export default {
}
return baseAbsolutePath;
},
blameItem() {
return {
text: __('Blame'),
href: this.blobInfo.blamePath,
extraAttrs: {
'data-testid': 'blame',
},
};
},
showBlameButton() {
return showBlameButton(this.blobInfo);
},
},
mounted() {
Mousetrap.bind(keysFor(PROJECT_FILES_GO_TO_PERMALINK), this.triggerCopyPermalink);
@ -46,6 +70,10 @@ export default {
Mousetrap.unbind(keysFor(PROJECT_FILES_GO_TO_PERMALINK));
},
methods: {
handleFindFile() {
InternalEvents.trackEvent(FIND_FILE_BUTTON_CLICK);
Shortcuts.focusSearchFile();
},
triggerCopyPermalink() {
const buttonElement = this.$refs.copyPermalinkButton.$el;
buttonElement.click();
@ -60,6 +88,27 @@ export default {
<template>
<gl-disclosure-dropdown-group>
<gl-disclosure-dropdown-item
:aria-keyshortcuts="findFileShortcutKey"
data-testid="find"
class="sm:gl-hidden"
@action="handleFindFile"
>
<template #list-item>
<span class="gl-flex gl-items-center gl-justify-between">
<span>{{ __('Find file') }}</span>
<kbd v-if="findFileShortcutKey && !shortcutsDisabled" class="flat">{{
findFileShortcutKey
}}</kbd>
</span>
</template>
</gl-disclosure-dropdown-item>
<gl-disclosure-dropdown-item
v-if="showBlameButton"
:item="blameItem"
class="js-blob-blame-link sm:gl-hidden"
data-testid="blame-dropdown-item"
/>
<gl-disclosure-dropdown-item
ref="copyPermalinkButton"
:aria-keyshortcuts="permalinkShortcutKey"

View File

@ -0,0 +1,41 @@
/**
* Returns storage information for a blob
*
* @param {Object} blobInfo - The blob information object
* @param {boolean} blobInfo.storedExternally - Whether the blob is stored externally
* @param {string} blobInfo.externalStorage - The type of external storage (e.g., 'lfs')
* @returns {Object} Storage information object
* @returns {boolean} returns.isExternallyStored - Whether the blob is stored externally
* @returns {string} returns.storageType - The type of external storage
* @returns {boolean} returns.isLfs - Whether the blob is stored in LFS (Large File Storage)
*/
const getStorageInfo = ({ storedExternally, externalStorage }) => ({
isExternallyStored: storedExternally,
isLfs: Boolean(storedExternally) && externalStorage === 'lfs',
});
/**
* Determines whether to show the blame button for a blob
* The blame button should not be shown for externally stored files or LFS files
*
* @param {Object} blobInfo - The blob information object
* @param {boolean} blobInfo.storedExternally - Whether the blob is stored externally
* @param {string} blobInfo.externalStorage - The type of external storage (e.g., 'lfs')
* @returns {boolean} Whether to show the blame button
*/
export const showBlameButton = (blobInfo) => {
const { isExternallyStored, isLfs } = getStorageInfo(blobInfo);
return !isExternallyStored && !isLfs;
};
/**
* Determines whether the blob is using Git LFS (Large File Storage)
*
* @param {Object} blobInfo - The blob information object
* @param {boolean} blobInfo.storedExternally - Whether the blob is stored externally
* @param {string} blobInfo.externalStorage - The type of external storage (e.g., 'lfs')
* @returns {boolean} Whether the blob is using LFS
*/
export const isUsingLfs = (blobInfo) => {
return getStorageInfo(blobInfo).isLfs;
};

View File

@ -68,7 +68,7 @@ import WorkItemMilestone from './work_item_milestone.vue';
import WorkItemParent from './work_item_parent.vue';
import WorkItemLoading from './work_item_loading.vue';
import WorkItemCrmContacts from './work_item_crm_contacts.vue';
import WorkItemDueDates from './work_item_due_dates.vue';
import WorkItemDates from './work_item_dates.vue';
export default {
components: {
@ -91,7 +91,7 @@ export default {
WorkItemProjectsListbox,
TitleSuggestions,
WorkItemParent,
WorkItemDueDates,
WorkItemDates,
WorkItemWeight: () => import('ee_component/work_items/components/work_item_weight.vue'),
WorkItemHealthStatus: () =>
import('ee_component/work_items/components/work_item_health_status.vue'),
@ -946,7 +946,7 @@ export default {
:work-item-type="selectedWorkItemTypeName"
@error="$emit('error', $event)"
/>
<work-item-due-dates
<work-item-dates
v-if="workItemStartAndDueDate"
class="work-item-attributes-item"
:can-update="canUpdate"

View File

@ -83,12 +83,12 @@ export default {
methods: {
findVersionId,
routeToVersion(versionId) {
const { show } = queryToObject(window.location.search);
this.$router.push({
path: this.$route.path,
query: {
// Retain any existing page params and only append/override `version`.
...queryToObject(window.location.search),
version: this.findVersionId(versionId),
...(show && { show }),
},
});
},

View File

@ -32,7 +32,7 @@ import WorkItemMilestone from './work_item_milestone.vue';
import WorkItemParent from './work_item_parent.vue';
import WorkItemTimeTracking from './work_item_time_tracking.vue';
import WorkItemCrmContacts from './work_item_crm_contacts.vue';
import WorkItemDueDates from './work_item_due_dates.vue';
import WorkItemDates from './work_item_dates.vue';
export default {
ListType,
@ -44,7 +44,7 @@ export default {
WorkItemParent,
WorkItemTimeTracking,
WorkItemCrmContacts,
WorkItemDueDates,
WorkItemDates,
WorkItemWeight: () => import('ee_component/work_items/components/work_item_weight.vue'),
WorkItemProgress: () => import('ee_component/work_items/components/work_item_progress.vue'),
WorkItemIteration: () => import('ee_component/work_items/components/work_item_iteration.vue'),
@ -249,7 +249,7 @@ export default {
:work-item-type="workItemType"
@error="$emit('error', $event)"
/>
<work-item-due-dates
<work-item-dates
v-if="workItemStartAndDueDate"
class="work-item-attributes-item"
:can-update="canUpdateMetadata"

View File

@ -324,46 +324,48 @@ export default {
</p>
</template>
<template #editing-content="{ stopEditing }">
<gl-form-group
class="gl-m-0 gl-flex gl-items-center gl-gap-3"
:label="s__('WorkItem|Start')"
:label-for="$options.startDateInputId"
label-class="!gl-font-normal !gl-pb-0 gl-min-w-7 sm:gl-min-w-fit md:gl-min-w-7 gl-break-words"
>
<gl-datepicker
v-model="localStartDate"
class="gl-max-w-20"
container="body"
:disabled="isDatepickerDisabled"
:input-id="$options.startDateInputId"
show-clear-button
:target="null"
data-testid="start-date-picker"
@clear="clearStartDatePicker"
@close="handleStartDateInput"
@keydown.esc.native="stopEditing"
/>
</gl-form-group>
<gl-form-group
class="gl-m-0 gl-flex gl-items-center gl-gap-3"
:label="s__('WorkItem|Due')"
:label-for="$options.dueDateInputId"
label-class="!gl-font-normal !gl-pb-0 gl-min-w-7 sm:gl-min-w-fit md:gl-min-w-7 gl-break-words"
>
<gl-datepicker
v-model="localDueDate"
class="gl-max-w-20"
container="body"
:disabled="isDatepickerDisabled"
:input-id="$options.dueDateInputId"
:min-date="localStartDate"
show-clear-button
:target="null"
data-testid="due-date-picker"
@clear="clearDueDatePicker"
@keydown.esc.native="stopEditing"
/>
</gl-form-group>
<div class="gl-flex gl-flex-wrap gl-gap-x-5 gl-gap-y-3 gl-pt-2 sm:gl-flex-row md:gl-flex-col">
<gl-form-group
class="gl-m-0 gl-flex gl-items-center gl-gap-3"
:label="s__('WorkItem|Start')"
:label-for="$options.startDateInputId"
label-class="!gl-font-normal !gl-pb-0 gl-min-w-7 sm:gl-min-w-fit md:gl-min-w-7 gl-break-words"
>
<gl-datepicker
v-model="localStartDate"
class="gl-max-w-20"
container="body"
:disabled="isDatepickerDisabled"
:input-id="$options.startDateInputId"
show-clear-button
:target="null"
data-testid="start-date-picker"
@clear="clearStartDatePicker"
@close="handleStartDateInput"
@keydown.esc.native="stopEditing"
/>
</gl-form-group>
<gl-form-group
class="gl-m-0 gl-flex gl-items-center gl-gap-3"
:label="s__('WorkItem|Due')"
:label-for="$options.dueDateInputId"
label-class="!gl-font-normal !gl-pb-0 gl-min-w-7 sm:gl-min-w-fit md:gl-min-w-7 gl-break-words"
>
<gl-datepicker
v-model="localDueDate"
class="gl-max-w-20"
container="body"
:disabled="isDatepickerDisabled"
:input-id="$options.dueDateInputId"
:min-date="localStartDate"
show-clear-button
:target="null"
data-testid="due-date-picker"
@clear="clearDueDatePicker"
@keydown.esc.native="stopEditing"
/>
</gl-form-group>
</div>
</template>
</work-item-sidebar-widget>
</template>

View File

@ -706,7 +706,7 @@
width: $gl-spacing-scale-5;
height: $gl-spacing-scale-3;
border-radius: $border-radius-base;
border: 1px solid var(--white, $white);
border: 1px solid var(--gl-dropdown-background-color);
}
.git-revision-dropdown {

View File

@ -11,6 +11,7 @@ class ApplicationRecord < ActiveRecord::Base
include ResetOnColumnErrors
include HasCheckConstraints
include IgnorableColumns
include PopulatesShardingKey
self.abstract_class = true

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
module PopulatesShardingKey # rubocop:disable Gitlab/BoundedContexts -- general purpose concern for ApplicationRecord
extend ActiveSupport::Concern
class_methods do
# Simple DSL to isolate sharding key population in models
# Examples:
# populate_sharding_key :project_id, source: :issue
# populate_sharding_key :project_id, source: :merge_request, field: :target_project_id
# populate_sharding_key :project_id do
# issue.project_id
# end
# populate_sharding_key :project_id, &:get_sharding_key
# Also see `populate_sharding_key` spec matcher
def populate_sharding_key(sharding_attribute, source: nil, field: sharding_attribute, &block)
value_proc = block || proc { send(source)&.public_send(field) } # rubocop:disable GitlabSecurity/PublicSend -- send is intended
before_validation -> { assign_attributes(sharding_attribute => instance_exec(self, &value_proc)) },
unless: :"#{sharding_attribute}?"
end
end
end

View File

@ -1,9 +0,0 @@
---
name: users_search_scoped_to_authorized_namespaces_basic_search
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/442091
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/182557
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/520710
milestone: '17.10'
group: group::global search
type: gitlab_com_derisk
default_enabled: false

View File

@ -1,9 +0,0 @@
---
name: users_search_scoped_to_authorized_namespaces_basic_search_by_ids
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/442091
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/182557
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/524297
milestone: '17.10'
group: group::global search
type: gitlab_com_derisk
default_enabled: false

View File

@ -324,6 +324,10 @@ group_wiki_repositories:
- table: namespaces
column: group_id
on_delete: async_delete
group_wiki_repository_states:
- table: namespaces
column: group_id
on_delete: async_delete
groups_visits:
- table: namespaces
column: entity_id
@ -478,6 +482,10 @@ pages_deployments:
- table: p_ci_builds
column: ci_build_id
on_delete: async_nullify
pool_repositories:
- table: projects
column: source_project_id
on_delete: async_nullify
project_authorizations:
- table: users
column: user_id

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveProjectIntegrationsMetric
options:
type: asana
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveGroupIntegrationsMetric
options:
type: asana
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveInstanceIntegrationsMetric
options:
type: asana
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveProjectsInheritingIntegrationsMetric
options:
type: asana
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveGroupsInheritingIntegrationsMetric
options:
type: asana
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveProjectIntegrationsMetric
options:
type: assembla
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveGroupIntegrationsMetric
options:
type: assembla
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveInstanceIntegrationsMetric
options:
type: assembla
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveProjectsInheritingIntegrationsMetric
options:
type: assembla
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveGroupsInheritingIntegrationsMetric
options:
type: assembla
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveProjectIntegrationsMetric
options:
type: bamboo
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveGroupIntegrationsMetric
options:
type: bamboo
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveInstanceIntegrationsMetric
options:
type: bamboo
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveProjectsInheritingIntegrationsMetric
options:
type: bamboo
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveGroupsInheritingIntegrationsMetric
options:
type: bamboo
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveProjectIntegrationsMetric
options:
type: bugzilla
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveGroupIntegrationsMetric
options:
type: bugzilla
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveInstanceIntegrationsMetric
options:
type: bugzilla
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveProjectsInheritingIntegrationsMetric
options:
type: bugzilla
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveGroupsInheritingIntegrationsMetric
options:
type: bugzilla
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveProjectIntegrationsMetric
options:
type: buildkite
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveGroupIntegrationsMetric
options:
type: buildkite
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveInstanceIntegrationsMetric
options:
type: buildkite
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveProjectsInheritingIntegrationsMetric
options:
type: buildkite
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveGroupsInheritingIntegrationsMetric
options:
type: buildkite
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveProjectIntegrationsMetric
options:
type: campfire
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveGroupIntegrationsMetric
options:
type: campfire
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveInstanceIntegrationsMetric
options:
type: campfire
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveProjectsInheritingIntegrationsMetric
options:
type: campfire
tiers:
- free
- premium

View File

@ -9,6 +9,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: ActiveGroupsInheritingIntegrationsMetric
options:
type: campfire
tiers:
- free
- premium

View File

@ -10,6 +10,9 @@ milestone: "15.8"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104888
time_frame: all
data_source: database
instrumentation_class: ActiveProjectsInheritingIntegrationsMetric
options:
type: apple_app_store
data_category: optional
performance_indicator_type: []
tiers:

View File

@ -10,6 +10,9 @@ milestone: "15.8"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104888
time_frame: all
data_source: database
instrumentation_class: ActiveGroupIntegrationsMetric
options:
type: apple_app_store
data_category: optional
performance_indicator_type: []
tiers:

View File

@ -10,6 +10,9 @@ milestone: "15.8"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104888
time_frame: all
data_source: database
instrumentation_class: ActiveProjectIntegrationsMetric
options:
type: apple_app_store
data_category: optional
performance_indicator_type: []
tiers:

View File

@ -10,6 +10,9 @@ milestone: "15.8"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104888
time_frame: all
data_source: database
instrumentation_class: ActiveGroupsInheritingIntegrationsMetric
options:
type: apple_app_store
data_category: optional
performance_indicator_type: []
tiers:

View File

@ -10,6 +10,9 @@ milestone: "15.8"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104888
time_frame: all
data_source: database
instrumentation_class: ActiveInstanceIntegrationsMetric
options:
type: apple_app_store
data_category: optional
performance_indicator_type: []
tiers:

View File

@ -8,6 +8,9 @@ milestone: "16.1"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732
time_frame: all
data_source: database
instrumentation_class: ActiveGroupIntegrationsMetric
options:
type: clickup
data_category: optional
performance_indicator_type: []
tiers:

View File

@ -8,6 +8,9 @@ milestone: "16.1"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732
time_frame: all
data_source: database
instrumentation_class: ActiveGroupsInheritingIntegrationsMetric
options:
type: clickup
data_category: optional
performance_indicator_type: []
tiers:

View File

@ -8,6 +8,9 @@ milestone: "16.1"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732
time_frame: all
data_source: database
instrumentation_class: ActiveInstanceIntegrationsMetric
options:
type: clickup
data_category: optional
performance_indicator_type: []
tiers:

View File

@ -8,6 +8,9 @@ milestone: "16.1"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732
time_frame: all
data_source: database
instrumentation_class: ActiveProjectIntegrationsMetric
options:
type: clickup
data_category: optional
performance_indicator_type: []
tiers:

View File

@ -8,6 +8,9 @@ milestone: "16.1"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732
time_frame: all
data_source: database
instrumentation_class: ActiveProjectsInheritingIntegrationsMetric
options:
type: clickup
data_category: optional
performance_indicator_type: []
tiers:

View File

@ -8,6 +8,9 @@ milestone: "16.9"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136754
time_frame: all
data_source: database
instrumentation_class: ActiveInstanceIntegrationsMetric
options:
type: beyond_identity
data_category: optional
performance_indicator_type: []
tiers:

View File

@ -8,6 +8,9 @@ milestone: "16.9"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136754
time_frame: all
data_source: database
instrumentation_class: ActiveGroupsInheritingIntegrationsMetric
options:
type: beyond_identity
data_category: optional
performance_indicator_type: []
tiers:

View File

@ -8,6 +8,9 @@ milestone: "16.9"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136754
time_frame: all
data_source: database
instrumentation_class: ActiveGroupIntegrationsMetric
options:
type: beyond_identity
data_category: optional
performance_indicator_type: []
tiers:

View File

@ -8,6 +8,9 @@ milestone: "16.9"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136754
time_frame: all
data_source: database
instrumentation_class: ActiveProjectsInheritingIntegrationsMetric
options:
type: beyond_identity
data_category: optional
performance_indicator_type: []
tiers:

View File

@ -8,6 +8,9 @@ milestone: "16.9"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136754
time_frame: all
data_source: database
instrumentation_class: ActiveProjectIntegrationsMetric
options:
type: beyond_identity
data_category: optional
performance_indicator_type: []
tiers:

View File

@ -0,0 +1,18 @@
---
key_path: settings.instance_runner_token_expiration_enabled
description: >
Tracks whether an expiration interval is defined for instance runner authentication tokens
product_group: runner
value_type: boolean
status: active
milestone: "17.11"
introduced_by_url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/185544'
time_frame: none
data_source: system
instrumentation_class: InstanceRunnerTokenExpirationEnabledMetric
data_category: optional
performance_indicator_type: []
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,18 @@
---
key_path: settings.group_runner_token_expiration_enabled
description: >
Tracks whether an expiration interval is defined for group runner authentication tokens
product_group: runner
value_type: boolean
status: active
milestone: "17.11"
introduced_by_url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/185544'
time_frame: none
data_source: system
instrumentation_class: GroupRunnerTokenExpirationEnabledMetric
data_category: optional
performance_indicator_type: []
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,18 @@
---
key_path: settings.project_runner_token_expiration_enabled
description: >
Tracks whether an expiration interval is defined for project runner authentication tokens
product_group: runner
value_type: boolean
status: active
milestone: "17.11"
introduced_by_url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/185544'
time_frame: none
data_source: system
instrumentation_class: ProjectRunnerTokenExpirationEnabledMetric
data_category: optional
performance_indicator_type: []
tiers:
- free
- premium
- ultimate

View File

@ -3,6 +3,7 @@ migration_job_name: DeleteOrphanedPartitionedCiRunnerMachineRecords
description: >-
Removes ci_runner_machines_687967fa8a records that don't have a matching ci_runners_e59bb2812d record.
This can happen because there was a period in time where a FK didn't exist.
Deleted in 17.11 since this can collide with 20250307080000_replace_ci_runners_machines_with_partitioned_table
feature_category: fleet_visibility
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/172422
milestone: '17.8'

View File

@ -5,6 +5,8 @@ description: >-
that don't have a matching ci_runners_e59bb2812d record, this time running also
for self-managed environments.
This can happen because there was a period in time where a FK didn't exist.
Deleted in 17.11 since this can collide with 20250307080000_replace_ci_runners_machines_with_partitioned_table
scheduled in 17.10.
feature_category: fleet_visibility
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/177774
milestone: '17.9'

View File

@ -0,0 +1,11 @@
---
table_name: ldap_admin_role_links
classes:
- Authz::LdapAdminRoleLink
feature_categories:
- permissions
description: Stores mapping between LDAP server and admin member roles
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/184606
milestone: '17.11'
gitlab_schema: gitlab_main_clusterwide
table_size: small

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
class CreateLdapAdminRoleLink < Gitlab::Database::Migration[2.2]
milestone '17.11'
def change
create_table :ldap_admin_role_links do |t|
t.references :member_role,
null: false,
foreign_key: { on_delete: :cascade },
index: true
t.timestamps_with_timezone null: false
t.text :provider, limit: 255, null: false
t.text :cn, limit: 255
t.text :filter, limit: 255
end
end
end

View File

@ -1,20 +1,22 @@
# frozen_string_literal: true
class QueueBackfillCiRunnerMachinesPartitionedTable < Gitlab::Database::Migration[2.2]
include Gitlab::Database::PartitioningMigrationHelpers
milestone '17.7'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_ci
MIGRATION = 'BackfillCiRunnerMachinesPartitionedTable'
TABLE_NAME = 'ci_runner_machines'
def up
enqueue_partitioning_data_migration TABLE_NAME, MIGRATION
# no-op: the backfill on this table is causing issues due to a problem in the timing of the PDMs:
#
# 1. db/post_migrate/20241107064635_queue_backfill_ci_runner_machines_partitioned_table.rb
# 2. db/post_migrate/20241211072300_retry_add_fk_from_partitioned_ci_runner_managers_to_partitioned_ci_runners.rb
# 3. db/post_migrate/20241219100359_requeue_backfill_ci_runners_partitioned_table.rb
#
# Migration #2 installs a FK check before the target table (ci_runners_e59bb2812d) is backfilled (#3), so we
# have https://gitlab.com/gitlab-org/gitlab/-/issues/520092. We can afford to not backfill
# ci_runner_machines_687967fa8a, since it'll be populated with runner data automatically as they request jobs from
# the GitLab instance.
end
def down
cleanup_partitioning_data_migration TABLE_NAME, MIGRATION
# no-op
end
end

View File

@ -6,25 +6,14 @@ class RequeueDeleteOrphanedPartitionedCiRunnerMachineRecords < Gitlab::Database:
restrict_gitlab_migration gitlab_schema: :gitlab_ci
MIGRATION = "RequeueDeleteOrphanedPartitionedCiRunnerMachineRecords"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 1000
SUB_BATCH_SIZE = 100
def up
delete_batched_background_migration(MIGRATION, :ci_runner_machines_687967fa8a, :runner_id, [])
queue_batched_background_migration(
MIGRATION,
:ci_runner_machines_687967fa8a,
:runner_id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
batch_class_name: 'LooseIndexScanBatchingStrategy',
sub_batch_size: SUB_BATCH_SIZE
)
# no-oped and deleted in 20250307070000_delete20250113164152_post_deployment_migration.rb
# since this can collide with 20250307080000_replace_ci_runners_machines_with_partitioned_table which
# is scheduled for 17.10 (no required stop between them)
end
def down
delete_batched_background_migration(MIGRATION, :ci_runner_machines_687967fa8a, :runner_id, [])
# no-op
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class CleanupDeleteOrphanedPartitionedCiRunnerMachineRecords < Gitlab::Database::Migration[2.2]
MIGRATION = "DeleteOrphanedPartitionedCiRunnerMachineRecords"
restrict_gitlab_migration gitlab_schema: :gitlab_ci
milestone '17.11'
def up
delete_batched_background_migration(MIGRATION, :ci_runner_machines_687967fa8a, :runner_id, [])
end
def down; end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class CleanupRequeueDeleteOrphanedPartitionedCiRunnerMachineRecords < Gitlab::Database::Migration[2.2]
MIGRATION = "RequeueDeleteOrphanedPartitionedCiRunnerMachineRecords"
restrict_gitlab_migration gitlab_schema: :gitlab_ci
milestone '17.11'
def up
delete_batched_background_migration(MIGRATION, :ci_runner_machines_687967fa8a, :runner_id, [])
end
def down; end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class RemoveProjectsPoolRepositoriesSourceProjectIdFk < Gitlab::Database::Migration[2.2]
milestone '17.11'
disable_ddl_transaction!
FOREIGN_KEY_NAME = "fk_rails_d2711daad4"
def up
with_lock_retries do
remove_foreign_key_if_exists(:pool_repositories, :projects,
name: FOREIGN_KEY_NAME, reverse_lock_order: true)
end
end
def down
add_concurrent_foreign_key(:pool_repositories, :projects,
name: FOREIGN_KEY_NAME, column: :source_project_id,
target_column: :id, on_delete: :nullify)
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class RemoveNamespacesGroupWikiRepositoryStatesGroupIdFk < Gitlab::Database::Migration[2.2]
milestone '17.11'
disable_ddl_transaction!
FOREIGN_KEY_NAME = "fk_621768bf3d"
def up
with_lock_retries do
remove_foreign_key_if_exists(:group_wiki_repository_states, :namespaces,
name: FOREIGN_KEY_NAME, reverse_lock_order: true)
end
end
def down
add_concurrent_foreign_key(:group_wiki_repository_states, :namespaces,
name: FOREIGN_KEY_NAME, column: :group_id,
target_column: :id, on_delete: :cascade)
end
end

View File

@ -0,0 +1 @@
d699d5db28d0608cf8e188535bd7cbd018c86a7bceb4003f837f670f1c623355

View File

@ -0,0 +1 @@
d4b2ed6c440ba0628f7265cbc97608280746d58bf97a8b1ec465b6596eb015a5

View File

@ -0,0 +1 @@
0d1d9ea3fa696476b184971cb743b4d1e899d91785a02bb345fe9f3be6cafab1

View File

@ -0,0 +1 @@
ac3bbb4babe4fd1b9e0146d6f2837d34e42b456042f2f335d8af7944bf08c747

View File

@ -0,0 +1 @@
940293f8a96e07bcc04f8353b1cd28920478920e601d65fd15452974eae95e03

View File

@ -16454,6 +16454,28 @@ CREATE SEQUENCE labels_id_seq
ALTER SEQUENCE labels_id_seq OWNED BY labels.id;
CREATE TABLE ldap_admin_role_links (
id bigint NOT NULL,
member_role_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
provider text NOT NULL,
cn text,
filter text,
CONSTRAINT check_7f4c5b8292 CHECK ((char_length(filter) <= 255)),
CONSTRAINT check_db3fe65cb5 CHECK ((char_length(cn) <= 255)),
CONSTRAINT check_f2efc15b43 CHECK ((char_length(provider) <= 255))
);
CREATE SEQUENCE ldap_admin_role_links_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE ldap_admin_role_links_id_seq OWNED BY ldap_admin_role_links.id;
CREATE TABLE ldap_group_links (
id bigint NOT NULL,
cn character varying,
@ -26989,6 +27011,8 @@ ALTER TABLE ONLY label_priorities ALTER COLUMN id SET DEFAULT nextval('label_pri
ALTER TABLE ONLY labels ALTER COLUMN id SET DEFAULT nextval('labels_id_seq'::regclass);
ALTER TABLE ONLY ldap_admin_role_links ALTER COLUMN id SET DEFAULT nextval('ldap_admin_role_links_id_seq'::regclass);
ALTER TABLE ONLY ldap_group_links ALTER COLUMN id SET DEFAULT nextval('ldap_group_links_id_seq'::regclass);
ALTER TABLE ONLY lfs_file_locks ALTER COLUMN id SET DEFAULT nextval('lfs_file_locks_id_seq'::regclass);
@ -29545,6 +29569,9 @@ ALTER TABLE ONLY label_priorities
ALTER TABLE ONLY labels
ADD CONSTRAINT labels_pkey PRIMARY KEY (id);
ALTER TABLE ONLY ldap_admin_role_links
ADD CONSTRAINT ldap_admin_role_links_pkey PRIMARY KEY (id);
ALTER TABLE ONLY ldap_group_links
ADD CONSTRAINT ldap_group_links_pkey PRIMARY KEY (id);
@ -35196,6 +35223,8 @@ CREATE INDEX index_labels_on_type_and_project_id ON labels USING btree (type, pr
CREATE INDEX index_last_usages_on_last_used_date ON catalog_resource_component_last_usages USING btree (last_used_date);
CREATE INDEX index_ldap_admin_role_links_on_member_role_id ON ldap_admin_role_links USING btree (member_role_id);
CREATE INDEX index_ldap_group_links_on_member_role_id ON ldap_group_links USING btree (member_role_id);
CREATE UNIQUE INDEX index_lfs_file_locks_on_project_id_and_path ON lfs_file_locks USING btree (project_id, path);
@ -42044,9 +42073,6 @@ ALTER TABLE ONLY dast_profile_schedules
ALTER TABLE ONLY events
ADD CONSTRAINT fk_61fbf6ca48 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY group_wiki_repository_states
ADD CONSTRAINT fk_621768bf3d FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY vulnerability_reads
ADD CONSTRAINT fk_62736f638f FOREIGN KEY (vulnerability_id) REFERENCES vulnerabilities(id) ON DELETE CASCADE;
@ -44078,6 +44104,9 @@ ALTER TABLE ONLY ci_running_builds
ALTER TABLE ONLY merge_request_approval_metrics
ADD CONSTRAINT fk_rails_5cb1ca73f8 FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
ALTER TABLE ONLY ldap_admin_role_links
ADD CONSTRAINT fk_rails_5d208a83e7 FOREIGN KEY (member_role_id) REFERENCES member_roles(id) ON DELETE CASCADE;
ALTER TABLE p_ci_stages
ADD CONSTRAINT fk_rails_5d4d96d44b_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
@ -45002,9 +45031,6 @@ ALTER TABLE ONLY cluster_agent_tokens
ALTER TABLE ONLY requirements_management_test_reports
ADD CONSTRAINT fk_rails_d1e8b498bf FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY pool_repositories
ADD CONSTRAINT fk_rails_d2711daad4 FOREIGN KEY (source_project_id) REFERENCES projects(id) ON DELETE SET NULL;
ALTER TABLE ONLY design_management_repository_states
ADD CONSTRAINT fk_rails_d2a258cc5a FOREIGN KEY (design_management_repository_id) REFERENCES design_management_repositories(id) ON DELETE CASCADE;

View File

@ -43,6 +43,12 @@ To configure your GitLab instance to access the available self-hosted models in
1. Under **Local AI Gateway URL**, enter your AI Gateway URL.
1. Select **Save changes**.
{{< alert type="note" >}}
If your AI gateway URL points to a local network or private IP address (for example, `172.31.x.x` or internal hostnames like `ip-172-xx-xx-xx.region.compute.internal`), GitLab might block the request for security reasons. To allow requests to this address, [add the address to the IP allowlist](../../security/webhooks.md#allow-outbound-requests-to-certain-ip-addresses-and-domains).
{{< /alert >}}
## Configure the self-hosted model
Prerequisites:
@ -144,7 +150,7 @@ When configuring GitLab Duo Chat sub-features, if you do not select a specific s
To disable a feature, you must explicitly select **Disabled** when configuring a feature or sub-feature.
- Not choosing a model for a sub-feature is insufficient.
- Not choosing a model for a sub-feature is insufficient.
- For Chat sub-features, not selecting a model causes that sub-feature to [fall back to using the model configured for **General Chat**](#gitlab-duo-chat-sub-feature-fall-back-configuration).
To disable a GitLab Duo feature or sub-feature:

View File

@ -128,6 +128,26 @@ Examples:
--tokenizer <path-to-model>/Mixtral-8x7B-Instruct-v0.1
```
#### Disable request logging to reduce latency
When running vLLM in production, you can significantly reduce latency by using the `--disable-log-requests` flag to disable request logging.
{{< alert type="note" >}}
Use this flag only when you do not need detailed request logging.
{{< /alert >}}
Disabling request logging minimizes the overhead introduced by verbose logs, especially under high load, and can help improve performance levels.
```shell
vllm serve <path-to-model>/<model-version> \
--served_model_name <choose-a-name-for-the-model> \
--disable-log-requests
```
This change has been observed to notably improve response times in internal benchmarks.
## For cloud-hosted model deployments
1. [AWS Bedrock](https://aws.amazon.com/bedrock/).

View File

@ -114,7 +114,7 @@ By default, GitLab Self-Managed instances do not collect event data through Snow
gdk restart
```
1. You can now see all events being sent by your local instance in the [Snowplow Micro UI](http://localhost:9091/micro/ui) and can filter for specific events.
1. You can now see all events being sent by your local instance in the Snowplow Micro UI and can filter for specific events. Snowplow Micro UI can be found under the `/micro/ui` path, for example `http://localhost:9092/micro/ui`.
### Introduction to Snowplow Micro UI and API

View File

@ -76,7 +76,7 @@ To be eligible for the GitLab Open Source Program, projects must be publicly vis
1. Select the **Users can request access** checkbox.
1. Take a screenshot of this view. Include as much of the publicly visible settings as possible. Make sure to include your project's name in the upper-left of the screenshot.
![Publicly visible setting](img/publicly-visible_v14_0.png)
![Project settings page showing enabled visibility settings for a public project](img/publicly-visible_v14_0.png)
{{< alert type="note" >}}

View File

@ -2,13 +2,44 @@
stage: Growth
group: Acquisition
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
title: Read-only projects
title: Read-only namespaces and projects
---
## Read-only namespaces
{{< details >}}
- Tier: Free
- Offering: GitLab.com
{{< /details >}}
A namespace is placed in a read-only state when it exceeds the [free user limit](free_user_limit.md), and when the namespace visibility is private.
To remove the read-only state of a namespace and its projects, you can:
- [Reduce the number of members](free_user_limit.md#manage-members-in-your-group-namespace) in your namespace.
- [Start a free trial](https://gitlab.com/-/trial_registrations/new), which includes an unlimited number of members.
- [Purchase a paid tier](https://about.gitlab.com/pricing/).
### Restricted actions
When a namespace is in a read-only state, you cannot execute the actions listed in the following table.
If you try to execute a restricted action, you might get a `404` error.
| Feature | Action restricted |
|---------|-------------------|
| Container registry | Create, edit, and delete cleanup policies. <br> Push an image to the container registry. |
| Merge requests | Create and update a merge request. |
| Package registry | Publish a package. |
| CI/CD | Create, edit, administer, and run pipelines. <br> Create, edit, administer, and run builds. <br> Create and edit admin environments. <br> Create and edit admin deployments. <br> Create and edit admin clusters. <br> Create and edit admin releases. |
| Namespaces | **For exceeded free user limits:** Invite new users. |
## Read-only projects
{{< details >}}
- Tier: Free, Premium, Ultimate
- Offering: GitLab.com
{{< /details >}}
@ -17,38 +48,7 @@ A project is placed in a read-only state when it exceeds the allocated storage l
- Free tier, when any project in the namespace is over the [free limit](storage_usage_quotas.md#free-limit).
- Premium and Ultimate tiers, when any project in the namespace is over the [fixed project limit](storage_usage_quotas.md#fixed-project-limit).
### Restricted actions
When a project is read-only due to storage limits, you can't push or add large files (LFS) to the project's repository.
A banner at the top of the project or namespace page indicates the read-only status.
## Read-only namespaces
{{< details >}}
- Tier: Free
{{< /details >}}
On the Free tier, a namespace is placed in a read-only state when it exceeds the [free user limit](free_user_limit.md), when the namespace visibility is private.
To remove the read-only state of a namespace and its projects, you can:
- [Reduce the number of members](free_user_limit.md#manage-members-in-your-group-namespace) in your namespace.
- [Start a free trial](https://gitlab.com/-/trial_registrations/new), which includes an unlimited number of members.
- [Purchase a paid tier](https://about.gitlab.com/pricing/).
## Restricted actions
| Feature | Action restricted |
|---------|-------------------|
| Container registry | Create, edit, and delete cleanup policies <br> Push an image to the container registry |
| Merge Requests | Create and update an MR |
| Package registry | Publish a package |
| Repositories | Add tags <br> Create new branches <br> Create and update commit status <br> Push and force push to non-protected branches <br> Push and force push to protected branches <br> Upload files <br> Create merge requests |
| CI/CD | Create, edit, admin, and run pipelines <br> Create, edit, admin, and run builds <br> Create and edit admin environments <br> Create and edit admin deployments <br> Create and edit admin clusters <br> Create and edit admin releases |
| Namespaces | **For exceeded free user limits:** Invite new users |
When you try to execute a restricted action in a read-only namespace, you might get a `404` error.
## Related topics
- [Free user limit](free_user_limit.md)

View File

@ -128,7 +128,8 @@ However, the query matches all possible variations of the string (for example, `
{{< history >}}
- Showing only users from authorized projects and groups [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/442091) in GitLab 17.10 [with flags](../../administration/feature_flags.md) named `users_search_scoped_to_authorized_namespaces_advanced_search` and `users_search_scoped_to_authorized_namespaces_basic_search`. Disabled by default.
- Showing only users from authorized projects and groups [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/442091) in GitLab 17.10 [with flags](../../administration/feature_flags.md) named `users_search_scoped_to_authorized_namespaces_advanced_search`, `users_search_scoped_to_authorized_namespaces_basic_search`, and `users_search_scoped_to_authorized_namespaces_basic_search_by_ids`. Disabled by default.
- [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/185577) in GitLab 17.11. Feature flags `users_search_scoped_to_authorized_namespaces_advanced_search`, `users_search_scoped_to_authorized_namespaces_basic_search`, and `users_search_scoped_to_authorized_namespaces_basic_search_by_ids` removed.
{{< /history >}}

View File

@ -1,19 +0,0 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Background migration to copy only valid data from ci_runner_machines to its corresponding partitioned table
# rubocop: disable Migration/BatchedMigrationBaseClass -- This is indirectly deriving from the correct base class
class BackfillCiRunnerMachinesPartitionedTable < BackfillPartitionedTable
extend ::Gitlab::Utils::Override
private
override :filter_sub_batch_content
def filter_sub_batch_content(relation)
relation.where(runner_type: 1).or(relation.where.not(sharding_key_id: nil))
end
end
# rubocop: enable Migration/BatchedMigrationBaseClass
end
end

View File

@ -1,12 +0,0 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# rubocop: disable Migration/BatchedMigrationBaseClass -- DeleteOrphanedPartitionedCiRunnerMachineRecords inherits from BatchedMigrationJob.
class RequeueDeleteOrphanedPartitionedCiRunnerMachineRecords < DeleteOrphanedPartitionedCiRunnerMachineRecords
operation_name :requeue_delete_orphaned_partitioned_ci_runner_machine_records
feature_category :fleet_visibility
end
# rubocop: enable Migration/BatchedMigrationBaseClass
end
end

View File

@ -112,12 +112,8 @@ module Gitlab
params = { search: query, use_minimum_char_limit: false }
if current_user && filters[:autocomplete]
if Feature.enabled?(:users_search_scoped_to_authorized_namespaces_basic_search, current_user)
params[:by_membership] = true
elsif Feature.enabled?(:users_search_scoped_to_authorized_namespaces_basic_search_by_ids, current_user)
params[:group_member_source_ids] = current_user_authorized_group_ids
params[:project_member_source_ids] = current_user_authorized_project_ids
end
params[:group_member_source_ids] = current_user_authorized_group_ids
params[:project_member_source_ids] = current_user_authorized_project_ids
end
UsersFinder.new(current_user, params).execute

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
module Gitlab
module Usage
module Metrics
module Instrumentations
class GroupRunnerTokenExpirationEnabledMetric < GenericMetric
def value
return true if Gitlab::CurrentSettings.group_runner_token_expiration_interval&.positive?
expiration_interval = NamespaceSetting.arel_table[:subgroup_runner_token_expiration_interval]
NamespaceSetting.where(expiration_interval.gt(0)).exists?
end
end
end
end
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module Gitlab
module Usage
module Metrics
module Instrumentations
class InstanceRunnerTokenExpirationEnabledMetric < GenericMetric
def value
!!Gitlab::CurrentSettings.runner_token_expiration_interval&.positive?
end
end
end
end
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
module Gitlab
module Usage
module Metrics
module Instrumentations
class ProjectRunnerTokenExpirationEnabledMetric < GenericMetric
def value
return true if Gitlab::CurrentSettings.project_runner_token_expiration_interval&.positive?
NamespaceSetting.where(NamespaceSetting.arel_table[:project_runner_token_expiration_interval].gt(0)).exists?
end
end
end
end
end
end

View File

@ -34,6 +34,16 @@ module Gitlab
].freeze
MIGRATED_INTEGRATIONS = %w[
amazon_q
apple_app_store
asana
assembla
bamboo
beyond_identity
bugzilla
buildkite
campfire
clickup
pivotaltracker
slack
slack_slash_commands

View File

@ -6254,9 +6254,6 @@ msgstr ""
msgid "Amazon Q"
msgstr ""
msgid "AmazonQ|:hourglass_flowing_sand: I'm creating unit tests for the selected lines of code. I'll update this comment when I'm done."
msgstr ""
msgid "AmazonQ|Active cloud connector token not found."
msgstr ""
@ -6362,6 +6359,12 @@ msgstr ""
msgid "AmazonQ|I understand that by selecting Save changes, GitLab creates a service account for Amazon Q and sends its credentials to AWS. Use of the Amazon Q Developer capabilities as part of GitLab Duo with Amazon Q is governed by the %{helpStart}AWS Customer Agreement%{helpEnd} or other written agreement between you and AWS governing your use of AWS services. Amazon Q Developer processes data across all US Regions and makes cross-region API calls when your requests require it."
msgstr ""
msgid "AmazonQ|I'm creating unit tests for the selected lines of code. I'll update this comment and this merge request when I'm done."
msgstr ""
msgid "AmazonQ|I'm creating unit tests for this merge request. I'll update this comment when I'm done."
msgstr ""
msgid "AmazonQ|I'm generating a fix for this review finding. I'll update this comment when I'm done."
msgstr ""

View File

@ -1,6 +1,5 @@
# Lines beginning with a hash are ignored, like this one.
# Do not add new specs to this file.
ee/spec/frontend/admin/application_settings/general/components/license_dropzone_spec.js
ee/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js
ee/spec/frontend/admin/subscriptions/show/components/subscription_breakdown_spec.js
ee/spec/frontend/ai/settings/components/duo_experiment_beta_features_form_spec.js
@ -61,7 +60,6 @@ ee/spec/frontend/roles_and_permissions/components/role_selector_spec.js
ee/spec/frontend/security_configuration/components/app_spec.js
ee/spec/frontend/security_configuration/components/dynamic_fields_spec.js
ee/spec/frontend/security_configuration/dast_profiles/components/dast_profiles_list_spec.js
ee/spec/frontend/security_dashboard/components/shared/vulnerability_details_graphql/details_section_spec.js
ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_graphql_spec.js
ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_report_spec.js
ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_report_tabs_spec.js

View File

@ -17,6 +17,7 @@ class SemgrepResultProcessor
MESSAGE_SCG_PING_APPSEC = "#{APPSEC_HANDLE} please review this finding, which is a potential violation of [GitLab's secure coding guidelines](https://docs.gitlab.com/development/secure_coding_guidelines/).".freeze
MESSAGE_S1_PING_APPSEC = "#{APPSEC_HANDLE} please review this finding. This MR potentially reintroduces code from a past S1 issue.".freeze
MESSAGE_PING_APPSEC = "#{APPSEC_HANDLE} please review this finding.".freeze
MESSAGE_FOOTER = <<~FOOTER
@ -142,7 +143,7 @@ class SemgrepResultProcessor
elsif check_id&.start_with?("builds.sast-custom-rules.s1")
"\n#{MESSAGE_S1_PING_APPSEC}"
else
""
"\n#{MESSAGE_PING_APPSEC}"
end
message_from_bot = "#{message_header}\n#{message}#{suffix}\n#{MESSAGE_FOOTER}"

View File

@ -127,7 +127,7 @@ RSpec.describe 'Project issue boards', :js, feature_category: :portfolio_managem
expect(page).to have_selector('.board', count: 3)
end
it 'infinite scrolls list' do
it 'infinite scrolls list', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/525518' do
# Use small height to avoid automatic loading via GlIntersectionObserver
page.driver.browser.manage.window.resize_to(400, 400)

View File

@ -656,32 +656,121 @@ describe('init markdown', () => {
});
expect(textArea.value).toEqual(text);
expect(textArea.selectionStart).toBe(text.length);
},
);
});
it.each([{ prefix: '> ' }, { prefix: ' >' }, { prefix: '>' }])(
"removes quote tag from selection with prefix '$prefix'",
({ prefix }) => {
const tag = '> ';
const initialValue = `${prefix}${text}`;
textArea.value = initialValue;
textArea.setSelectionRange(0, initialValue.length);
describe('preserves selection', () => {
it.each([{ tag: '**' }, { tag: '_' }, { tag: '~~' }, { tag: '`' }])(
"when removing '$tag' with selection containing the tag",
({ tag }) => {
const initialValue = `${tag}${text}${tag}`;
textArea.value = initialValue;
textArea.setSelectionRange(0, initialValue.length);
insertMarkdownText({
textArea,
text: textArea.value,
tag,
blockTag: null,
selected: initialValue,
wrap: true,
});
expect(textArea.value).toEqual(text);
expect(textArea.selectionStart).toBe(0);
expect(textArea.selectionEnd).toBe(text.length);
},
);
it.each([{ tag: '**' }, { tag: '_' }, { tag: '~~' }])(
"when removing '$tag' with selection inside the tag",
({ tag }) => {
const initialValue = `${tag}${text}${tag}`;
textArea.value = initialValue;
textArea.setSelectionRange(tag.length, initialValue.length - tag.length);
insertMarkdownText({
textArea,
text: textArea.value,
tag,
blockTag: null,
selected: text,
wrap: true,
});
expect(textArea.value).toEqual(text);
expect(textArea.selectionStart).toBe(0);
expect(textArea.selectionEnd).toBe(text.length);
},
);
it.each([{ tag: '**' }, { tag: '_' }, { tag: '~~' }, { tag: '`' }])(
"with created tags when adding '$tag'",
({ tag }) => {
textArea.value = text;
textArea.setSelectionRange(0, text.length);
insertMarkdownText({
textArea,
text: textArea.value,
tag,
blockTag: null,
selected: text,
wrap: true,
});
const expectedValue = `${tag}${text}${tag}`;
expect(textArea.value).toEqual(expectedValue);
expect(textArea.selectionStart).toBe(0);
expect(textArea.selectionEnd).toBe(expectedValue.length);
},
);
});
describe('removes selection and moves cursor to end of selection', () => {
it.each([{ prefix: '> ' }, { prefix: ' >' }, { prefix: '>' }])(
"when removing selection with prefix '$prefix'",
({ prefix }) => {
const tag = '> ';
const initialValue = `${prefix}${text}`;
textArea.value = initialValue;
textArea.setSelectionRange(0, initialValue.length);
insertMarkdownText({
textArea,
text: textArea.value,
tag,
blockTag: null,
selected: initialValue,
wrap: false,
});
expect(textArea.value).toEqual(text);
expect(textArea.selectionStart).toBe(text.length);
expect(textArea.selectionEnd).toBe(text.length);
},
);
it.each([{ tag: '> ' }])("when adding '$tag'", ({ tag }) => {
textArea.value = text;
textArea.setSelectionRange(0, text.length);
insertMarkdownText({
textArea,
text: textArea.value,
tag,
blockTag: null,
selected: initialValue,
selected: text,
wrap: false,
});
expect(textArea.value).toEqual(text);
expect(textArea.selectionStart).toBe(text.length);
},
);
const expectedValue = `${tag}${text}`;
expect(textArea.value).toEqual(expectedValue);
expect(textArea.selectionStart).toBe(expectedValue.length);
expect(textArea.selectionEnd).toBe(expectedValue.length);
});
});
it('replaces the placeholder in the tag', () => {
insertMarkdownText({

View File

@ -281,6 +281,18 @@ describe('Blob controls component', () => {
await createComponent({ glFeatures: { blobOverflowMenu: true } });
});
describe('Find file button', () => {
it('does not render on mobile layout', () => {
expect(findFindButton().classes()).toContain('gl-hidden', 'sm:gl-inline-flex');
});
});
describe('Blame button', () => {
it('does not render on mobile layout', () => {
expect(findBlameButton().classes()).toContain('gl-hidden', 'sm:gl-inline-flex');
});
});
describe('WebIdeLink component', () => {
it('renders the WebIdeLink component with the correct props', () => {
expect(findWebIdeLink().props()).toMatchObject({

View File

@ -9,7 +9,7 @@ import { createAlert } from '~/alert';
import projectInfoQuery from 'ee_else_ce/repository/queries/project_info.query.graphql';
import BlobOverflowMenu from '~/repository/components/header_area/blob_overflow_menu.vue';
import BlobDefaultActionsGroup from '~/repository/components/header_area/blob_default_actions_group.vue';
import PermalinkDropdownItem from '~/repository/components/header_area/permalink_dropdown_item.vue';
import BlobRepositoryActionsGroup from '~/repository/components/header_area/blob_repository_actions_group.vue';
import BlobButtonGroup from 'ee_else_ce/repository/components/header_area/blob_button_group.vue';
import BlobDeleteFileGroup from '~/repository/components/header_area/blob_delete_file_group.vue';
import createRouter from '~/repository/router';
@ -66,7 +66,7 @@ describe('Blob Overflow Menu', () => {
const findBlobActionsDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
const findBlobDefaultActionsGroup = () => wrapper.findComponent(BlobDefaultActionsGroup);
const findBlobButtonGroup = () => wrapper.findComponent(BlobButtonGroup);
const findPermalinkDropdownItem = () => wrapper.findComponent(PermalinkDropdownItem);
const findBlobRepositoryActionsGroup = () => wrapper.findComponent(BlobRepositoryActionsGroup);
const findBlobDeleteFileGroup = () => wrapper.findComponent(BlobDeleteFileGroup);
beforeEach(async () => {
@ -173,9 +173,9 @@ describe('Blob Overflow Menu', () => {
});
});
describe('Permalink Dropdown Item', () => {
describe('Blob Repository Actions Group', () => {
it('renders component', () => {
expect(findPermalinkDropdownItem().exists()).toBe(true);
expect(findBlobRepositoryActionsGroup().exists()).toBe(true);
});
});
});

View File

@ -1,11 +1,18 @@
import { nextTick } from 'vue';
import { GlDisclosureDropdownGroup } from '@gitlab/ui';
import PermalinkDropdownItem from '~/repository/components/header_area/permalink_dropdown_item.vue';
import { keysFor, PROJECT_FILES_GO_TO_PERMALINK } from '~/behaviors/shortcuts/keybindings';
import BlobRepositoryActionsGroup from '~/repository/components/header_area/blob_repository_actions_group.vue';
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
import Shortcuts from '~/behaviors/shortcuts/shortcuts';
import {
keysFor,
PROJECT_FILES_GO_TO_PERMALINK,
START_SEARCH_PROJECT_FILE,
} from '~/behaviors/shortcuts/keybindings';
import { shouldDisableShortcuts } from '~/behaviors/shortcuts/shortcuts_toggle';
import { Mousetrap } from '~/lib/mousetrap';
import { lineState } from '~/blob/state';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { blobControlsDataMock } from 'ee_else_ce_jest/repository/mock_data';
jest.mock('~/behaviors/shortcuts/shortcuts_toggle');
jest.mock('~/blob/state');
@ -13,18 +20,21 @@ jest.mock('~/blob/state');
const relativePermalinkPath =
'flightjs/Flight/-/blob/46ca9ebd5a43ec240ee8d64e2bb829169dff744e/bower.json';
describe('PermalinkDropdownItem', () => {
describe('BlobRepositoryActionsGroup', () => {
let wrapper;
const $toast = {
show: jest.fn(),
};
const createComponent = (props = {}) => {
wrapper = shallowMountExtended(PermalinkDropdownItem, {
const createComponent = (provided = {}) => {
wrapper = shallowMountExtended(BlobRepositoryActionsGroup, {
propsData: {
permalinkPath: relativePermalinkPath,
...props,
},
provide: {
blobInfo: blobControlsDataMock.repository.blobs.nodes[0],
...provided,
},
stubs: {
GlDisclosureDropdownGroup,
@ -34,8 +44,10 @@ describe('PermalinkDropdownItem', () => {
};
const findDropdownGroup = () => wrapper.findComponent(GlDisclosureDropdownGroup);
const findFindFileDropdownItem = () => wrapper.findByTestId('find');
const findBlameDropdownItem = () => wrapper.findByTestId('blame-dropdown-item');
const findPermalinkLinkDropdown = () => wrapper.findByTestId('permalink');
const { bindInternalEventDocument } = useMockInternalEventsTracking();
beforeEach(() => {
lineState.currentLineNumber = null;
@ -44,9 +56,51 @@ describe('PermalinkDropdownItem', () => {
it('renders correctly', () => {
expect(findDropdownGroup().exists()).toBe(true);
expect(findFindFileDropdownItem().exists()).toBe(true);
expect(findBlameDropdownItem().exists()).toBe(true);
expect(findPermalinkLinkDropdown().exists()).toBe(true);
});
describe('Find file dropdown item', () => {
it('renders only on mobile layout', () => {
expect(findFindFileDropdownItem().classes()).toContain('sm:gl-hidden');
});
it('triggers a `focusSearchFile` shortcut when the findFile button is clicked', () => {
jest.spyOn(Shortcuts, 'focusSearchFile').mockResolvedValue();
findFindFileDropdownItem().vm.$emit('action');
expect(Shortcuts.focusSearchFile).toHaveBeenCalled();
});
it('emits a tracking event when the Find file button is clicked', () => {
const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
jest.spyOn(Shortcuts, 'focusSearchFile').mockResolvedValue();
findFindFileDropdownItem().vm.$emit('action');
expect(trackEventSpy).toHaveBeenCalledWith('click_find_file_button_on_repository_pages');
});
});
describe('Blame dropdown item', () => {
it('renders only on mobile layout', () => {
expect(findBlameDropdownItem().classes()).toContain('sm:gl-hidden');
});
it('does not render for lfs files', () => {
createComponent({
blobInfo: {
...blobControlsDataMock.repository.blobs.nodes[0],
storedExternally: true,
externalStorage: 'lfs',
},
});
expect(findBlameDropdownItem().exists()).toBe(false);
});
});
describe('updatedPermalinkPath', () => {
it('returns absolutePermalinkPath when no line number is set', () => {
expect(findPermalinkLinkDropdown().attributes('data-clipboard-text')).toBe(
@ -100,13 +154,20 @@ describe('PermalinkDropdownItem', () => {
it('displays the shortcut key when shortcuts are not disabled', () => {
shouldDisableShortcuts.mockReturnValue(false);
createComponent();
expect(wrapper.find('kbd').exists()).toBe(true);
expect(wrapper.find('kbd').text()).toBe(keysFor(PROJECT_FILES_GO_TO_PERMALINK)[0]);
expect(findFindFileDropdownItem().find('kbd').exists()).toBe(true);
expect(findFindFileDropdownItem().find('kbd').text()).toBe(
keysFor(START_SEARCH_PROJECT_FILE)[0],
);
expect(findPermalinkLinkDropdown().find('kbd').exists()).toBe(true);
expect(findPermalinkLinkDropdown().find('kbd').text()).toBe(
keysFor(PROJECT_FILES_GO_TO_PERMALINK)[0],
);
});
it('does not display the shortcut key when shortcuts are disabled', () => {
shouldDisableShortcuts.mockReturnValue(true);
createComponent();
expect(wrapper.find('kbd').exists()).toBe(false);
expect(findFindFileDropdownItem().find('kbd').exists()).toBe(false);
expect(findPermalinkLinkDropdown().find('kbd').exists()).toBe(false);
});
});

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