Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
fad8f90d7a
commit
23985334ba
|
|
@ -240,7 +240,6 @@
|
|||
"WorkItemWidgetDefinition": [
|
||||
"WorkItemWidgetDefinitionAssignees",
|
||||
"WorkItemWidgetDefinitionGeneric",
|
||||
"WorkItemWidgetDefinitionHealthStatus",
|
||||
"WorkItemWidgetDefinitionHierarchy",
|
||||
"WorkItemWidgetDefinitionLabels",
|
||||
"WorkItemWidgetDefinitionWeight"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#import "~/graphql_shared/fragments/author.fragment.graphql"
|
||||
|
||||
query note($id: NoteID!) {
|
||||
query noteForTooltip($id: NoteID!) {
|
||||
note(id: $id) {
|
||||
id
|
||||
author {
|
||||
|
|
|
|||
|
|
@ -818,7 +818,10 @@ export default {
|
|||
this.viewType = ISSUES_LIST_VIEW_KEY;
|
||||
},
|
||||
handleSelectIssuable(issuable) {
|
||||
this.activeIssuable = issuable;
|
||||
this.activeIssuable = {
|
||||
...issuable,
|
||||
fullPath: this.fullPath,
|
||||
};
|
||||
},
|
||||
updateIssuablesCache(workItem) {
|
||||
const client = this.$apollo.provider.clients.defaultClient;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import errorTrackingStore from '~/error_tracking/store';
|
|||
import { apolloProvider } from '~/graphql_shared/issuable_client';
|
||||
import { TYPE_INCIDENT, TYPE_ISSUE } from '~/issues/constants';
|
||||
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { scrollToTargetOnResize } from '~/lib/utils/resize_observer';
|
||||
import initLinkedResources from '~/linked_resources';
|
||||
import IssueApp from './components/app.vue';
|
||||
import DescriptionComponent from './components/description.vue';
|
||||
|
|
@ -73,8 +72,6 @@ export function initIssuableApp(store) {
|
|||
|
||||
bootstrapApollo({ ...issueState, issueType });
|
||||
|
||||
scrollToTargetOnResize();
|
||||
|
||||
if (issueType === TYPE_INCIDENT) {
|
||||
initLinkedResources();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export default {
|
|||
{ id: 0, href: this.switchDashboardPath, text: this.dashboardLinkText },
|
||||
{
|
||||
id: 1,
|
||||
href: 'https://gitlab.com/gitlab-org/gitlab/-/issues/460910',
|
||||
href: 'https://gitlab.com/gitlab-org/gitlab/-/issues/478844',
|
||||
text: __('Provide feedback'),
|
||||
extraAttrs: {
|
||||
target: '__blank',
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ export default {
|
|||
class="gl-relative"
|
||||
data-testid="assigned-user"
|
||||
>
|
||||
<gl-avatar :src="user.avatarUrl" :size="32" />
|
||||
<gl-avatar :src="user.avatarUrl" :size="32" class="!gl-bg-white" />
|
||||
<span
|
||||
v-if="isCurrentUser(user)"
|
||||
class="gl-absolute -gl-left-2 -gl-top-2 gl-flex gl-h-5 gl-w-5 gl-items-center gl-justify-center gl-rounded-full gl-bg-blue-500 gl-p-1 gl-text-white"
|
||||
|
|
|
|||
|
|
@ -90,15 +90,18 @@ export default {
|
|||
},
|
||||
canEdit: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
canAwardEmoji: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
canDelete: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
canResolve: {
|
||||
type: Boolean,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
<script>
|
||||
import { renderGFM } from '~/behaviors/markdown/render_gfm';
|
||||
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { TYPENAME_NOTE } from '~/graphql_shared/constants';
|
||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
import noteQuery from '../graphql/note.query.graphql';
|
||||
import NoteEditedText from './note_edited_text.vue';
|
||||
import NoteableNote from './noteable_note.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NoteEditedText,
|
||||
NoteableNote,
|
||||
},
|
||||
directives: {
|
||||
SafeHtml,
|
||||
},
|
||||
mixins: [timeagoMixin, glFeatureFlagMixin()],
|
||||
props: {
|
||||
noteId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
note: null,
|
||||
hidden: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showNote() {
|
||||
return this.note && !this.hidden && !this.isSyntheticNote;
|
||||
},
|
||||
showEdited() {
|
||||
return this.note && this.note.created_at !== this.note.last_edited_at;
|
||||
},
|
||||
isSyntheticNote() {
|
||||
return Boolean(this.noteId?.match(/([a-f0-9]{40})/));
|
||||
},
|
||||
noteHtml() {
|
||||
return this.note?.body_html;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
async noteHtml() {
|
||||
try {
|
||||
await this.$nextTick();
|
||||
renderGFM(this.$refs.noteBody);
|
||||
} catch {
|
||||
this.fallback();
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.isSyntheticNote) {
|
||||
this.fallback();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fallback() {
|
||||
this.hidden = true;
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
note: {
|
||||
skip() {
|
||||
return !this.noteId || this.isSyntheticNote;
|
||||
},
|
||||
query: noteQuery,
|
||||
variables() {
|
||||
return {
|
||||
id: convertToGraphQLId(TYPENAME_NOTE, this.noteId),
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
return {
|
||||
...data.note,
|
||||
author: {
|
||||
...data.note.author,
|
||||
id: getIdFromGraphQLId(data.note.author.id),
|
||||
},
|
||||
last_edited_by: {
|
||||
...data.note.last_edited_by,
|
||||
id: getIdFromGraphQLId(data.note.last_edited_by?.id),
|
||||
},
|
||||
id: getIdFromGraphQLId(data.note.id),
|
||||
};
|
||||
},
|
||||
result(result) {
|
||||
if (result?.errors?.length > 0) {
|
||||
Sentry.captureException(result.errors[0].message);
|
||||
this.fallback();
|
||||
}
|
||||
|
||||
if (!result?.data?.note) {
|
||||
this.fallback();
|
||||
}
|
||||
},
|
||||
error(error) {
|
||||
Sentry.captureException(error);
|
||||
this.fallback();
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<noteable-note
|
||||
v-if="showNote"
|
||||
:id="`note_${noteId}`"
|
||||
:note="note"
|
||||
:show-reply-button="false"
|
||||
should-scroll-to-note
|
||||
>
|
||||
<template #note-body>
|
||||
<div ref="noteBody" class="note-body">
|
||||
<div v-safe-html:[$options.safeHtmlConfig]="noteHtml" class="note-text md"></div>
|
||||
<note-edited-text
|
||||
v-if="showEdited"
|
||||
:edited-at="note.last_edited_at"
|
||||
:edited-by="note.last_edited_by"
|
||||
:action-text="__('Edited')"
|
||||
class="note_edited_ago"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</noteable-note>
|
||||
</template>
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
<script>
|
||||
import { GlSprintf, GlAvatarLink, GlAvatar } from '@gitlab/ui';
|
||||
import $ from 'jquery';
|
||||
import { escape } from 'lodash';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
||||
import { INLINE_DIFF_LINES_KEY } from '~/diffs/constants';
|
||||
|
|
@ -126,6 +126,9 @@ export default {
|
|||
author() {
|
||||
return this.note.author;
|
||||
},
|
||||
authorId() {
|
||||
return getIdFromGraphQLId(this.author.id);
|
||||
},
|
||||
commentType() {
|
||||
return this.note.internal ? __('internal note') : __('comment');
|
||||
},
|
||||
|
|
@ -136,11 +139,17 @@ export default {
|
|||
'is-requesting being-posted': this.isRequesting,
|
||||
'disabled-content': this.isDeleting,
|
||||
target: this.isTarget,
|
||||
'is-editable': this.note.current_user.can_edit,
|
||||
'is-editable': this.canEdit,
|
||||
};
|
||||
},
|
||||
canAwardEmoji() {
|
||||
return this.note.current_user?.can_award_emoji ?? false;
|
||||
},
|
||||
canEdit() {
|
||||
return this.note.current_user?.can_edit ?? false;
|
||||
},
|
||||
canReportAsAbuse() {
|
||||
return Boolean(this.reportAbusePath) && this.author.id !== this.getUserData.id;
|
||||
return Boolean(this.reportAbusePath) && this.authorId !== this.getUserData.id;
|
||||
},
|
||||
noteAnchorId() {
|
||||
return `note_${this.note.id}`;
|
||||
|
|
@ -176,7 +185,7 @@ export default {
|
|||
if (!this.discussionRoot) return false;
|
||||
if (!this.note.resolvable) return false;
|
||||
|
||||
return this.note.current_user.can_resolve_discussion;
|
||||
return this.note.current_user?.can_resolve_discussion;
|
||||
},
|
||||
lineRange() {
|
||||
return this.note.position?.line_range;
|
||||
|
|
@ -236,14 +245,14 @@ export default {
|
|||
if (noteId === this.note.id) {
|
||||
this.isEditing = true;
|
||||
this.setSelectedCommentPositionHover();
|
||||
this.scrollToNoteIfNeeded($(this.$el));
|
||||
this.$el.scrollIntoView();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.isTarget && this.shouldScrollToNote) {
|
||||
this.scrollToNoteIfNeeded($(this.$el));
|
||||
this.$el.scrollIntoView({ duration: 0 });
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -253,7 +262,6 @@ export default {
|
|||
'removeNote',
|
||||
'updateNote',
|
||||
'toggleResolveNote',
|
||||
'scrollToNoteIfNeeded',
|
||||
'updateAssignees',
|
||||
'setSelectedCommentPositionHover',
|
||||
]),
|
||||
|
|
@ -428,7 +436,7 @@ export default {
|
|||
<div v-if="isMRDiffView" class="timeline-avatar gl-float-left gl-pt-2">
|
||||
<gl-avatar-link
|
||||
:href="author.path"
|
||||
:data-user-id="author.id"
|
||||
:data-user-id="authorId"
|
||||
:data-username="author.username"
|
||||
class="js-user-link"
|
||||
>
|
||||
|
|
@ -446,7 +454,7 @@ export default {
|
|||
<div v-else class="timeline-avatar gl-float-left">
|
||||
<gl-avatar-link
|
||||
:href="author.path"
|
||||
:data-user-id="author.id"
|
||||
:data-user-id="authorId"
|
||||
:data-username="author.username"
|
||||
class="js-user-link gl-relative"
|
||||
>
|
||||
|
|
@ -480,7 +488,7 @@ export default {
|
|||
</note-header>
|
||||
<note-actions
|
||||
:author="author"
|
||||
:author-id="author.id"
|
||||
:author-id="authorId"
|
||||
:note-id="note.id"
|
||||
:note-url="note.noteable_note_url"
|
||||
:access-level="note.human_access"
|
||||
|
|
@ -489,9 +497,9 @@ export default {
|
|||
:project-name="note.project_name"
|
||||
:noteable-type="note.noteable_type"
|
||||
:show-reply="showReplyButton"
|
||||
:can-edit="note.current_user.can_edit"
|
||||
:can-award-emoji="note.current_user.can_award_emoji"
|
||||
:can-delete="note.current_user.can_edit"
|
||||
:can-edit="canEdit"
|
||||
:can-award-emoji="canAwardEmoji"
|
||||
:can-delete="canEdit"
|
||||
:can-report-as-abuse="canReportAsAbuse"
|
||||
:can-resolve="canResolve"
|
||||
:resolvable="note.resolvable || note.isDraft"
|
||||
|
|
@ -511,17 +519,19 @@ export default {
|
|||
</div>
|
||||
<div class="timeline-discussion-body">
|
||||
<slot name="discussion-resolved-text"></slot>
|
||||
<note-body
|
||||
ref="noteBody"
|
||||
:note="note"
|
||||
:can-edit="note.current_user.can_edit"
|
||||
:line="line"
|
||||
:file="diffFile"
|
||||
:is-editing="isEditing"
|
||||
:help-page-path="helpPagePath"
|
||||
@handleFormUpdate="formUpdateHandler"
|
||||
@cancelForm="formCancelHandler"
|
||||
/>
|
||||
<slot name="note-body">
|
||||
<note-body
|
||||
ref="noteBody"
|
||||
:note="note"
|
||||
:can-edit="canEdit"
|
||||
:line="line"
|
||||
:file="diffFile"
|
||||
:is-editing="isEditing"
|
||||
:help-page-path="helpPagePath"
|
||||
@handleFormUpdate="formUpdateHandler"
|
||||
@cancelForm="formCancelHandler"
|
||||
/>
|
||||
</slot>
|
||||
<div class="timeline-discussion-body-footer">
|
||||
<slot name="after-note-body"></slot>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ import { v4 as uuidv4 } from 'uuid';
|
|||
import { InternalEvents } from '~/tracking';
|
||||
import { getDraft, getAutoSaveKeyFromDiscussion } from '~/lib/utils/autosave';
|
||||
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
|
||||
import { scrollToTargetOnResize } from '~/lib/utils/resize_observer';
|
||||
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
|
||||
import OrderedLayout from '~/vue_shared/components/ordered_layout.vue';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import DraftNote from '~/batch_comments/components/draft_note.vue';
|
||||
import { getLocationHash } from '~/lib/utils/url_utility';
|
||||
import NotePreview from '~/notes/components/note_preview.vue';
|
||||
import PlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue';
|
||||
import PlaceholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue';
|
||||
import SkeletonLoadingContainer from '~/vue_shared/components/notes/skeleton_note.vue';
|
||||
|
|
@ -26,6 +28,7 @@ import NotesActivityHeader from './notes_activity_header.vue';
|
|||
export default {
|
||||
name: 'NotesApp',
|
||||
components: {
|
||||
NotePreview,
|
||||
NotesActivityHeader,
|
||||
NoteableNote,
|
||||
NoteableDiscussion,
|
||||
|
|
@ -97,6 +100,7 @@ export default {
|
|||
'userCanReply',
|
||||
'sortDirection',
|
||||
'timelineEnabled',
|
||||
'targetNoteHash',
|
||||
]),
|
||||
sortDirDesc() {
|
||||
return this.sortDirection === constants.DESC;
|
||||
|
|
@ -104,6 +108,10 @@ export default {
|
|||
noteableType() {
|
||||
return this.noteableData.noteableType;
|
||||
},
|
||||
previewNoteId() {
|
||||
if (!this.isLoading || !this.targetNoteHash) return null;
|
||||
return this.targetNoteHash.replace('note_', '');
|
||||
},
|
||||
allDiscussions() {
|
||||
let skeletonNotes = [];
|
||||
|
||||
|
|
@ -113,6 +121,17 @@ export default {
|
|||
skeletonNotes = new Array(prerenderedNotesCount).fill({
|
||||
isSkeletonNote: true,
|
||||
});
|
||||
|
||||
if (
|
||||
this.previewNoteId &&
|
||||
!this.discussions.find((d) => d.notes[0].id === this.previewNoteId)
|
||||
) {
|
||||
const previewNote = {
|
||||
id: this.previewNoteId,
|
||||
isPreviewNote: true,
|
||||
};
|
||||
skeletonNotes.splice(prerenderedNotesCount / 2, 0, previewNote);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.sortDirDesc) {
|
||||
|
|
@ -172,6 +191,10 @@ export default {
|
|||
|
||||
window.addEventListener('hashchange', this.handleHashChanged);
|
||||
|
||||
if (this.targetNoteHash && this.targetNoteHash.startsWith('note_')) {
|
||||
scrollToTargetOnResize();
|
||||
}
|
||||
|
||||
eventHub.$on('notesApp.updateIssuableConfidentiality', this.setConfidentiality);
|
||||
},
|
||||
updated() {
|
||||
|
|
@ -213,7 +236,7 @@ export default {
|
|||
},
|
||||
checkLocationHash() {
|
||||
const hash = getLocationHash();
|
||||
const noteId = hash && hash.replace(/^note_/, '');
|
||||
const noteId = (hash && hash.startsWith('note_') && hash.replace(/^note_/, '')) ?? null;
|
||||
|
||||
if (noteId) {
|
||||
const discussion = this.discussions.find((d) => d.notes.some(({ id }) => id === noteId));
|
||||
|
|
@ -278,6 +301,13 @@ export default {
|
|||
:key="discussion.id"
|
||||
class="note-skeleton"
|
||||
/>
|
||||
<timeline-entry-item
|
||||
v-else-if="discussion.isPreviewNote"
|
||||
:key="discussion.id"
|
||||
class="target note note-wrapper note-comment"
|
||||
>
|
||||
<note-preview :note-id="previewNoteId" />
|
||||
</timeline-entry-item>
|
||||
<timeline-entry-item v-else-if="discussion.isDraft" :key="discussion.id">
|
||||
<draft-note :draft="discussion" />
|
||||
</timeline-entry-item>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
query snakeCaseNote($id: NoteID!) {
|
||||
note(id: $id) {
|
||||
id
|
||||
author {
|
||||
id
|
||||
avatar_url: avatarUrl
|
||||
name
|
||||
username
|
||||
web_url: webUrl
|
||||
web_path: webPath
|
||||
}
|
||||
body_html: bodyHtml
|
||||
created_at: createdAt
|
||||
last_edited_at: lastEditedAt
|
||||
last_edited_by: lastEditedBy {
|
||||
id
|
||||
avatar_url: avatarUrl
|
||||
name
|
||||
username
|
||||
web_url: webUrl
|
||||
web_path: webPath
|
||||
}
|
||||
internal
|
||||
url
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ import { confidentialWidget } from '~/sidebar/components/confidential/sidebar_co
|
|||
import updateIssueLockMutation from '~/sidebar/queries/update_issue_lock.mutation.graphql';
|
||||
import updateMergeRequestLockMutation from '~/sidebar/queries/update_merge_request_lock.mutation.graphql';
|
||||
import loadAwardsHandler from '~/awards_handler';
|
||||
import { isInViewport, scrollToElement, isInMRPage } from '~/lib/utils/common_utils';
|
||||
import { isInMRPage } from '~/lib/utils/common_utils';
|
||||
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
||||
import sidebarTimeTrackingEventHub from '~/sidebar/event_hub';
|
||||
import TaskList from '~/task_list';
|
||||
|
|
@ -632,12 +632,6 @@ export const toggleAwardRequest = ({ dispatch }, data) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const scrollToNoteIfNeeded = (context, el) => {
|
||||
if (!isInViewport(el[0])) {
|
||||
scrollToElement(el);
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchDiscussionDiffLines = ({ commit }, discussion) =>
|
||||
axios.get(discussion.truncated_diff_lines_path).then(({ data }) => {
|
||||
commit(types.SET_DISCUSSION_DIFF_LINES, {
|
||||
|
|
|
|||
|
|
@ -116,11 +116,6 @@ export default {
|
|||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showForm: this.isFormVisible || this.hasError,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hasRelatedIssues() {
|
||||
return this.relatedIssues.length > 0;
|
||||
|
|
@ -176,29 +171,25 @@ export default {
|
|||
: sprintf(this.$options.i18n.emptyItemsFree, { issuableType: this.issuableType });
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
if (this.showForm) {
|
||||
this.$refs.relatedIssuesWidget.showForm();
|
||||
}
|
||||
});
|
||||
},
|
||||
updated() {
|
||||
this.$nextTick(() => {
|
||||
if (this.hasError) {
|
||||
watch: {
|
||||
isFormVisible(newVal) {
|
||||
if (newVal === true) {
|
||||
this.$refs.relatedIssuesWidget.showForm();
|
||||
} else {
|
||||
this.$refs.relatedIssuesWidget.hideForm();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.isFormVisible) {
|
||||
this.$refs.relatedIssuesWidget.showForm();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleFormSubmit(event) {
|
||||
this.showForm = false;
|
||||
this.$emit('addIssuableFormSubmit', event);
|
||||
},
|
||||
handleFormCancel(event) {
|
||||
this.showForm = false;
|
||||
this.$emit('addIssuableFormCancel', event);
|
||||
this.$refs.relatedIssuesWidget.hideForm();
|
||||
},
|
||||
|
|
@ -227,6 +218,8 @@ export default {
|
|||
:help-link-text="helpLinkText"
|
||||
anchor-id="related-issues"
|
||||
data-testid="related-issues-block"
|
||||
@showForm="$emit('showForm')"
|
||||
@hideForm="$emit('hideForm')"
|
||||
>
|
||||
<template #actions>
|
||||
<slot name="header-actions"></slot>
|
||||
|
|
@ -253,7 +246,7 @@ export default {
|
|||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="!shouldShowTokenBody && !isFormVisible" #empty>
|
||||
<template v-if="!shouldShowTokenBody" #empty>
|
||||
<slot name="empty-state-message">{{ emptyStateMessage }}</slot>
|
||||
<gl-link
|
||||
v-if="hasHelpPath"
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ export default {
|
|||
if (response && response.data && response.data.message) {
|
||||
this.errorMessage = response.data.message;
|
||||
}
|
||||
this.isFormVisible = true;
|
||||
})
|
||||
.finally(() => {
|
||||
this.isSubmitting = false;
|
||||
|
|
@ -282,5 +283,7 @@ export default {
|
|||
@addIssuableFormCancel="onPendingFormCancel"
|
||||
@pendingIssuableRemoveRequest="onPendingIssueRemoveRequest"
|
||||
@relatedIssueRemoveRequest="onRelatedIssueRemoveRequest"
|
||||
@showForm="isFormVisible = true"
|
||||
@hideForm="isFormVisible = false"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -108,9 +108,11 @@ export default {
|
|||
showForm() {
|
||||
this.isFormVisible = true;
|
||||
this.collapsed = false;
|
||||
this.$emit('showForm');
|
||||
},
|
||||
hideForm() {
|
||||
this.isFormVisible = false;
|
||||
this.$emit('hideForm');
|
||||
},
|
||||
toggleForm() {
|
||||
if (this.isFormVisible) {
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ export default {
|
|||
e.preventDefault();
|
||||
this.$emit('select-issuable', {
|
||||
iid: this.issuableIid,
|
||||
webUrl: this.issuableLinkHref,
|
||||
webUrl: this.issuable.webUrl,
|
||||
fullPath: this.workItemFullPath,
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { GlLink, GlDrawer } from '@gitlab/ui';
|
||||
import { GlLink, GlDrawer, GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { escapeRegExp } from 'lodash';
|
||||
import { __ } from '~/locale';
|
||||
import deleteWorkItemMutation from '~/work_items/graphql/delete_work_item.mutation.graphql';
|
||||
import { TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
|
||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
|
|
@ -8,9 +9,13 @@ import { visitUrl } from '~/lib/utils/url_utility';
|
|||
|
||||
export default {
|
||||
name: 'WorkItemDrawer',
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
components: {
|
||||
GlLink,
|
||||
GlDrawer,
|
||||
GlButton,
|
||||
WorkItemDetail: () => import('~/work_items/components/work_item_detail.vue'),
|
||||
},
|
||||
inject: ['fullPath'],
|
||||
|
|
@ -31,6 +36,11 @@ export default {
|
|||
default: TYPE_ISSUE,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
copyTooltipText: this.$options.i18n.copyTooltipText,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
activeItemFullPath() {
|
||||
if (this.activeItem?.fullPath) {
|
||||
|
|
@ -45,6 +55,10 @@ export default {
|
|||
modalIsGroup() {
|
||||
return this.issuableType === TYPE_EPIC;
|
||||
},
|
||||
headerReference() {
|
||||
const path = this.activeItemFullPath.substring(this.activeItemFullPath.lastIndexOf('/') + 1);
|
||||
return `${path}#${this.activeItem.iid}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async deleteWorkItem({ workItemId }) {
|
||||
|
|
@ -62,8 +76,12 @@ export default {
|
|||
Sentry.captureException(error);
|
||||
}
|
||||
},
|
||||
redirectToWorkItem() {
|
||||
redirectToWorkItem(e) {
|
||||
const workItem = this.activeItem;
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
const escapedFullPath = escapeRegExp(this.fullPath);
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const regex = new RegExp(`groups\/${escapedFullPath}\/-\/(work_items|epics)\/\\d+`);
|
||||
|
|
@ -80,6 +98,17 @@ export default {
|
|||
visitUrl(workItem.webUrl);
|
||||
}
|
||||
},
|
||||
handleCopyToClipboard() {
|
||||
this.copyTooltipText = this.$options.i18n.copiedTooltipText;
|
||||
setTimeout(() => {
|
||||
this.copyTooltipText = this.$options.i18n.copyTooltipText;
|
||||
}, 2000);
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
copyTooltipText: __('Copy item URL'),
|
||||
copiedTooltipText: __('Copied'),
|
||||
openTooltipText: __('Open in full page'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -94,12 +123,39 @@ export default {
|
|||
@close="$emit('close')"
|
||||
>
|
||||
<template #title>
|
||||
<gl-link
|
||||
class="gl-text-default"
|
||||
:href="activeItem.webUrl"
|
||||
@click.prevent="redirectToWorkItem"
|
||||
>{{ __('Open full view') }}</gl-link
|
||||
>
|
||||
<div class="gl-text gl-flex gl-w-full gl-items-center gl-gap-x-2 xl:gl-px-4">
|
||||
<gl-link
|
||||
:href="activeItem.webUrl"
|
||||
class="gl-text-sm gl-font-bold gl-text-default"
|
||||
@click="redirectToWorkItem"
|
||||
>
|
||||
{{ headerReference }}
|
||||
</gl-link>
|
||||
<gl-button
|
||||
v-gl-tooltip
|
||||
data-testid="work-item-drawer-copy-button"
|
||||
:title="copyTooltipText"
|
||||
category="tertiary"
|
||||
class="gl-text-secondary"
|
||||
icon="link"
|
||||
size="small"
|
||||
:aria-label="$options.i18n.copyTooltipText"
|
||||
:data-clipboard-text="activeItem.webUrl"
|
||||
@click="handleCopyToClipboard"
|
||||
/>
|
||||
<gl-button
|
||||
v-gl-tooltip
|
||||
data-testid="work-item-drawer-link-button"
|
||||
:href="activeItem.webUrl"
|
||||
:title="$options.i18n.openTooltipText"
|
||||
category="tertiary"
|
||||
class="gl-text-secondary"
|
||||
icon="maximize"
|
||||
size="small"
|
||||
:aria-label="$options.i18n.openTooltipText"
|
||||
@click="redirectToWorkItem"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<work-item-detail
|
||||
|
|
|
|||
|
|
@ -107,7 +107,11 @@ $diff-file-header: 41px;
|
|||
.image {
|
||||
@apply gl-bg-strong;
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
padding: $gl-padding;
|
||||
|
||||
&:has(> .view) {
|
||||
padding-bottom: $gl-padding-32;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
display: inline-block;
|
||||
|
|
|
|||
|
|
@ -253,16 +253,6 @@ ul.related-merge-requests > li gl-emoji {
|
|||
}
|
||||
}
|
||||
|
||||
.issuable-header-slide-enter-active,
|
||||
.issuable-header-slide-leave-active {
|
||||
@apply gl-transition-all;
|
||||
}
|
||||
|
||||
.issuable-header-slide-enter,
|
||||
.issuable-header-slide-leave-to {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.issuable-sticky-header-visible {
|
||||
--issuable-sticky-header-height: 40px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,13 +17,11 @@ module TimeTrackable
|
|||
|
||||
alias_method :time_spent?, :time_spent
|
||||
|
||||
attribute :time_estimate, default: 0
|
||||
|
||||
validate :check_time_estimate
|
||||
validate :check_negative_time_spent
|
||||
|
||||
has_many :timelogs, dependent: :destroy, autosave: true # rubocop:disable Cop/ActiveRecordDependent
|
||||
after_initialize :set_time_estimate_default_value
|
||||
before_save :set_time_estimate_default_value
|
||||
after_save :clear_memoized_total_time_spent
|
||||
end
|
||||
|
||||
|
|
@ -95,11 +93,18 @@ module TimeTrackable
|
|||
val.is_a?(Integer) ? super([val, Gitlab::Database::MAX_INT_VALUE].min) : super(val)
|
||||
end
|
||||
|
||||
def time_estimate
|
||||
super || self.class.column_defaults['time_estimate']
|
||||
end
|
||||
|
||||
def set_time_estimate_default_value
|
||||
return if new_record?
|
||||
return unless has_attribute?(:time_estimate)
|
||||
# time estimate can be set to nil, in case of an invalid value, e.g. a String instead of a number, in which case
|
||||
# we should not be overwriting it to default value, but rather have the validation catch the error
|
||||
return if time_estimate_changed?
|
||||
|
||||
self.time_estimate ||= self.class.column_defaults['time_estimate']
|
||||
self.time_estimate = self.class.column_defaults['time_estimate'] if read_attribute(:time_estimate).nil?
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -136,8 +141,9 @@ module TimeTrackable
|
|||
end
|
||||
|
||||
def check_time_estimate
|
||||
return unless new_record? || time_estimate_changed?
|
||||
return if time_estimate.is_a?(Numeric) && time_estimate >= 0
|
||||
# we'll set the time_tracking to zero at DB level through default value
|
||||
return unless time_estimate_changed?
|
||||
return if read_attribute(:time_estimate).is_a?(Numeric) && read_attribute(:time_estimate) >= 0
|
||||
|
||||
errors.add(:time_estimate, _('must have a valid format and be greater than or equal to zero.'))
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ChangeTimeEstimateDefaultFromNullToZeroOnIssues < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.4'
|
||||
enable_lock_retries!
|
||||
|
||||
TABLE_NAME = :issues
|
||||
COLUMN_NAME = :time_estimate
|
||||
|
||||
def up
|
||||
change_column_default(TABLE_NAME, COLUMN_NAME, from: nil, to: 0)
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column_default(TABLE_NAME, COLUMN_NAME)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ChangeTimeEstimateDefaultFromNullToZeroOnMergeRequests < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.4'
|
||||
enable_lock_retries!
|
||||
|
||||
TABLE_NAME = :merge_requests
|
||||
COLUMN_NAME = :time_estimate
|
||||
|
||||
def up
|
||||
change_column_default(TABLE_NAME, COLUMN_NAME, from: nil, to: 0)
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column_default(TABLE_NAME, COLUMN_NAME)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
39c3afa6ca1f4005d7d26d3ea510d406a2d4aa101bf5bf2208961b796d0d47bf
|
||||
|
|
@ -0,0 +1 @@
|
|||
d7f9e2f68e4d0375e381f2345bc2b8fe4eac2f965fa467529d9a27c70e73f5a4
|
||||
|
|
@ -12367,7 +12367,7 @@ CREATE TABLE issues (
|
|||
lock_version integer DEFAULT 0,
|
||||
title_html text,
|
||||
description_html text,
|
||||
time_estimate integer,
|
||||
time_estimate integer DEFAULT 0,
|
||||
relative_position integer,
|
||||
service_desk_reply_to character varying,
|
||||
cached_markdown_version integer,
|
||||
|
|
@ -13232,7 +13232,7 @@ CREATE TABLE merge_requests (
|
|||
lock_version integer DEFAULT 0,
|
||||
title_html text,
|
||||
description_html text,
|
||||
time_estimate integer,
|
||||
time_estimate integer DEFAULT 0,
|
||||
squash boolean DEFAULT false NOT NULL,
|
||||
cached_markdown_version integer,
|
||||
last_edited_at timestamp without time zone,
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
---
|
||||
# Error: gitlab_base.CodeBlockNesting
|
||||
#
|
||||
# Ensures content nested in lists are spaced correctly.
|
||||
#
|
||||
extends: existence
|
||||
message: "Items under an ordered list must be indented three spaces. Items under an unordered list must be indented two spaces."
|
||||
link: https://docs.gitlab.com/ee/development/documentation/styleguide/#nesting-inside-a-list-item
|
||||
level: error
|
||||
nonword: true
|
||||
ignorecase: true
|
||||
scope: raw
|
||||
tokens:
|
||||
- '^1. .*\n\n? ( )?[`\w-]'
|
||||
- '^- .*\n\n? ( )?[`\w-]'
|
||||
|
||||
# Regex guide:
|
||||
#
|
||||
# "^1. .*" - Lines that start with an ordered list.
|
||||
# "^- .*" - Lines that start with an unordered list.
|
||||
#
|
||||
# "\n\n?" - Then one or two newlines
|
||||
#
|
||||
# Ordered lists: " ( )?" - Two or four spaces (three = correct)
|
||||
# Unordered lists: " ( )?" - One or three spaces (two = correct)
|
||||
#
|
||||
# "[`\w-]" - Any one of:
|
||||
#
|
||||
# - A backtick - For code blocks after a list.
|
||||
# - A letter/number - For alert boxes, sentences, and nested ordered lists (after a list).
|
||||
# - A hyphen - For nested unordered lists (after a list).
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
# Error: gitlab_base.ListIndentation
|
||||
#
|
||||
# Ensures content nested in lists is spaced correctly.
|
||||
#
|
||||
extends: existence
|
||||
message: "Items nested under an ordered list must be indented three spaces. Items nested under an unordered list must be indented two spaces."
|
||||
link: https://docs.gitlab.com/ee/development/documentation/styleguide/#nesting-inside-a-list-item
|
||||
level: error
|
||||
nonword: true
|
||||
ignorecase: true
|
||||
scope: raw
|
||||
tokens:
|
||||
- '^1\. [^\n]*\n\n?( | | )[`\w-]'
|
||||
- '^- [^\n]*\n\n?( | | )[`\w-]'
|
||||
|
||||
# Regex guide:
|
||||
#
|
||||
# "^1. [^\n]*" - Lines that start with an ordered list.
|
||||
# "^- [^\n]*" - Lines that start with an unordered list.
|
||||
#
|
||||
# "\n\n?" - Then one or two newlines
|
||||
#
|
||||
# Ordered lists: "( | | )" - One, two, or four spaces (three = OK)
|
||||
# Unordered lists: "( | | )" - One, three, or four spaces (two = OK)
|
||||
#
|
||||
# "[`\w-]" - Any one of:
|
||||
#
|
||||
# - A backtick - For code blocks (after a list item).
|
||||
# - A letter/number - For alert boxes, sentences, and nested ordered lists (after a list item).
|
||||
# - A hyphen - For nested unordered lists (after a list item).
|
||||
|
|
@ -43,17 +43,17 @@ To set up your self-hosted model deployment infrastructure:
|
|||
|
||||
Install one of the following GitLab-approved LLM models:
|
||||
|
||||
| Model | Code Completion | Code Generation | Duo Chat |
|
||||
|-----------------------------------------------------------------------------------------------------------------------------|-----------------|-----------------|----------|
|
||||
| [CodeGemma 2b](https://huggingface.co/google/codegemma-2b) | ✅ | - | - |
|
||||
| [CodeGemma 7b-it](https://huggingface.co/google/codegemma-7b-it) (Instruction) | - | ✅ | - |
|
||||
| [CodeGemma 7b-code](https://huggingface.co/google/codegemma-7b) (Code) | ✅ | - | - |
|
||||
| [Code-Llama 13b-code](https://huggingface.co/meta-llama/CodeLlama-13b-hf) | ✅ | - | - |
|
||||
| [Code-Llama 13b](https://huggingface.co/meta-llama/CodeLlama-13b-Instruct-hf) | - | ✅ | - |
|
||||
| [Codestral 22B](https://huggingface.co/mistralai/Codestral-22B-v0.1) (see [setup instructions](litellm_proxy_setup.md#example-setup-for-codestral-with-ollama)) | ✅ | ✅ | - |
|
||||
| [Mistral 7B](https://huggingface.co/mistralai/Mistral-7B-v0.1) | - | ✅ | ✅ |
|
||||
| [Mixtral 8x22B](https://huggingface.co/mistral-community/Mixtral-8x22B-v0.1) | - | ✅ | ✅ |
|
||||
| [Mixtral 8x7B](https://huggingface.co/mistralai/Mixtral-8x7B-Instruct-v0.1) | - | ✅ | ✅ |
|
||||
| Model | Code completion | Code generation | GitLab Duo Chat |
|
||||
|------------------------------------------------------------------------------------|-----------------|-----------------|---------|
|
||||
| [CodeGemma 2b](https://huggingface.co/google/codegemma-2b) | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
|
||||
| [CodeGemma 7b-it](https://huggingface.co/google/codegemma-7b-it) (Instruction) | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No |
|
||||
| [CodeGemma 7b-code](https://huggingface.co/google/codegemma-7b) (Code) | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
|
||||
| [Code-Llama 13b-code](https://huggingface.co/meta-llama/CodeLlama-13b-hf) | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
|
||||
| [Code-Llama 13b](https://huggingface.co/meta-llama/CodeLlama-13b-Instruct-hf) | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No |
|
||||
| [Codestral 22B](https://huggingface.co/mistralai/Codestral-22B-v0.1) (see [setup instructions](litellm_proxy_setup.md#example-setup-for-codestral-with-ollama)) | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No |
|
||||
| [Mistral 7B](https://huggingface.co/mistralai/Mistral-7B-v0.1) | **{dotted-circle}** No | **{check-circle}** Yes | **{check-circle}** Yes |
|
||||
| [Mixtral 8x22B](https://huggingface.co/mistral-community/Mixtral-8x22B-v0.1) | **{dotted-circle}** No | **{check-circle}** Yes | **{check-circle}** Yes |
|
||||
| [Mixtral 8x7B](https://huggingface.co/mistralai/Mixtral-8x7B-Instruct-v0.1) | **{dotted-circle}** No | **{check-circle}** Yes | **{check-circle}** Yes |
|
||||
|
||||
### Recommended serving architectures
|
||||
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ When Terraform state administration is disabled:
|
|||
- On the left sidebar, you cannot select **Operate > Terraform states**.
|
||||
- Any CI/CD jobs that access the Terraform state fail with this error:
|
||||
|
||||
```shell
|
||||
Error refreshing state: HTTP remote state endpoint invalid auth
|
||||
```
|
||||
```shell
|
||||
Error refreshing state: HTTP remote state endpoint invalid auth
|
||||
```
|
||||
|
||||
To disable Terraform administration, follow the steps below according to your installation.
|
||||
|
||||
|
|
|
|||
|
|
@ -34889,18 +34889,6 @@ Represents a generic widget definition.
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="workitemwidgetdefinitiongenerictype"></a>`type` | [`WorkItemWidgetType!`](#workitemwidgettype) | Widget type. |
|
||||
|
||||
### `WorkItemWidgetDefinitionHealthStatus`
|
||||
|
||||
Represents a health status widget definition.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="workitemwidgetdefinitionhealthstatuseditable"></a>`editable` | [`Boolean!`](#boolean) | Indicates whether editable health status is available. |
|
||||
| <a id="workitemwidgetdefinitionhealthstatusrollup"></a>`rollUp` | [`Boolean!`](#boolean) | Indicates whether rolled up health status is available. |
|
||||
| <a id="workitemwidgetdefinitionhealthstatustype"></a>`type` | [`WorkItemWidgetType!`](#workitemwidgettype) | Widget type. |
|
||||
|
||||
### `WorkItemWidgetDefinitionHierarchy`
|
||||
|
||||
Represents a hierarchy widget definition.
|
||||
|
|
@ -34998,7 +34986,6 @@ Represents a health status widget.
|
|||
| <a id="workitemwidgethealthstatushealthstatus"></a>`healthStatus` | [`HealthStatus`](#healthstatus) | Health status of the work item. |
|
||||
| <a id="workitemwidgethealthstatusrolleduphealthstatus"></a>`rolledUpHealthStatus` **{warning-solid}** | [`[WorkItemWidgetHealthStatusCount!]`](#workitemwidgethealthstatuscount) | **Introduced** in GitLab 17.3. **Status**: Experiment. Rolled up health status of the work item. |
|
||||
| <a id="workitemwidgethealthstatustype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
|
||||
| <a id="workitemwidgethealthstatuswidgetdefinition"></a>`widgetDefinition` | [`WorkItemWidgetDefinitionHealthStatus`](#workitemwidgetdefinitionhealthstatus) | Health status widget definition. |
|
||||
|
||||
### `WorkItemWidgetHealthStatusCount`
|
||||
|
||||
|
|
@ -40735,7 +40722,6 @@ Implementations:
|
|||
|
||||
- [`WorkItemWidgetDefinitionAssignees`](#workitemwidgetdefinitionassignees)
|
||||
- [`WorkItemWidgetDefinitionGeneric`](#workitemwidgetdefinitiongeneric)
|
||||
- [`WorkItemWidgetDefinitionHealthStatus`](#workitemwidgetdefinitionhealthstatus)
|
||||
- [`WorkItemWidgetDefinitionHierarchy`](#workitemwidgetdefinitionhierarchy)
|
||||
- [`WorkItemWidgetDefinitionLabels`](#workitemwidgetdefinitionlabels)
|
||||
- [`WorkItemWidgetDefinitionWeight`](#workitemwidgetdefinitionweight)
|
||||
|
|
|
|||
|
|
@ -102,11 +102,11 @@ plan:
|
|||
key: TP
|
||||
name: test plan
|
||||
stages:
|
||||
- Default Stage:
|
||||
manual: false
|
||||
final: false
|
||||
jobs:
|
||||
- Default Job
|
||||
- Default Stage:
|
||||
manual: false
|
||||
final: false
|
||||
jobs:
|
||||
- Default Job
|
||||
Default Job:
|
||||
key: JOB1
|
||||
tasks:
|
||||
|
|
@ -116,20 +116,20 @@ Default Job:
|
|||
- script:
|
||||
interpreter: SHELL
|
||||
scripts:
|
||||
- |-
|
||||
ruby -v # Print out ruby version for debugging
|
||||
bundle config set --local deployment true # Install dependencies into ./vendor/ruby
|
||||
bundle install -j $(nproc)
|
||||
rubocop
|
||||
rspec spec
|
||||
- |-
|
||||
ruby -v # Print out ruby version for debugging
|
||||
bundle config set --local deployment true # Install dependencies into ./vendor/ruby
|
||||
bundle install -j $(nproc)
|
||||
rubocop
|
||||
rspec spec
|
||||
description: run bundler
|
||||
artifact-subscriptions: []
|
||||
repositories:
|
||||
- Demo Project:
|
||||
scope: global
|
||||
- Demo Project:
|
||||
scope: global
|
||||
triggers:
|
||||
- polling:
|
||||
period: '180'
|
||||
- polling:
|
||||
period: '180'
|
||||
branches:
|
||||
create: manually
|
||||
delete: never
|
||||
|
|
@ -150,20 +150,20 @@ version: 2
|
|||
plan:
|
||||
key: AB-TP
|
||||
plan-permissions:
|
||||
- users:
|
||||
- root
|
||||
permissions:
|
||||
- view
|
||||
- edit
|
||||
- build
|
||||
- clone
|
||||
- admin
|
||||
- view-configuration
|
||||
- roles:
|
||||
- logged-in
|
||||
- anonymous
|
||||
permissions:
|
||||
- view
|
||||
- users:
|
||||
- root
|
||||
permissions:
|
||||
- view
|
||||
- edit
|
||||
- build
|
||||
- clone
|
||||
- admin
|
||||
- view-configuration
|
||||
- roles:
|
||||
- logged-in
|
||||
- anonymous
|
||||
permissions:
|
||||
- view
|
||||
...
|
||||
|
||||
```
|
||||
|
|
@ -175,7 +175,7 @@ default:
|
|||
image: ruby:latest
|
||||
|
||||
stages:
|
||||
- default-stage
|
||||
- default-stage
|
||||
|
||||
job1:
|
||||
stage: default-stage
|
||||
|
|
@ -416,10 +416,10 @@ Default Job:
|
|||
- script:
|
||||
interpreter: SHELL
|
||||
scripts:
|
||||
- |-
|
||||
ruby -v
|
||||
bundle config set --local deployment true
|
||||
bundle install -j $(nproc)
|
||||
- |-
|
||||
ruby -v
|
||||
bundle config set --local deployment true
|
||||
bundle install -j $(nproc)
|
||||
description: run bundler
|
||||
other:
|
||||
concurrent-build-plugin: system-default
|
||||
|
|
@ -489,8 +489,8 @@ For example, in a Bamboo build plan:
|
|||
version: 2
|
||||
#...
|
||||
triggers:
|
||||
- polling:
|
||||
period: '180'
|
||||
- polling:
|
||||
period: '180'
|
||||
```
|
||||
|
||||
GitLab CI/CD pipelines can be triggered based on code change, on schedule, or triggered by
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ to stop them from running:
|
|||
- Use [`rules`](../yaml/index.md#rules) to skip tests that aren't needed. For example,
|
||||
skip backend tests when only the frontend code is changed.
|
||||
- Run non-essential [scheduled pipelines](schedules.md) less frequently.
|
||||
- Distribute [`cron` schedules](schedules.md#view-and-optimize-pipeline-schedules) evenly across time.
|
||||
|
||||
### Fail fast
|
||||
|
||||
|
|
|
|||
|
|
@ -96,7 +96,8 @@ When working with pipeline schedules, you might encounter the following issues.
|
|||
|
||||
### Short refs are expanded to full refs
|
||||
|
||||
When you provide a short `ref` to the API, it is automatically expanded to a full `ref`. This behavior is intended and ensures explicit resource identification.
|
||||
When you provide a short `ref` to the API, it is automatically expanded to a full `ref`.
|
||||
This behavior is intended and ensures explicit resource identification.
|
||||
|
||||
The API accepts both short refs (such as `main`) and full refs (such as `refs/heads/main` or `refs/tags/main`).
|
||||
|
||||
|
|
@ -108,3 +109,31 @@ In some cases, the API can't automatically expand a short `ref` to a full `ref`.
|
|||
- You provide a short `ref`, but no branch or tag with that name exists.
|
||||
|
||||
To resolve this issue, provide the full `ref` to ensure the correct resource is identified.
|
||||
|
||||
### View and optimize pipeline schedules
|
||||
|
||||
To prevent [excessive load](pipeline_efficiency.md) caused by too many pipelines starting simultaneously,
|
||||
you can review and optimize your pipeline schedules.
|
||||
|
||||
To get an overview of all existing schedules and identify opportunities to distribute them more evenly:
|
||||
|
||||
1. Run this command to extract and format schedule data:
|
||||
|
||||
```shell
|
||||
outfile=/tmp/gitlab_ci_schedules.tsv
|
||||
sudo gitlab-psql --command "
|
||||
COPY (SELECT
|
||||
ci_pipeline_schedules.cron,
|
||||
projects.path AS project,
|
||||
users.email
|
||||
FROM ci_pipeline_schedules
|
||||
JOIN projects ON projects.id = ci_pipeline_schedules.project_id
|
||||
JOIN users ON users.id = ci_pipeline_schedules.owner_id
|
||||
) TO '$outfile' CSV HEADER DELIMITER E'\t' ;"
|
||||
sort "$outfile" | uniq -c | sort -n
|
||||
```
|
||||
|
||||
1. Review the output to identify popular `cron` patterns.
|
||||
For example, you might see many schedules set to run at the start of each hour (`0 * * * *`).
|
||||
1. Adjust the schedules to create a staggered [`cron` pattern](../../topics/cron/index.md#cron-syntax), especially for large repositories.
|
||||
For example, instead of multiple schedules running at the start of each hour, distribute them throughout the hour (`5 * * * *`, `15 * * * *`, `25 * * * *`).
|
||||
|
|
|
|||
|
|
@ -127,18 +127,18 @@ GitLab also supports the following user types for unique use cases:
|
|||
- [Auditor Users](../administration/auditor_users.md) - The auditor role provides read-only access to all groups, projects and other resources except for the **Admin** area and project/group settings. You can use the auditor role when engaging with third-party auditors that require access to certain projects to validate processes.
|
||||
|
||||
- [External Users](../administration/external_users.md) -
|
||||
External users can be set to provide limited access for users that
|
||||
may not be part of the organization. Typically, this can be used to
|
||||
satisfy managing access for contractors or other third parties.
|
||||
Controls such as IA-4(4) require non-organizational users to be
|
||||
identified and managed in accordance with company policy. Setting
|
||||
external users can reduce risk to an organization by limiting access
|
||||
to projects by default and assisting administrators in identifying
|
||||
which users are not employed by the organization.
|
||||
External users can be set to provide limited access for users that
|
||||
may not be part of the organization. Typically, this can be used to
|
||||
satisfy managing access for contractors or other third parties.
|
||||
Controls such as IA-4(4) require non-organizational users to be
|
||||
identified and managed in accordance with company policy. Setting
|
||||
external users can reduce risk to an organization by limiting access
|
||||
to projects by default and assisting administrators in identifying
|
||||
which users are not employed by the organization.
|
||||
|
||||
- [Service Accounts](../user/profile/service_accounts.md#self-managed-gitlab) -
|
||||
Service accounts may be added to accommodate automated tasks.
|
||||
Service accounts do not use a seat under the license.
|
||||
Service accounts may be added to accommodate automated tasks.
|
||||
Service accounts do not use a seat under the license.
|
||||
|
||||
**Admin** area - In the **Admin** area, administrators can [export permissions](../administration/admin_area.md#user-permission-export),
|
||||
[review user identities](../administration/admin_area.md#user-identities), [administer groups](../administration/admin_area.md#administering-groups),
|
||||
|
|
@ -148,60 +148,60 @@ requirements:
|
|||
- [Reset user password](reset_user_password.md) when suspected of compromise.
|
||||
|
||||
- [Unlock users](unlock_user.md).
|
||||
By default, GitLab locks users after 10 failed sign-in attempts.
|
||||
Users remain locked for 10 minutes or until an administrator unlocks
|
||||
the user. In GitLab 16.5 and later, administrators can [use the API](../api/settings.md#list-of-settings-that-can-be-accessed-via-api-calls)
|
||||
to configure max login attempts and time period for remaining locked
|
||||
out. Per guidance in AC-7, FedRAMP defers to NIST 800-63B for
|
||||
defining parameters for account lockouts, which the default setting
|
||||
satisfies.
|
||||
By default, GitLab locks users after 10 failed sign-in attempts.
|
||||
Users remain locked for 10 minutes or until an administrator unlocks
|
||||
the user. In GitLab 16.5 and later, administrators can [use the API](../api/settings.md#list-of-settings-that-can-be-accessed-via-api-calls)
|
||||
to configure max login attempts and time period for remaining locked
|
||||
out. Per guidance in AC-7, FedRAMP defers to NIST 800-63B for
|
||||
defining parameters for account lockouts, which the default setting
|
||||
satisfies.
|
||||
|
||||
- Review [abuse reports](../administration/review_abuse_reports.md)
|
||||
or [spam logs](../administration/review_spam_logs.md).
|
||||
FedRAMP requires organizations to monitor accounts for atypical use
|
||||
(AC-2(12)). GitLab empowers users to flag abuse in abuse reports,
|
||||
where administrators can remove access pending investigation. Spam
|
||||
logs are consolidated in the **Spam logs** section of the **Admin** area.
|
||||
Administrators can remove, block, or trust users flagged in that
|
||||
area.
|
||||
or [spam logs](../administration/review_spam_logs.md).
|
||||
FedRAMP requires organizations to monitor accounts for atypical use
|
||||
(AC-2(12)). GitLab empowers users to flag abuse in abuse reports,
|
||||
where administrators can remove access pending investigation. Spam
|
||||
logs are consolidated in the **Spam logs** section of the **Admin** area.
|
||||
Administrators can remove, block, or trust users flagged in that
|
||||
area.
|
||||
|
||||
- [Set password storage parameters](password_storage.md).
|
||||
Stored secrets must satisfy FIPS 140-2 or 140-3 as outlined in
|
||||
SC-13. PBKDF2+SHA512 is supported with FIPS compliant ciphers when
|
||||
FIPS mode is enabled.
|
||||
Stored secrets must satisfy FIPS 140-2 or 140-3 as outlined in
|
||||
SC-13. PBKDF2+SHA512 is supported with FIPS compliant ciphers when
|
||||
FIPS mode is enabled.
|
||||
|
||||
- [Credentials inventory](../administration/credentials_inventory.md)
|
||||
enables administrators to review all secrets used in a GitLab
|
||||
self-managed instance in one place. A consolidated view of
|
||||
credentials, tokens, and keys may assist with satisfying
|
||||
requirements such as reviewing passwords or rotating credentials.
|
||||
enables administrators to review all secrets used in a GitLab
|
||||
self-managed instance in one place. A consolidated view of
|
||||
credentials, tokens, and keys may assist with satisfying
|
||||
requirements such as reviewing passwords or rotating credentials.
|
||||
|
||||
- [Set customer password length limits](password_length_limits.md).
|
||||
FedRAMP defers to NIST 800-63B in IA-5 for establishing password
|
||||
length requirements. GitLab supports 8-128 character passwords, with
|
||||
8 characters set as the default. GitLab provides [instructions for updating the minimum password length](password_length_limits.md#modify-minimum-password-length)
|
||||
with the GitLab UI, which organizations interested
|
||||
in enforcing longer passwords can use. Additionally, self-managed customers
|
||||
may [configure complexity requirements](../administration/settings/sign_up_restrictions.md#password-complexity-requirements)
|
||||
through the **Admin** area UI.
|
||||
FedRAMP defers to NIST 800-63B in IA-5 for establishing password
|
||||
length requirements. GitLab supports 8-128 character passwords, with
|
||||
8 characters set as the default. GitLab provides [instructions for updating the minimum password length](password_length_limits.md#modify-minimum-password-length)
|
||||
with the GitLab UI, which organizations interested
|
||||
in enforcing longer passwords can use. Additionally, self-managed customers
|
||||
may [configure complexity requirements](../administration/settings/sign_up_restrictions.md#password-complexity-requirements)
|
||||
through the **Admin** area UI.
|
||||
|
||||
- [Default session durations](../administration/settings/account_and_limit_settings.md#customize-the-default-session-duration) -
|
||||
FedRAMP establishes that users that have been inactive for a set
|
||||
time period should be logged out. FedRAMP does not specify the time
|
||||
period, however, clarifies that for privileged users they should be
|
||||
logged out at the end of the standard work period. Administrators
|
||||
can establish [default session durations](../administration/settings/account_and_limit_settings.md#customize-the-default-session-duration).
|
||||
FedRAMP establishes that users that have been inactive for a set
|
||||
time period should be logged out. FedRAMP does not specify the time
|
||||
period, however, clarifies that for privileged users they should be
|
||||
logged out at the end of the standard work period. Administrators
|
||||
can establish [default session durations](../administration/settings/account_and_limit_settings.md#customize-the-default-session-duration).
|
||||
|
||||
- [Provisioning New Users](../user/profile/account/create_accounts.md) -
|
||||
Administrators can create new users for their GitLab account with the
|
||||
**Admin** area UI. In compliance with IA-5, GitLab requires new users to
|
||||
change their passwords on first login.
|
||||
Administrators can create new users for their GitLab account with the
|
||||
**Admin** area UI. In compliance with IA-5, GitLab requires new users to
|
||||
change their passwords on first login.
|
||||
|
||||
- Deprovisioning Users - Administrators are able to [remove users with the **Admin** area UI](../user/profile/account/delete_account.md#delete-users-and-user-contributions).
|
||||
An alternative to deleting users is to [block a user](../administration/moderate_users.md#block-a-user)
|
||||
and remove all access. Blocking a user maintains their data in
|
||||
repositories while removing all access. Blocked users do not impact
|
||||
seat counts.
|
||||
An alternative to deleting users is to [block a user](../administration/moderate_users.md#block-a-user)
|
||||
and remove all access. Blocking a user maintains their data in
|
||||
repositories while removing all access. Blocked users do not impact
|
||||
seat counts.
|
||||
|
||||
- Deactivate Users - Inactive users that have been identified during account reviews [may be temporarily deactivated](../administration/moderate_users.md#deactivate-a-user). Deactivation is similar to blocking, but there are a few important differences. Deactivating a user does not prohibit the user from signing into the GitLab UI. A deactivated user can become active again by signing in. A deactivated user:
|
||||
- Cannot access repositories or the API.
|
||||
|
|
@ -396,19 +396,19 @@ roles after the necessary reviews have been completed. Additional merge
|
|||
settings to consider:
|
||||
|
||||
- Remove all approvals when a commit is added - Ensures that approvals
|
||||
are not carried over when new commits are made to a merge request.
|
||||
are not carried over when new commits are made to a merge request.
|
||||
|
||||
- Restrict individuals who can dismiss code change reviews.
|
||||
|
||||
- Assign [code owners](../user/project/codeowners/index.md#codeowners-file)
|
||||
to be notified when sensitive code or configurations are changed through
|
||||
merge requests.
|
||||
to be notified when sensitive code or configurations are changed through
|
||||
merge requests.
|
||||
|
||||
- [Ensure all open comments are resolved before allowing code change merging](../user/project/merge_requests/index.md#prevent-merge-unless-all-threads-are-resolved).
|
||||
|
||||
- [Configure push rules](../user/project/repository/push_rules.md) -
|
||||
Push rules can be configured to meet requirements such as reviewing
|
||||
signed code, verifying users, and more.
|
||||
Push rules can be configured to meet requirements such as reviewing
|
||||
signed code, verifying users, and more.
|
||||
|
||||
**Testing and Validation of Changes**
|
||||
|
||||
|
|
@ -509,41 +509,41 @@ to ensure that Geo is configured appropriately for each use case.
|
|||
Implementing Geo provides the following benefits:
|
||||
|
||||
- Reduce from minutes to seconds the time taken for distributed
|
||||
developers to clone and fetch large repositories and projects.
|
||||
developers to clone and fetch large repositories and projects.
|
||||
|
||||
- Enable developers to contribute ideas and work in parallel, across
|
||||
regions..
|
||||
regions.
|
||||
|
||||
- Balance the read-only load between primary and secondary sites.
|
||||
|
||||
- Can be used for cloning and fetching projects, in addition to
|
||||
reading any data available in the GitLab web interface (see
|
||||
limitations).
|
||||
reading any data available in the GitLab web interface (see
|
||||
limitations).
|
||||
|
||||
- Overcomes slow connections between distant offices, saving time by
|
||||
improving speed for distributed teams.
|
||||
improving speed for distributed teams.
|
||||
|
||||
- Helps reduce the loading time for automated tasks, custom
|
||||
integrations, and internal workflows.
|
||||
integrations, and internal workflows.
|
||||
|
||||
- Can quickly fail over to a secondary site in a disaster recovery
|
||||
scenario.
|
||||
scenario.
|
||||
|
||||
- Allows planned failover to a secondary site.
|
||||
|
||||
Geo provides the following core features:
|
||||
|
||||
- Read-only secondary sites: Maintain one primary GitLab site while
|
||||
still enabling read-only secondary sites for distributed teams.
|
||||
still enabling read-only secondary sites for distributed teams.
|
||||
|
||||
- Authentication system hooks: Secondary sites receive all
|
||||
authentication data (like user accounts and logins) from the primary
|
||||
instance.
|
||||
authentication data (like user accounts and logins) from the primary
|
||||
instance.
|
||||
|
||||
- An intuitive UI: Secondary sites use the same web interface as the
|
||||
primary site. In addition, there are visual notifications that block
|
||||
write operations and make it clear that a user is in a secondary
|
||||
site.
|
||||
primary site. In addition, there are visual notifications that block
|
||||
write operations and make it clear that a user is in a secondary
|
||||
site.
|
||||
|
||||
Additional Geo Resources:
|
||||
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ Prerequisites:
|
|||
- You have installed and enabled the
|
||||
[GitLab Workflow extension for VS Code](../../../../editor_extensions/visual_studio_code/index.md).
|
||||
- You have completed the [VS Code extension setup](https://gitlab.com/gitlab-org/gitlab-vscode-extension/#setup)
|
||||
instructions, and authorized the extension to access your GitLab account.
|
||||
instructions, and authorized the extension to access your GitLab account.
|
||||
|
||||
To do this:
|
||||
|
||||
|
|
@ -129,7 +129,7 @@ Prerequisites:
|
|||
- You have installed and enabled the
|
||||
[GitLab plugin for JetBrains IDEs](../../../../editor_extensions/jetbrains_ide/index.md).
|
||||
- You have completed the [Jetbrains extension setup](https://gitlab.com/gitlab-org/editor-extensions/gitlab-jetbrains-plugin#setup)
|
||||
instructions, and authorized the extension to access your GitLab account.
|
||||
instructions, and authorized the extension to access your GitLab account.
|
||||
|
||||
To do this:
|
||||
|
||||
|
|
|
|||
|
|
@ -38,15 +38,10 @@ If you are a self-managed user, ensure that Code Suggestions for the [GitLab Web
|
|||
If the settings are enabled, but suggestions are still not displayed, try the following steps:
|
||||
|
||||
1. In the GitLab Workflow **Extension Settings**, enable **GitLab: Debug**.
|
||||
1. Open the extension log in **View > Output** and change the dropdown list to **GitLab Workflow** as the log filter. The command palette command is `GitLab: Show Extension Logs`.
|
||||
1. Disable and re-enable the **Enable code completion** checkbox.
|
||||
1. Verify that the debug log contains similar output:
|
||||
|
||||
```shell
|
||||
2023-07-14T17:29:00:763 [debug]: Disabling code completion
|
||||
2023-07-14T17:29:01:802 [debug]: Enabling code completion
|
||||
2023-07-14T17:29:01:802 [debug]: AI Assist: Using server: https://cloud.gitlab.com/ai/v2/code/completions
|
||||
```
|
||||
1. On the top menu, select **View > Output** to open the bottom panel, then either:
|
||||
- In the command palette, select `GitLab: Show Extension Logs`.
|
||||
- In the bottom panel, on the right, select the dropdown list to filter the logs. Select **GitLab Workflow**.
|
||||
1. In the GitLab Workflow **Extension Settings**, clear and re-select the **GitLab Duo Code Suggestions** checkbox.
|
||||
|
||||
To enable debug logging for the Language Server (LS), enable **GitLab › Ls: Debug**.
|
||||
|
||||
|
|
|
|||
|
|
@ -87,11 +87,12 @@ For more information, see [Pack-objects cache](../../../../administration/gitaly
|
|||
|
||||
### Reduce concurrent clones in CI/CD
|
||||
|
||||
CI/CD loads tend to be concurrent because pipelines are scheduled during set times.
|
||||
CI/CD loads tend to be concurrent because pipelines are [scheduled during set times](../../../../ci/pipelines/pipeline_efficiency.md#reduce-how-often-jobs-run).
|
||||
As a result, the Git requests against the repositories can spike notably during
|
||||
these times and lead to reduced performance for both CI/CD and users alike.
|
||||
|
||||
Reduce CI/CD pipeline concurrency by staggering them to run at different times.
|
||||
Reduce CI/CD pipeline concurrency by [staggering them](../../../../ci/pipelines/schedules.md#view-and-optimize-pipeline-schedules)
|
||||
to run at different times.
|
||||
For example, a set running at one time and another set running several minutes
|
||||
later.
|
||||
|
||||
|
|
|
|||
|
|
@ -15625,6 +15625,9 @@ msgstr ""
|
|||
msgid "Copy issue URL to clipboard"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy item URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy key"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -37712,9 +37715,6 @@ msgstr ""
|
|||
msgid "Open evidence JSON in new tab"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open full view"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open in Gitpod"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -37724,6 +37724,9 @@ msgstr ""
|
|||
msgid "Open in file view"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open in full page"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open in your IDE"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@
|
|||
"yaml": "^2.0.0-10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@gitlab/eslint-plugin": "20.2.0",
|
||||
"@gitlab/eslint-plugin": "20.2.1",
|
||||
"@gitlab/stylelint-config": "6.2.1",
|
||||
"@graphql-eslint/eslint-plugin": "3.20.1",
|
||||
"@originjs/vite-plugin-commonjs": "^1.0.3",
|
||||
|
|
|
|||
|
|
@ -97,22 +97,6 @@ tests = [
|
|||
expected: ['spec/initializers/action_mailer_hooks_spec.rb']
|
||||
},
|
||||
|
||||
{
|
||||
explanation: 'FOSS Snowplow definitions map to event validation spec',
|
||||
changed_file: 'config/events/status_page_incident_unpublished.yml',
|
||||
expected: ['spec/lib/gitlab/tracking/event_definition_validator_validate_all_spec.rb']
|
||||
},
|
||||
{
|
||||
explanation: 'EE Snowplow definitions map to event validation spec',
|
||||
changed_file: 'ee/config/events/licenses_list_viewed.yml',
|
||||
expected: ['spec/lib/gitlab/tracking/event_definition_validator_validate_all_spec.rb']
|
||||
},
|
||||
{
|
||||
explanation: 'Snowplow schema maps to event validation spec',
|
||||
changed_file: 'config/events/schema.json',
|
||||
expected: ['spec/lib/gitlab/tracking/event_definition_validator_validate_all_spec.rb']
|
||||
},
|
||||
|
||||
{
|
||||
explanation: 'DB structure should map to schema spec',
|
||||
changed_file: 'db/structure.sql',
|
||||
|
|
@ -255,7 +239,101 @@ tests = [
|
|||
# Note: The metrics seem to be changed every year or so, so this test will fail once a year or so.
|
||||
# You will need to change the metric below for another metric present in the project.
|
||||
changed_file: 'ee/config/metrics/counts_all/20221114065035_delete_merge_request.yml',
|
||||
expected: ['ee/spec/config/metrics/every_metric_definition_spec.rb']
|
||||
expected: %w[
|
||||
spec/config/metrics/every_metric_definition_spec.rb
|
||||
ee/spec/config/metrics/every_metric_definition_spec.rb
|
||||
spec/lib/gitlab/usage/metric_definition_validate_all_spec.rb
|
||||
]
|
||||
},
|
||||
{
|
||||
explanation: 'FOSS metric defintions map to metric validation specs',
|
||||
changed_file: 'config/metrics/counts_28d/count_total_merge_request_click_start_review_on_overview_tab_monthly.yml',
|
||||
expected: %w[
|
||||
spec/config/metrics/every_metric_definition_spec.rb
|
||||
ee/spec/config/metrics/every_metric_definition_spec.rb
|
||||
spec/lib/gitlab/usage/metric_definition_validate_all_spec.rb
|
||||
]
|
||||
},
|
||||
{
|
||||
explanation: 'Metric schema maps to metric validation spec',
|
||||
changed_file: 'config/metrics/schema/base.json',
|
||||
expected: %w[
|
||||
spec/config/metrics/every_metric_definition_spec.rb
|
||||
ee/spec/config/metrics/every_metric_definition_spec.rb
|
||||
spec/lib/gitlab/usage/metric_definition_validate_all_spec.rb
|
||||
spec/scripts/internal_events/cli_spec.rb
|
||||
spec/support_specs/matchers/internal_events_matchers_spec.rb
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
explanation: 'FOSS Snowplow definitions map to event validation spec',
|
||||
changed_file: 'config/events/status_page_incident_unpublished.yml',
|
||||
expected: ['spec/lib/gitlab/tracking/event_definition_validator_validate_all_spec.rb']
|
||||
},
|
||||
{
|
||||
explanation: 'EE Snowplow definitions map to event validation spec',
|
||||
changed_file: 'ee/config/events/licenses_list_viewed.yml',
|
||||
expected: ['spec/lib/gitlab/tracking/event_definition_validator_validate_all_spec.rb']
|
||||
},
|
||||
{
|
||||
explanation: 'Snowplow schema maps to event validation spec',
|
||||
changed_file: 'config/events/schema.json',
|
||||
expected: %w[
|
||||
spec/lib/gitlab/tracking/event_definition_validator_validate_all_spec.rb
|
||||
spec/scripts/internal_events/cli_spec.rb
|
||||
spec/support_specs/matchers/internal_events_matchers_spec.rb
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
explanation: 'Internal Events application logic maps to internal events tooling',
|
||||
changed_file: 'lib/gitlab/internal_events.rb',
|
||||
expected: %w[
|
||||
spec/lib/gitlab/internal_events_spec.rb
|
||||
spec/scripts/internal_events/cli_spec.rb
|
||||
spec/support_specs/matchers/internal_events_matchers_spec.rb
|
||||
]
|
||||
},
|
||||
{
|
||||
explanation: 'Internal Events CLI entrypoint maps to internal events tooling',
|
||||
changed_file: 'scripts/internal_events/cli.rb',
|
||||
expected: %w[
|
||||
spec/scripts/internal_events/cli_spec.rb
|
||||
spec/support_specs/matchers/internal_events_matchers_spec.rb
|
||||
]
|
||||
},
|
||||
{
|
||||
explanation: 'Internal Events CLI logic maps to internal events tooling',
|
||||
changed_file: 'scripts/internal_events/cli/global_state.rb',
|
||||
expected: %w[
|
||||
spec/scripts/internal_events/cli_spec.rb
|
||||
spec/support_specs/matchers/internal_events_matchers_spec.rb
|
||||
]
|
||||
},
|
||||
{
|
||||
explanation: 'Internal Events default test setup maps to internal events tooling',
|
||||
changed_file: 'spec/support/shared_examples/controllers/internal_event_tracking_examples.rb',
|
||||
expected: %w[
|
||||
spec/scripts/internal_events/cli_spec.rb
|
||||
spec/support_specs/matchers/internal_events_matchers_spec.rb
|
||||
]
|
||||
},
|
||||
{
|
||||
explanation: 'Internal Events custom test setup maps to internal events tooling',
|
||||
changed_file: 'spec/support/matchers/internal_events_matchers.rb',
|
||||
expected: %w[
|
||||
spec/scripts/internal_events/cli_spec.rb
|
||||
spec/support_specs/matchers/internal_events_matchers_spec.rb
|
||||
]
|
||||
},
|
||||
{
|
||||
explanation: 'Internal Events test cases map to internal events tooling',
|
||||
changed_file: 'spec/fixtures/scripts/internal_events/new_events.yml',
|
||||
expected: %w[
|
||||
spec/scripts/internal_events/cli_spec.rb
|
||||
spec/support_specs/matchers/internal_events_matchers_spec.rb
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1126,9 +1126,10 @@ describe('CE IssuesListApp component', () => {
|
|||
});
|
||||
|
||||
it('selects active issuable', () => {
|
||||
expect(findIssuableList().props('activeIssuable')).toEqual(
|
||||
getIssuesQueryResponse.data.project.issues.nodes[0],
|
||||
);
|
||||
expect(findIssuableList().props('activeIssuable')).toEqual({
|
||||
...getIssuesQueryResponse.data.project.issues.nodes[0],
|
||||
fullPath: defaultProvide.fullPath,
|
||||
});
|
||||
});
|
||||
|
||||
describe('when closing the drawer', () => {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ exports[`Merge request dashboard assigned users component renders user avatars 1
|
|||
>
|
||||
<img
|
||||
alt="avatar"
|
||||
class="gl-avatar gl-avatar-circle gl-avatar-s32"
|
||||
class="!gl-bg-white gl-avatar gl-avatar-circle gl-avatar-s32"
|
||||
src="/root"
|
||||
/>
|
||||
<span
|
||||
|
|
@ -67,7 +67,7 @@ exports[`Merge request dashboard assigned users component renders user avatars 1
|
|||
>
|
||||
<img
|
||||
alt="avatar"
|
||||
class="gl-avatar gl-avatar-circle gl-avatar-s32"
|
||||
class="!gl-bg-white gl-avatar gl-avatar-circle gl-avatar-s32"
|
||||
src="/root"
|
||||
/>
|
||||
<span
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import NotePreview from '~/notes/components/note_preview.vue';
|
||||
import NoteableNote from '~/notes/components/noteable_note.vue';
|
||||
import noteQuery from '~/notes/graphql/note.query.graphql';
|
||||
|
||||
const noteQueryHandler = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
note: {
|
||||
id: 'gid://gitlab/Note/1',
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
name: 'Administrator',
|
||||
username: 'root',
|
||||
avatar_url: '',
|
||||
web_url: '',
|
||||
web_path: '',
|
||||
},
|
||||
body_html: 'my quick note',
|
||||
created_at: '2020-01-01T10:00:00.000Z',
|
||||
last_edited_at: null,
|
||||
last_edited_by: null,
|
||||
internal: false,
|
||||
url: '/note/1',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('Note preview', () => {
|
||||
let wrapper;
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
const createComponent = ({ noteId = '1' }) => {
|
||||
wrapper = shallowMount(NotePreview, {
|
||||
apolloProvider: createMockApollo([[noteQuery, noteQueryHandler]]),
|
||||
propsData: {
|
||||
noteId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findNoteableNote = () => wrapper.findComponent(NoteableNote);
|
||||
|
||||
it('does nothing if URL does not contain a note id', () => {
|
||||
createComponent({ noteId: null });
|
||||
|
||||
expect(noteQueryHandler).not.toHaveBeenCalled();
|
||||
expect(wrapper.html()).toBe('');
|
||||
});
|
||||
|
||||
it('does nothing if URL links to a system note', () => {
|
||||
createComponent({
|
||||
noteId: '50f036b11addf3c1dc3d4b43a96cfeb799ae2f7c',
|
||||
});
|
||||
|
||||
expect(noteQueryHandler).not.toHaveBeenCalled();
|
||||
expect(wrapper.html()).toBe('');
|
||||
});
|
||||
|
||||
it('renders a note', async () => {
|
||||
createComponent({ noteId: '1234' });
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findNoteableNote().exists()).toBe(true);
|
||||
expect(findNoteableNote().props('showReplyButton')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -15,6 +15,7 @@ import notesEventHub from '~/notes/event_hub';
|
|||
import CommentForm from '~/notes/components/comment_form.vue';
|
||||
import NotesApp from '~/notes/components/notes_app.vue';
|
||||
import NotesActivityHeader from '~/notes/components/notes_activity_header.vue';
|
||||
import NotePreview from '~/notes/components/note_preview.vue';
|
||||
import * as constants from '~/notes/constants';
|
||||
import createStore from '~/notes/stores';
|
||||
import OrderedLayout from '~/vue_shared/components/ordered_layout.vue';
|
||||
|
|
@ -22,6 +23,9 @@ import OrderedLayout from '~/vue_shared/components/ordered_layout.vue';
|
|||
import * as mockData from '../mock_data';
|
||||
|
||||
jest.mock('~/behaviors/markdown/render_gfm');
|
||||
jest.mock('~/lib/utils/resize_observer', () => ({
|
||||
scrollToTargetOnResize: jest.fn(),
|
||||
}));
|
||||
|
||||
const TYPE_COMMENT_FORM = 'comment-form';
|
||||
const TYPE_NOTES_LIST = 'notes-list';
|
||||
|
|
@ -316,13 +320,13 @@ describe('note_app', () => {
|
|||
return waitForPromises();
|
||||
});
|
||||
|
||||
it('should listen hashchange event', () => {
|
||||
const hash = 'some dummy hash';
|
||||
it('should listen hashchange event for notes', () => {
|
||||
const hash = 'note_1234';
|
||||
jest.spyOn(urlUtility, 'getLocationHash').mockReturnValue(hash);
|
||||
const dispatchMock = jest.spyOn(store, 'dispatch');
|
||||
window.dispatchEvent(new Event('hashchange'), hash);
|
||||
|
||||
expect(dispatchMock).toHaveBeenCalledWith('setTargetNoteHash', 'some dummy hash');
|
||||
expect(dispatchMock).toHaveBeenCalledWith('setTargetNoteHash', 'note_1234');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -375,6 +379,30 @@ describe('note_app', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('preview note shown inside skeleton notes', () => {
|
||||
it.each`
|
||||
urlHash | exists
|
||||
${''} | ${false}
|
||||
${'note_123'} | ${true}
|
||||
`('url is `$urlHash`', ({ urlHash, exists }) => {
|
||||
jest.spyOn(urlUtility, 'getLocationHash').mockReturnValue(urlHash);
|
||||
|
||||
store = createStore();
|
||||
store.state.isLoading = true;
|
||||
store.state.targetNoteHash = urlHash;
|
||||
|
||||
wrapper = shallowMount(NotesApp, {
|
||||
propsData,
|
||||
store,
|
||||
stubs: {
|
||||
'ordered-layout': OrderedLayout,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.findComponent(NotePreview).exists()).toBe(exists);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when multiple draft types are present', () => {
|
||||
beforeEach(() => {
|
||||
store = createStore();
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ describe('Work item add note', () => {
|
|||
workItemResponse = workItemByIidResponseFactory({ canUpdate, canCreateNote }),
|
||||
signedIn = true,
|
||||
isEditing = true,
|
||||
isGroup = false,
|
||||
workItemType = 'Task',
|
||||
isInternalThread = false,
|
||||
isNewDiscussion = false,
|
||||
|
|
@ -66,9 +65,6 @@ describe('Work item add note', () => {
|
|||
[workItemByIidQuery, workItemResponseHandler],
|
||||
[createNoteMutation, mutationHandler],
|
||||
]),
|
||||
provide: {
|
||||
isGroup,
|
||||
},
|
||||
propsData: {
|
||||
fullPath: 'test-project-path',
|
||||
workItemId: id,
|
||||
|
|
|
|||
|
|
@ -48,15 +48,11 @@ describe('Work item comment form component', () => {
|
|||
isNewDiscussion = false,
|
||||
workItemState = STATE_OPEN,
|
||||
workItemType = 'Task',
|
||||
isGroup = false,
|
||||
hasReplies = false,
|
||||
isDiscussionResolved = false,
|
||||
isDiscussionResolvable = false,
|
||||
} = {}) => {
|
||||
wrapper = shallowMountExtended(WorkItemCommentForm, {
|
||||
provide: {
|
||||
isGroup,
|
||||
},
|
||||
propsData: {
|
||||
fullPath: 'test-project-path',
|
||||
workItemIid: '1',
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ describe('Work Item Note', () => {
|
|||
const createComponent = ({
|
||||
note = mockWorkItemCommentNote,
|
||||
isFirstNote = false,
|
||||
isGroup = false,
|
||||
updateNoteMutationHandler = successHandler,
|
||||
workItemId = mockWorkItemId,
|
||||
updateWorkItemMutationHandler = updateWorkItemMutationSuccessHandler,
|
||||
|
|
@ -98,9 +97,6 @@ describe('Work Item Note', () => {
|
|||
workItemByIidResponseHandler = workItemResponseHandler,
|
||||
} = {}) => {
|
||||
wrapper = shallowMount(WorkItemNote, {
|
||||
provide: {
|
||||
isGroup,
|
||||
},
|
||||
propsData: {
|
||||
fullPath: 'test-project-path',
|
||||
workItemId,
|
||||
|
|
@ -155,7 +151,7 @@ describe('Work Item Note', () => {
|
|||
});
|
||||
|
||||
it('should show the awards list when in edit mode', async () => {
|
||||
createComponent({ note: mockWorkItemCommentNote, workItemsAlpha: true });
|
||||
createComponent({ note: mockWorkItemCommentNote });
|
||||
findNoteActions().vm.$emit('startEditing');
|
||||
await nextTick();
|
||||
expect(findAwardsList().exists()).toBe(true);
|
||||
|
|
@ -438,7 +434,7 @@ describe('Work Item Note', () => {
|
|||
});
|
||||
|
||||
it('passes note props to awards list', () => {
|
||||
createComponent({ note: mockWorkItemCommentNote, workItemsAlpha: true });
|
||||
createComponent({ note: mockWorkItemCommentNote });
|
||||
|
||||
expect(findAwardsList().props('note')).toBe(mockWorkItemCommentNote);
|
||||
expect(findAwardsList().props('workItemIid')).toBe('1');
|
||||
|
|
|
|||
|
|
@ -148,10 +148,6 @@ describe('WorkItemActions component', () => {
|
|||
hideSubscribe,
|
||||
hasChildren,
|
||||
},
|
||||
provide: {
|
||||
isGroup: false,
|
||||
glFeatures: { workItemsBeta: true, workItemsAlpha: true },
|
||||
},
|
||||
mocks: {
|
||||
$toast,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ describe('WorkItemAttributesWrapper component', () => {
|
|||
|
||||
const createComponent = ({
|
||||
workItem = workItemQueryResponse.data.workItem,
|
||||
workItemsBeta = false,
|
||||
workItemsAlpha = false,
|
||||
groupPath = '',
|
||||
workItemParticipantsQueryHandler = workItemParticipantsQuerySuccessHandler,
|
||||
|
|
@ -72,7 +71,6 @@ describe('WorkItemAttributesWrapper component', () => {
|
|||
provide: {
|
||||
hasSubepicsFeature: true,
|
||||
glFeatures: {
|
||||
workItemsBeta,
|
||||
workItemsAlpha,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ describe('WorkItemCreatedUpdated component', () => {
|
|||
confidential = false,
|
||||
discussionLocked = false,
|
||||
updateInProgress = false,
|
||||
isGroup = false,
|
||||
} = {}) => {
|
||||
const workItemQueryResponse = workItemByIidResponseFactory({
|
||||
author,
|
||||
|
|
@ -45,9 +44,6 @@ describe('WorkItemCreatedUpdated component', () => {
|
|||
|
||||
wrapper = shallowMount(WorkItemCreatedUpdated, {
|
||||
apolloProvider: createMockApollo([[workItemByIidQuery, successHandler]]),
|
||||
provide: {
|
||||
isGroup,
|
||||
},
|
||||
propsData: {
|
||||
fullPath: '/some/project',
|
||||
workItemIid,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ describe('WorkItemDescriptionRendered', () => {
|
|||
workItemDescription = defaultWorkItemDescription,
|
||||
canEdit = false,
|
||||
mockComputed = {},
|
||||
hasWorkItemsBeta = false,
|
||||
} = {}) => {
|
||||
wrapper = shallowMount(WorkItemDescriptionRendered, {
|
||||
propsData: {
|
||||
|
|
@ -40,7 +39,6 @@ describe('WorkItemDescriptionRendered', () => {
|
|||
computed: mockComputed,
|
||||
provide: {
|
||||
fullPath: 'full/path',
|
||||
workItemsBeta: hasWorkItemsBeta,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -109,12 +109,10 @@ describe('WorkItemDetail component', () => {
|
|||
mutationHandler,
|
||||
error = undefined,
|
||||
workItemsAlphaEnabled = false,
|
||||
workItemsBeta = false,
|
||||
namespaceLevelWorkItems = true,
|
||||
hasSubepicsFeature = true,
|
||||
router = true,
|
||||
modalIsGroup = null,
|
||||
isGroup = false,
|
||||
} = {}) => {
|
||||
wrapper = shallowMountExtended(WorkItemDetail, {
|
||||
apolloProvider: createMockApollo([
|
||||
|
|
@ -141,14 +139,12 @@ describe('WorkItemDetail component', () => {
|
|||
provide: {
|
||||
glFeatures: {
|
||||
workItemsAlpha: workItemsAlphaEnabled,
|
||||
workItemsBeta,
|
||||
namespaceLevelWorkItems,
|
||||
},
|
||||
hasSubepicsFeature,
|
||||
fullPath: 'group/project',
|
||||
groupPath: 'group',
|
||||
reportAbusePath: '/report/abuse/path',
|
||||
isGroup,
|
||||
},
|
||||
stubs: {
|
||||
WorkItemAncestors: true,
|
||||
|
|
|
|||
|
|
@ -132,7 +132,6 @@ describe('WorkItemDevelopment CE', () => {
|
|||
.mockResolvedValue(closedWorkItemWithAutoCloseFlagEnabled);
|
||||
|
||||
const createComponent = ({
|
||||
isGroup = false,
|
||||
workItemId = 'gid://gitlab/WorkItem/1',
|
||||
workItemIid = '1',
|
||||
workItemFullPath = 'full-path',
|
||||
|
|
@ -153,7 +152,6 @@ describe('WorkItemDevelopment CE', () => {
|
|||
workItemFullPath,
|
||||
},
|
||||
provide: {
|
||||
isGroup,
|
||||
glFeatures: {
|
||||
workItemsAlpha: workItemsAlphaEnabled,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { GlDrawer, GlLink } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
|
||||
|
|
@ -10,6 +9,7 @@ import { TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
|
|||
import WorkItemDrawer from '~/work_items/components/work_item_drawer.vue';
|
||||
import WorkItemDetail from '~/work_items/components/work_item_detail.vue';
|
||||
import deleteWorkItemMutation from '~/work_items/graphql/delete_work_item.mutation.graphql';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
|
|
@ -27,10 +27,10 @@ describe('WorkItemDrawer', () => {
|
|||
|
||||
const createComponent = ({
|
||||
open = false,
|
||||
activeItem = { iid: '1', webUrl: 'test' },
|
||||
activeItem = { iid: '1', webUrl: 'test', fullPath: 'gitlab-org/gitlab' },
|
||||
issuableType = TYPE_ISSUE,
|
||||
} = {}) => {
|
||||
wrapper = shallowMount(WorkItemDrawer, {
|
||||
wrapper = shallowMountExtended(WorkItemDrawer, {
|
||||
propsData: {
|
||||
activeItem,
|
||||
open,
|
||||
|
|
@ -44,7 +44,6 @@ describe('WorkItemDrawer', () => {
|
|||
reportAbusePath: '',
|
||||
groupPath: '',
|
||||
hasSubepicsFeature: false,
|
||||
isGroup: false,
|
||||
},
|
||||
apolloProvider: createMockApollo([[deleteWorkItemMutation, deleteWorkItemMutationHandler]]),
|
||||
});
|
||||
|
|
@ -56,10 +55,26 @@ describe('WorkItemDrawer', () => {
|
|||
expect(findGlDrawer().props('open')).toBe(false);
|
||||
});
|
||||
|
||||
it('displays correct URL in link', () => {
|
||||
it('displays correct URL and text in link', () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.findComponent(GlLink).attributes('href')).toBe('test');
|
||||
const link = wrapper.findComponent(GlLink);
|
||||
expect(link.attributes('href')).toBe('test');
|
||||
expect(link.text()).toBe('gitlab#1');
|
||||
});
|
||||
|
||||
it('displays the correct URL in the full page button', () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.findByTestId('work-item-drawer-link-button').attributes('href')).toBe('test');
|
||||
});
|
||||
|
||||
it('has a copy to clipboard button for the item URL', () => {
|
||||
createComponent();
|
||||
|
||||
expect(
|
||||
wrapper.findByTestId('work-item-drawer-copy-button').attributes('data-clipboard-text'),
|
||||
).toBe('test');
|
||||
});
|
||||
|
||||
it('emits `close` event when drawer is closed', () => {
|
||||
|
|
|
|||
|
|
@ -87,9 +87,6 @@ describe('WorkItemChildrenWrapper', () => {
|
|||
|
||||
wrapper = shallowMountExtended(WorkItemChildrenWrapper, {
|
||||
apolloProvider: mockApollo,
|
||||
provide: {
|
||||
isGroup: false,
|
||||
},
|
||||
propsData: {
|
||||
fullPath: 'test/project',
|
||||
workItemType,
|
||||
|
|
|
|||
|
|
@ -64,13 +64,9 @@ describe('WorkItemActions component', () => {
|
|||
workItemType,
|
||||
workItemReference,
|
||||
},
|
||||
provide: {
|
||||
glFeatures: { workItemsBeta: true, workItemsAlpha: true },
|
||||
},
|
||||
mocks: {
|
||||
$toast,
|
||||
},
|
||||
stubs: {},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ describe('Work Item State toggle button component', () => {
|
|||
workItemState = STATE_OPEN,
|
||||
workItemType = 'Task',
|
||||
hasComment = false,
|
||||
isGroup = false,
|
||||
} = {}) => {
|
||||
wrapper = shallowMount(WorkItemStateToggle, {
|
||||
apolloProvider: createMockApollo([
|
||||
|
|
@ -57,9 +56,6 @@ describe('Work Item State toggle button component', () => {
|
|||
[workItemByIidQuery, querySuccessHander],
|
||||
[workItemLinkedItemsQuery, workItemLinkedItemsHandler],
|
||||
]),
|
||||
provide: {
|
||||
isGroup,
|
||||
},
|
||||
propsData: {
|
||||
workItemId: id,
|
||||
workItemIid: iid,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ describe('WorkItemTimeTracking component', () => {
|
|||
},
|
||||
provide: {
|
||||
fullPath: 'gitlab-org/gitlab',
|
||||
isGroup: false,
|
||||
},
|
||||
stubs: {
|
||||
GlProgressBar: true,
|
||||
|
|
|
|||
|
|
@ -84,9 +84,6 @@ describe('WorkItemTodo component', () => {
|
|||
workItemFullpath: mockWorkItemFullpath,
|
||||
currentUserTodos,
|
||||
},
|
||||
provide: {
|
||||
isGroup: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -452,7 +452,7 @@ RSpec.describe Issuable, feature_category: :team_planning do
|
|||
end
|
||||
|
||||
it 'skips coercion for not Integer values' do
|
||||
expect { issue.time_estimate = nil }.to change { issue.time_estimate }.to(nil)
|
||||
expect { issue.time_estimate = nil }.to change { issue.read_attribute(:time_estimate) }.to(nil)
|
||||
expect { issue.time_estimate = 'invalid time' }.not_to raise_error
|
||||
expect { issue.time_estimate = 22.33 }.not_to raise_error
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2060,6 +2060,25 @@ RSpec.describe Issue, feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#time_estimate' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
|
||||
context 'when time estimate on the issue record is NULL' do
|
||||
before do
|
||||
issue.update_column(:time_estimate, nil)
|
||||
end
|
||||
|
||||
it 'sets time estimate to zeor on save' do
|
||||
expect(issue.read_attribute(:time_estimate)).to be_nil
|
||||
|
||||
issue.save!
|
||||
|
||||
expect(issue.reload.read_attribute(:time_estimate)).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#supports_move_and_clone?' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be_with_refind(:issue) { create(:incident, project: project) }
|
||||
|
|
|
|||
|
|
@ -13,14 +13,14 @@ RSpec.describe ProjectStatistics do
|
|||
|
||||
describe 'scopes' do
|
||||
describe '.for_project_ids' do
|
||||
it 'returns only requested projects', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/448381' do
|
||||
it 'returns only requested projects' do
|
||||
stats = create_list(:project_statistics, 3)
|
||||
project_ids = stats[0..1].map(&:project_id)
|
||||
expected_ids = stats[0..1].map(&:id)
|
||||
|
||||
requested_stats = described_class.for_project_ids(project_ids).pluck(:id)
|
||||
|
||||
expect(requested_stats).to eq(expected_ids)
|
||||
expect(requested_stats).to match_array(expected_ids)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -472,8 +472,7 @@ RSpec.describe Issues::CreateService, feature_category: :team_planning do
|
|||
iid: { current: kind_of(Integer), previous: nil },
|
||||
project_id: { current: project.id, previous: nil },
|
||||
title: { current: opts[:title], previous: nil },
|
||||
updated_at: { current: kind_of(Time), previous: nil },
|
||||
time_estimate: { current: 0, previous: nil }
|
||||
updated_at: { current: kind_of(Time), previous: nil }
|
||||
},
|
||||
object_attributes: include(
|
||||
opts.merge(
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
- ee/spec/services/ee/users/create_service_spec.rb
|
||||
- ee/spec/services/users/service_accounts/create_service_spec.rb
|
||||
- ee/spec/services/epics/update_dates_service_spec.rb
|
||||
- ee/spec/services/gitlab_subscriptions/trials/create_service_spec.rb
|
||||
- spec/controllers/admin/groups_controller_spec.rb
|
||||
- spec/controllers/admin/users_controller_spec.rb
|
||||
- spec/controllers/groups_controller_spec.rb
|
||||
|
|
|
|||
51
tests.yml
51
tests.yml
|
|
@ -49,12 +49,6 @@ mapping:
|
|||
- source: 'config/initializers/(?<rest>.+)\.rb'
|
||||
test: 'spec/initializers/%{rest}_spec.rb'
|
||||
|
||||
# Snowplow definitions map to schema validation spec
|
||||
- source:
|
||||
- '(?<prefix>ee/)?config/events/.+\.yml'
|
||||
- 'config/events/schema\.json'
|
||||
test: 'spec/lib/gitlab/tracking/event_definition_validator_validate_all_spec.rb'
|
||||
|
||||
# DB structure should map to schema spec
|
||||
- source: 'db/structure\.sql'
|
||||
test: 'spec/db/schema_spec.rb'
|
||||
|
|
@ -136,13 +130,46 @@ mapping:
|
|||
test:
|
||||
- 'ee/spec/requests/api/graphql/remote_development/workspace/with_id_arg_spec.rb'
|
||||
|
||||
# Any change to metrics definition should trigger the specs in the ee/spec/config/metrics/ folder.
|
||||
#
|
||||
# Note: We only have those tests for ee, even though we have non-ee metrics.
|
||||
#
|
||||
# Usage metric schema changes should trigger validations for all metrics and tooling
|
||||
- source: 'config/metrics/schema/.*\.json'
|
||||
test:
|
||||
- 'spec/config/metrics/every_metric_definition_spec.rb'
|
||||
- 'ee/spec/config/metrics/every_metric_definition_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metric_definition_validate_all_spec.rb'
|
||||
- 'spec/scripts/internal_events/cli_spec.rb'
|
||||
- 'spec/support_specs/matchers/internal_events_matchers_spec.rb'
|
||||
|
||||
# Internal events schema changes should trigger validations for all events and tooling
|
||||
- source: 'config/events/schema\.json'
|
||||
test:
|
||||
- 'spec/lib/gitlab/tracking/event_definition_validator_validate_all_spec.rb'
|
||||
- 'spec/scripts/internal_events/cli_spec.rb'
|
||||
- 'spec/support_specs/matchers/internal_events_matchers_spec.rb'
|
||||
|
||||
# Any change to metric/event definitions should trigger the specs in the ee/spec/config/metrics/ folder.
|
||||
# See https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues/287#note_1192008962
|
||||
- source: 'ee/config/metrics/.*\.yml'
|
||||
test: 'ee/spec/config/metrics/every_metric_definition_spec.rb'
|
||||
- source:
|
||||
- '(ee/)?config/metrics/.*\.yml'
|
||||
test:
|
||||
- 'spec/config/metrics/every_metric_definition_spec.rb'
|
||||
- 'ee/spec/config/metrics/every_metric_definition_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metric_definition_validate_all_spec.rb'
|
||||
|
||||
# Internal event/Snowplow event definitions map to schema validation spec
|
||||
- source:
|
||||
- '(ee/)?config/events/.+\.yml'
|
||||
test: 'spec/lib/gitlab/tracking/event_definition_validator_validate_all_spec.rb'
|
||||
|
||||
# Changes to Internal Events behavior should trigger tests for the CLI and shared matchers
|
||||
- source:
|
||||
- 'lib/gitlab/internal_events\.rb'
|
||||
- 'scripts/internal_events/cli(/.+)?\.rb'
|
||||
- 'spec/support/shared_examples/controllers/internal_event_tracking_examples\.rb'
|
||||
- 'spec/support/matchers/internal_events_matchers\.rb'
|
||||
- 'spec/fixtures/scripts/internal_events/.+\.yml'
|
||||
test:
|
||||
- 'spec/scripts/internal_events/cli_spec.rb'
|
||||
- 'spec/support_specs/matchers/internal_events_matchers_spec.rb'
|
||||
|
||||
# See https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/issues/146
|
||||
- source: 'config/feature_categories\.yml'
|
||||
|
|
|
|||
39
yarn.lock
39
yarn.lock
|
|
@ -1315,10 +1315,10 @@
|
|||
core-js "^3.29.1"
|
||||
mitt "^3.0.1"
|
||||
|
||||
"@gitlab/eslint-plugin@20.2.0":
|
||||
version "20.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-20.2.0.tgz#12cefe534ef3204b800c4793ae5fbbd2d34f7d50"
|
||||
integrity sha512-euERL6TPP9GrUCXJqZ3KvUsBdhh88pBtTxQMZa3RuRL+TwMBskwCZY+XQnXKKwiz55olZU6ZbUUN0NE9G4Ox0g==
|
||||
"@gitlab/eslint-plugin@20.2.1":
|
||||
version "20.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-20.2.1.tgz#2414f621ef3d88e90405436460febbfb247b4f91"
|
||||
integrity sha512-n0ISWpRI/h6ZO8bfX10EY4u3y31Pdo9T2ieaRo1dSaC0nz7ZXHd23ykJ2gQDmYogjh6EHGS/SiqL1chMA4C+eQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin" "^7.14.1"
|
||||
eslint-config-airbnb-base "^15.0.0"
|
||||
|
|
@ -13104,7 +13104,16 @@ string-length@^4.0.1:
|
|||
char-regex "^1.0.2"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
|
|
@ -13157,7 +13166,7 @@ string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@~1.1.1:
|
|||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
|
|
@ -13171,6 +13180,13 @@ strip-ansi@^5.2.0:
|
|||
dependencies:
|
||||
ansi-regex "^4.1.0"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.1, strip-ansi@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||
|
|
@ -14862,7 +14878,7 @@ worker-loader@^3.0.8:
|
|||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
|
|
@ -14880,6 +14896,15 @@ wrap-ansi@^6.2.0:
|
|||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
|
|
|
|||
Loading…
Reference in New Issue