Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0102b53b44
commit
307115302b
|
|
@ -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 }) {
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 }),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ class ApplicationRecord < ActiveRecord::Base
|
|||
include ResetOnColumnErrors
|
||||
include HasCheckConstraints
|
||||
include IgnorableColumns
|
||||
include PopulatesShardingKey
|
||||
|
||||
self.abstract_class = true
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveProjectIntegrationsMetric
|
||||
options:
|
||||
type: asana
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveGroupIntegrationsMetric
|
||||
options:
|
||||
type: asana
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveInstanceIntegrationsMetric
|
||||
options:
|
||||
type: asana
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveProjectsInheritingIntegrationsMetric
|
||||
options:
|
||||
type: asana
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveGroupsInheritingIntegrationsMetric
|
||||
options:
|
||||
type: asana
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveProjectIntegrationsMetric
|
||||
options:
|
||||
type: assembla
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveGroupIntegrationsMetric
|
||||
options:
|
||||
type: assembla
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveInstanceIntegrationsMetric
|
||||
options:
|
||||
type: assembla
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveProjectsInheritingIntegrationsMetric
|
||||
options:
|
||||
type: assembla
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveGroupsInheritingIntegrationsMetric
|
||||
options:
|
||||
type: assembla
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveProjectIntegrationsMetric
|
||||
options:
|
||||
type: bamboo
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveGroupIntegrationsMetric
|
||||
options:
|
||||
type: bamboo
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveInstanceIntegrationsMetric
|
||||
options:
|
||||
type: bamboo
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveProjectsInheritingIntegrationsMetric
|
||||
options:
|
||||
type: bamboo
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveGroupsInheritingIntegrationsMetric
|
||||
options:
|
||||
type: bamboo
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveProjectIntegrationsMetric
|
||||
options:
|
||||
type: bugzilla
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveGroupIntegrationsMetric
|
||||
options:
|
||||
type: bugzilla
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveInstanceIntegrationsMetric
|
||||
options:
|
||||
type: bugzilla
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveProjectsInheritingIntegrationsMetric
|
||||
options:
|
||||
type: bugzilla
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveGroupsInheritingIntegrationsMetric
|
||||
options:
|
||||
type: bugzilla
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveProjectIntegrationsMetric
|
||||
options:
|
||||
type: buildkite
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveGroupIntegrationsMetric
|
||||
options:
|
||||
type: buildkite
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveInstanceIntegrationsMetric
|
||||
options:
|
||||
type: buildkite
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveProjectsInheritingIntegrationsMetric
|
||||
options:
|
||||
type: buildkite
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveGroupsInheritingIntegrationsMetric
|
||||
options:
|
||||
type: buildkite
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveProjectIntegrationsMetric
|
||||
options:
|
||||
type: campfire
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveGroupIntegrationsMetric
|
||||
options:
|
||||
type: campfire
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveInstanceIntegrationsMetric
|
||||
options:
|
||||
type: campfire
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveProjectsInheritingIntegrationsMetric
|
||||
options:
|
||||
type: campfire
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: ActiveGroupsInheritingIntegrationsMetric
|
||||
options:
|
||||
type: campfire
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
d699d5db28d0608cf8e188535bd7cbd018c86a7bceb4003f837f670f1c623355
|
||||
|
|
@ -0,0 +1 @@
|
|||
d4b2ed6c440ba0628f7265cbc97608280746d58bf97a8b1ec465b6596eb015a5
|
||||
|
|
@ -0,0 +1 @@
|
|||
0d1d9ea3fa696476b184971cb743b4d1e899d91785a02bb345fe9f3be6cafab1
|
||||
|
|
@ -0,0 +1 @@
|
|||
ac3bbb4babe4fd1b9e0146d6f2837d34e42b456042f2f335d8af7944bf08c747
|
||||
|
|
@ -0,0 +1 @@
|
|||
940293f8a96e07bcc04f8353b1cd28920478920e601d65fd15452974eae95e03
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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/).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||

|
||||

|
||||
|
||||
{{< alert type="note" >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue