Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
08c811d7ce
commit
172b9765ba
|
|
@ -110,8 +110,6 @@ export default {
|
|||
'app/assets/javascripts/members/components/filter_sort/sort_dropdown.vue',
|
||||
'app/assets/javascripts/merge_request_dashboard/components/status_badge.vue',
|
||||
'app/assets/javascripts/merge_requests/components/reviewers/reviewer_drawer.vue',
|
||||
'app/assets/javascripts/milestones/components/delete_milestone_modal.vue',
|
||||
'app/assets/javascripts/milestones/components/more_actions_dropdown.vue',
|
||||
'app/assets/javascripts/ml/experiment_tracking/components/candidate_list.vue',
|
||||
'app/assets/javascripts/ml/experiment_tracking/components/delete_button.vue',
|
||||
'app/assets/javascripts/ml/experiment_tracking/routes/candidates/promote/model_selection_dropdown.vue',
|
||||
|
|
@ -240,9 +238,6 @@ export default {
|
|||
'app/assets/javascripts/vue_merge_request_widget/widgets/accessibility/index.vue',
|
||||
'app/assets/javascripts/vue_merge_request_widget/widgets/code_quality/index.vue',
|
||||
'app/assets/javascripts/webhooks/components/form_custom_header_item.vue',
|
||||
'app/assets/javascripts/work_items/components/create_work_item.vue',
|
||||
'app/assets/javascripts/work_items/components/work_item_detail.vue',
|
||||
'app/assets/javascripts/work_items/components/work_item_development/work_item_create_branch_merge_request_modal.vue',
|
||||
'ee/app/assets/javascripts/admin/subscriptions/show/components/subscription_breakdown.vue',
|
||||
'ee/app/assets/javascripts/ai/components/duo_chat_feedback_modal.vue',
|
||||
'ee/app/assets/javascripts/ai/components/user_feedback.vue',
|
||||
|
|
@ -289,7 +284,6 @@ export default {
|
|||
'ee/app/assets/javascripts/dependencies/components/dependency_project_count.vue',
|
||||
'ee/app/assets/javascripts/environments_dashboard/components/dashboard/dashboard.vue',
|
||||
'ee/app/assets/javascripts/environments_dashboard/components/dashboard/environment.vue',
|
||||
'ee/app/assets/javascripts/epic/components/epic_header.vue',
|
||||
'ee/app/assets/javascripts/external_issues_show/components/note.vue',
|
||||
'ee/app/assets/javascripts/geo_sites/components/header/geo_site_last_updated.vue',
|
||||
'ee/app/assets/javascripts/groups/settings/compliance_frameworks/components/form_modal.vue',
|
||||
|
|
@ -300,7 +294,6 @@ export default {
|
|||
'ee/app/assets/javascripts/integrations/edit/components/jira_issue_creation_vulnerabilities.vue',
|
||||
'ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue',
|
||||
'ee/app/assets/javascripts/invite_members/components/invite_modal_base.vue',
|
||||
'ee/app/assets/javascripts/issues/components/related_feature_flags.vue',
|
||||
'ee/app/assets/javascripts/iterations/components/iteration_cadence_list_item.vue',
|
||||
'ee/app/assets/javascripts/iterations/components/iteration_form.vue',
|
||||
'ee/app/assets/javascripts/iterations/components/iteration_report.vue',
|
||||
|
|
@ -328,10 +321,6 @@ export default {
|
|||
'ee/app/assets/javascripts/projects/components/move_personal_project_to_group_modal.vue',
|
||||
'ee/app/assets/javascripts/projects/merge_requests/blocking_mr_input_root.vue',
|
||||
'ee/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue',
|
||||
'ee/app/assets/javascripts/related_items_tree/components/create_epic_form.vue',
|
||||
'ee/app/assets/javascripts/related_items_tree/components/related_items_tree_app.vue',
|
||||
'ee/app/assets/javascripts/related_items_tree/components/related_items_tree_header_actions.vue',
|
||||
'ee/app/assets/javascripts/related_items_tree/components/tree_root.vue',
|
||||
'ee/app/assets/javascripts/requirements/components/export_requirements_modal.vue',
|
||||
'ee/app/assets/javascripts/requirements/components/import_requirements_modal.vue',
|
||||
'ee/app/assets/javascripts/requirements/components/requirements_root.vue',
|
||||
|
|
|
|||
|
|
@ -3158,9 +3158,15 @@
|
|||
rules:
|
||||
- <<: *if-merge-request-labels-pipeline-expedite
|
||||
when: never
|
||||
- !reference [".prevent-tier-2-and-above", rules]
|
||||
- <<: *if-merge-request
|
||||
changes: *code-patterns
|
||||
- <<: *if-merge-request # no code changes, but has doc changes
|
||||
changes:
|
||||
- "doc/**/*"
|
||||
when: never
|
||||
- <<: *if-merge-request-tier-1
|
||||
allow_failure: true
|
||||
- !reference [".prevent-tier-2-and-above", rules]
|
||||
# If we cannot find a tier label, run this job
|
||||
#
|
||||
# This could be the very first pipeline of a merge request if no
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<!-- See Pipelines for the GitLab project: https://docs.gitlab.com/ee/development/pipelines -->
|
||||
<!-- When in doubt about a Pipeline configuration change, feel free to ping @gl-dx/eng-prod. -->
|
||||
<!-- When in doubt about a Pipeline configuration change, feel free to ping @gl-dx/pipeline-maintainers. -->
|
||||
|
||||
## What does this MR do?
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
0.0.34
|
||||
0.0.35
|
||||
|
|
|
|||
|
|
@ -1,28 +1,158 @@
|
|||
<script>
|
||||
import { mapActions, mapState } from 'pinia';
|
||||
import { GlDrawer } from '@gitlab/ui';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapGetters as mapVuexGetters } from 'vuex';
|
||||
import {
|
||||
GlDrawer,
|
||||
GlModal,
|
||||
GlButton,
|
||||
GlFormRadioGroup,
|
||||
GlTooltipDirective as GlTooltip,
|
||||
GlLoadingIcon,
|
||||
GlFormInput,
|
||||
GlForm,
|
||||
} from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
import { createAlert } from '~/alert';
|
||||
import PreviewItem from '~/batch_comments/components/preview_item.vue';
|
||||
import { useBatchComments } from '~/batch_comments/store';
|
||||
import { setUrlParams, visitUrl } from '~/lib/utils/url_utility';
|
||||
import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
|
||||
import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
|
||||
import { scrollToElement } from '~/lib/utils/common_utils';
|
||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||
import { fetchPolicies } from '~/lib/graphql';
|
||||
import toast from '~/vue_shared/plugins/global_toast';
|
||||
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
|
||||
import MarkdownHeaderDivider from '~/vue_shared/components/markdown/header_divider.vue';
|
||||
import { trackSavedUsingEditor } from '~/vue_shared/components/markdown/tracking';
|
||||
import { CLEAR_AUTOSAVE_ENTRY_EVENT, CONTENT_EDITOR_PASTE } from '~/vue_shared/constants';
|
||||
import markdownEditorEventHub from '~/vue_shared/components/markdown/eventhub';
|
||||
import { updateText } from '~/lib/utils/text_markdown';
|
||||
import userCanApproveQuery from '../queries/can_approve.query.graphql';
|
||||
|
||||
const REVIEW_STATES = {
|
||||
REVIEWED: 'reviewed',
|
||||
REQUESTED_CHANGES: 'requested_changes',
|
||||
APPROVED: 'approved',
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'ReviewDrawer',
|
||||
components: { GlDrawer, PreviewItem },
|
||||
apollo: {
|
||||
userPermissions: {
|
||||
fetchPolicy: fetchPolicies.NETWORK_ONLY,
|
||||
query: userCanApproveQuery,
|
||||
variables() {
|
||||
return {
|
||||
projectPath: this.projectPath.replace(/^\//, ''),
|
||||
iid: `${this.getNoteableData.iid}`,
|
||||
};
|
||||
},
|
||||
update: (data) => data.project?.mergeRequest?.userPermissions,
|
||||
skip() {
|
||||
return !this.drawerOpened;
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
GlDrawer,
|
||||
GlButton,
|
||||
GlFormRadioGroup,
|
||||
GlModal,
|
||||
GlLoadingIcon,
|
||||
GlFormInput,
|
||||
GlForm,
|
||||
PreviewItem,
|
||||
MarkdownEditor,
|
||||
MarkdownHeaderDivider,
|
||||
ApprovalPassword: () => import('ee_component/batch_comments/components/approval_password.vue'),
|
||||
SummarizeMyReview: () =>
|
||||
import('ee_component/batch_comments/components/summarize_my_review.vue'),
|
||||
},
|
||||
directives: { GlTooltip },
|
||||
inject: {
|
||||
canSummarize: { default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isSubmitting: false,
|
||||
summarizeReviewLoading: false,
|
||||
showMarkdownEditor: false,
|
||||
userPermissions: {},
|
||||
noteData: {
|
||||
noteable_type: '',
|
||||
noteable_id: '',
|
||||
note: '',
|
||||
approve: false,
|
||||
approval_password: '',
|
||||
reviewer_state: REVIEW_STATES.REVIEWED,
|
||||
},
|
||||
discarding: false,
|
||||
showDiscardModal: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(useLegacyDiffs, ['viewDiffsFileByFile']),
|
||||
...mapState(useLegacyDiffs, ['viewDiffsFileByFile', 'projectPath']),
|
||||
...mapState(useBatchComments, ['sortedDrafts', 'draftsCount', 'drawerOpened']),
|
||||
...mapVuexGetters(['getNoteableData', 'getNotesData', 'getCurrentUserLastNote']),
|
||||
getDrawerHeaderHeight() {
|
||||
if (!this.drawerOpened) return '0';
|
||||
|
||||
return getContentWrapperHeight();
|
||||
},
|
||||
autocompleteDataSources() {
|
||||
return gl.GfmAutoComplete?.dataSources;
|
||||
},
|
||||
autosaveKey() {
|
||||
return `submit_review_dropdown/${this.getNoteableData.id}`;
|
||||
},
|
||||
radioGroupOptions() {
|
||||
return [
|
||||
{
|
||||
html: [
|
||||
__('Comment'),
|
||||
`<p class="help-text">
|
||||
${__('Submit general feedback without explicit approval.')}
|
||||
</p>`,
|
||||
].join('<br />'),
|
||||
value: REVIEW_STATES.REVIEWED,
|
||||
},
|
||||
{
|
||||
html: [
|
||||
__('Approve'),
|
||||
`<p class="help-text">
|
||||
${__('Submit feedback and approve these changes.')}
|
||||
</p>`,
|
||||
].join('<br />'),
|
||||
value: REVIEW_STATES.APPROVED,
|
||||
disabled: !this.userPermissions.canApprove,
|
||||
},
|
||||
{
|
||||
html: [
|
||||
__('Request changes'),
|
||||
`<p class="help-text">
|
||||
${__('Submit feedback that should be addressed before merging.')}
|
||||
</p>`,
|
||||
].join('<br />'),
|
||||
value: REVIEW_STATES.REQUESTED_CHANGES,
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.noteData.noteable_type = this.noteableType;
|
||||
this.noteData.noteable_id = this.getNoteableData.id;
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useLegacyDiffs, ['goToFile']),
|
||||
...mapActions(useBatchComments, ['scrollToDraft', 'setDrawerOpened']),
|
||||
...mapActions(useBatchComments, [
|
||||
'scrollToDraft',
|
||||
'setDrawerOpened',
|
||||
'publishReview',
|
||||
'discardDrafts',
|
||||
'clearDrafts',
|
||||
]),
|
||||
async onClickDraft(draft) {
|
||||
if (this.viewDiffsFileByFile) {
|
||||
await this.goToFile({ path: draft.file_path });
|
||||
|
|
@ -36,8 +166,107 @@ export default {
|
|||
await this.scrollToDraft(draft);
|
||||
}
|
||||
},
|
||||
async submitReview() {
|
||||
this.isSubmitting = true;
|
||||
if (this.userLastNoteWatcher) this.userLastNoteWatcher();
|
||||
|
||||
if (this.$refs.markdownEditor) {
|
||||
trackSavedUsingEditor(
|
||||
this.$refs.markdownEditor.isContentEditorActive,
|
||||
'MergeRequest_review',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const { note, reviewer_state: reviewerState } = this.noteData;
|
||||
|
||||
await this.publishReview({ ...this.noteData });
|
||||
|
||||
markdownEditorEventHub.$emit(CLEAR_AUTOSAVE_ENTRY_EVENT, this.autosaveKey);
|
||||
|
||||
this.noteData.note = '';
|
||||
this.noteData.reviewer_state = REVIEW_STATES.REVIEWED;
|
||||
this.noteData.approval_password = '';
|
||||
|
||||
if (note) {
|
||||
this.userLastNoteWatcher = this.$watch(
|
||||
'getCurrentUserLastNote',
|
||||
() => {
|
||||
if (note) {
|
||||
window.location.hash = `note_${this.getCurrentUserLastNote.id}`;
|
||||
}
|
||||
|
||||
window.mrTabs?.tabShown('show');
|
||||
|
||||
setTimeout(() => {
|
||||
scrollToElement(document.getElementById(`note_${this.getCurrentUserLastNote.id}`));
|
||||
|
||||
this.clearDrafts();
|
||||
this.setDrawerOpened(false);
|
||||
this.isSubmitting = false;
|
||||
});
|
||||
|
||||
this.userLastNoteWatcher();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
} else {
|
||||
if (reviewerState === REVIEW_STATES.APPROVED) {
|
||||
window.mrTabs?.tabShown('show');
|
||||
}
|
||||
|
||||
this.clearDrafts();
|
||||
this.setDrawerOpened(false);
|
||||
this.isSubmitting = false;
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.data?.message) {
|
||||
createAlert({ message: e.data.message, captureError: true, error: e });
|
||||
}
|
||||
|
||||
this.isSubmitting = false;
|
||||
}
|
||||
},
|
||||
async discardReviews() {
|
||||
this.discarding = true;
|
||||
|
||||
try {
|
||||
await this.discardDrafts();
|
||||
|
||||
toast(__('Review discarded'));
|
||||
} finally {
|
||||
this.discarding = false;
|
||||
}
|
||||
},
|
||||
updateNote(note) {
|
||||
const textArea = this.$el.querySelector('textarea');
|
||||
|
||||
if (textArea) {
|
||||
updateText({
|
||||
textArea,
|
||||
tag: note,
|
||||
cursorOffset: 0,
|
||||
wrap: false,
|
||||
});
|
||||
} else {
|
||||
markdownEditorEventHub.$emit(CONTENT_EDITOR_PASTE, note);
|
||||
}
|
||||
},
|
||||
},
|
||||
DRAWER_Z_INDEX,
|
||||
modal: {
|
||||
cancelAction: { text: __('Keep review') },
|
||||
primaryAction: { text: __('Discard review'), attributes: { variant: 'danger' } },
|
||||
},
|
||||
formFieldProps: {
|
||||
id: 'review-note-body',
|
||||
name: 'review[note]',
|
||||
placeholder: __('Write a comment or drag your files here…'),
|
||||
'aria-label': __('Comment'),
|
||||
'data-testid': 'comment-textarea',
|
||||
},
|
||||
restrictedToolbarItems: ['full-screen'],
|
||||
REVIEW_STATES,
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -53,6 +282,93 @@ export default {
|
|||
<template #title>
|
||||
<h4 class="gl-m-0">{{ __('Submit your review') }}</h4>
|
||||
</template>
|
||||
<div class="gl-border-b-0">
|
||||
<h5 class="h6 gl-mb-5 gl-mt-0">
|
||||
{{ __('Review approval') }}
|
||||
</h5>
|
||||
<gl-form data-testid="submit-gl-form" @submit.prevent="submitReview">
|
||||
<gl-form-radio-group
|
||||
v-model="noteData.reviewer_state"
|
||||
:options="radioGroupOptions"
|
||||
class="gl-mt-4"
|
||||
data-testid="reviewer_states"
|
||||
/>
|
||||
<approval-password
|
||||
v-if="userPermissions.canApprove && getNoteableData.require_password_to_approve"
|
||||
v-show="noteData.reviewer_state === $options.REVIEW_STATES.APPROVED"
|
||||
v-model="noteData.approval_password"
|
||||
class="gl-mt-3"
|
||||
data-testid="approve_password"
|
||||
/>
|
||||
<div v-if="showMarkdownEditor" class="common-note-form gfm-form gl-mb-5 gl-mt-3">
|
||||
<markdown-editor
|
||||
ref="markdownEditor"
|
||||
v-model="noteData.note"
|
||||
class="js-no-autosize"
|
||||
:is-submitting="isSubmitting"
|
||||
:render-markdown-path="getNoteableData.preview_note_path"
|
||||
:markdown-docs-path="getNotesData.markdownDocsPath"
|
||||
:form-field-props="$options.formFieldProps"
|
||||
enable-autocomplete
|
||||
:autocomplete-data-sources="autocompleteDataSources"
|
||||
:disabled="isSubmitting"
|
||||
:restricted-tool-bar-items="$options.restrictedToolbarItems"
|
||||
:force-autosize="false"
|
||||
:autosave-key="autosaveKey"
|
||||
supports-quick-actions
|
||||
autofocus
|
||||
@input="$emit('input', $event)"
|
||||
@keydown.meta.enter="submitReview"
|
||||
@keydown.ctrl.enter="submitReview"
|
||||
>
|
||||
<template v-if="canSummarize" #header-buttons>
|
||||
<markdown-header-divider class="gl-ml-2" />
|
||||
<summarize-my-review
|
||||
:id="getNoteableData.id"
|
||||
v-model="summarizeReviewLoading"
|
||||
@input="updateNote"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="summarizeReviewLoading" #toolbar>
|
||||
<div class="gl-ml-auto gl-mr-2 gl-inline-flex">
|
||||
{{ __('Generating review summary') }}
|
||||
<gl-loading-icon class="gl-ml-2 gl-mt-2" />
|
||||
</div>
|
||||
</template>
|
||||
</markdown-editor>
|
||||
</div>
|
||||
<gl-form-input
|
||||
v-else
|
||||
class="reply-placeholder-input-field gl-mb-5 gl-mt-3"
|
||||
:placeholder="__('Add optional summary content…')"
|
||||
data-testid="placeholder-input-field"
|
||||
@focus="showMarkdownEditor = true"
|
||||
/>
|
||||
<div class="gl-mt-3 gl-flex gl-gap-3">
|
||||
<gl-button
|
||||
type="submit"
|
||||
variant="confirm"
|
||||
:loading="isSubmitting"
|
||||
data-testid="submit-review-button"
|
||||
>
|
||||
{{ __('Submit review') }}
|
||||
</gl-button>
|
||||
<gl-button @click="setDrawerOpened(false)">{{ __('Continue review') }}</gl-button>
|
||||
<gl-button
|
||||
v-if="draftsCount > 0"
|
||||
v-gl-tooltip
|
||||
icon="remove"
|
||||
category="tertiary"
|
||||
class="gl-ml-auto"
|
||||
:title="__('Discard review')"
|
||||
:aria-label="__('Discard review')"
|
||||
:loading="discarding"
|
||||
data-testid="discard-review-btn"
|
||||
@click="showDiscardModal = true"
|
||||
/>
|
||||
</div>
|
||||
</gl-form>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="h6 gl-mb-5 gl-mt-0" data-testid="reviewer-drawer-heading">
|
||||
<template v-if="draftsCount > 0">
|
||||
|
|
@ -70,5 +386,20 @@ export default {
|
|||
@click="onClickDraft"
|
||||
/>
|
||||
</div>
|
||||
<gl-modal
|
||||
v-model="showDiscardModal"
|
||||
modal-id="discard-review-modal"
|
||||
:title="__('Discard pending review?')"
|
||||
:action-primary="$options.modal.primaryAction"
|
||||
:action-cancel="$options.modal.cancelAction"
|
||||
data-testid="discard-review-modal"
|
||||
@primary="discardReviews"
|
||||
>
|
||||
{{
|
||||
__(
|
||||
'Are you sure you want to discard your pending review comments? This action cannot be undone.',
|
||||
)
|
||||
}}
|
||||
</gl-modal>
|
||||
</gl-drawer>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -26,10 +26,6 @@ export default {
|
|||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
milestoneId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
milestoneTitle: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
isDropdownVisible: false,
|
||||
isPromotionModalVisible: false,
|
||||
isDeleteModalVisible: false,
|
||||
isPromoteModalVisible: false,
|
||||
};
|
||||
|
|
@ -52,9 +51,6 @@ export default {
|
|||
widthClasses() {
|
||||
return this.size === 'small' ? 'gl-min-w-6' : 'gl-min-w-7';
|
||||
},
|
||||
hasUrl() {
|
||||
return this.editUrl || this.closeUrl || this.reopenUrl || this.promoteUrl;
|
||||
},
|
||||
copiedToClipboard() {
|
||||
return this.$options.i18n.copiedToClipboard;
|
||||
},
|
||||
|
|
@ -239,7 +235,6 @@ export default {
|
|||
:visible="isDeleteModalVisible"
|
||||
:issue-count="issueCount"
|
||||
:merge-request-count="mergeRequestCount"
|
||||
:milestone-id="id"
|
||||
:milestone-title="title"
|
||||
:milestone-url="milestoneUrl"
|
||||
@deleteModalVisible="setDeleteModalVisibility"
|
||||
|
|
|
|||
|
|
@ -90,6 +90,11 @@ const initReviewDrawer = () => {
|
|||
el,
|
||||
pinia,
|
||||
store,
|
||||
apolloProvider,
|
||||
provide: {
|
||||
newCommentTemplatePaths: JSON.parse(el.dataset.newCommentTemplatePaths),
|
||||
canSummarize: parseBoolean(el.dataset.canSummarize),
|
||||
},
|
||||
render(h) {
|
||||
return h(ReviewDrawer);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ export default {
|
|||
projectsLoadingError: false,
|
||||
sortKey: this.customSortKey ?? 'STORAGE_SIZE_DESC',
|
||||
initialSortBy: this.customSortKey ? null : 'storage',
|
||||
enableSortableFields: !this.customSortKey,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -191,7 +190,6 @@ export default {
|
|||
:is-loading="$apollo.queries.projects.loading"
|
||||
:help-links="helpLinks"
|
||||
:sort-by="initialSortBy"
|
||||
:enable-sortable-fields="enableSortableFields"
|
||||
@sortChanged="onSortChanged"
|
||||
/>
|
||||
<div class="gl-mt-5 gl-flex gl-justify-center">
|
||||
|
|
|
|||
|
|
@ -46,27 +46,23 @@ export default {
|
|||
required: false,
|
||||
default: null,
|
||||
},
|
||||
enableSortableFields: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.fields = [
|
||||
{ key: 'name', label: __('Project') },
|
||||
{ key: 'storage', label: __('Total'), sortable: this.enableSortableFields },
|
||||
{ key: 'repository', label: __('Repository'), sortable: this.enableSortableFields },
|
||||
{ key: 'snippets', label: __('Snippets'), sortable: this.enableSortableFields },
|
||||
{ key: 'buildArtifacts', label: __('Job artifacts'), sortable: this.enableSortableFields },
|
||||
{ key: 'lfsObjects', label: __('LFS'), sortable: this.enableSortableFields },
|
||||
{ key: 'packages', label: __('Packages'), sortable: this.enableSortableFields },
|
||||
{ key: 'wiki', label: __('Wiki'), sortable: this.enableSortableFields },
|
||||
{ key: 'storage', label: __('Total'), sortable: true },
|
||||
{ key: 'repository', label: __('Repository'), sortable: true },
|
||||
{ key: 'snippets', label: __('Snippets'), sortable: true },
|
||||
{ key: 'buildArtifacts', label: __('Job artifacts'), sortable: true },
|
||||
{ key: 'lfsObjects', label: __('LFS'), sortable: true },
|
||||
{ key: 'packages', label: __('Packages'), sortable: true },
|
||||
{ key: 'wiki', label: __('Wiki'), sortable: true },
|
||||
{
|
||||
key: 'containerRegistry',
|
||||
label: __('Containers'),
|
||||
thClass: '!gl-border-l',
|
||||
tdClass: '!gl-border-l',
|
||||
sortable: this.enableSortableFields,
|
||||
sortable: true,
|
||||
},
|
||||
].map((f) => ({
|
||||
...f,
|
||||
|
|
|
|||
|
|
@ -420,9 +420,6 @@ export default {
|
|||
canUpdate() {
|
||||
return this.workItem?.userPermissions?.updateWorkItem;
|
||||
},
|
||||
workItemType() {
|
||||
return this.workItem?.workItemType?.name;
|
||||
},
|
||||
workItemParticipantNodes() {
|
||||
return this.workItemParticipants?.participants?.nodes ?? [];
|
||||
},
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ import {
|
|||
WIDGET_TYPE_DESIGNS,
|
||||
WORK_ITEM_REFERENCE_CHAR,
|
||||
WORK_ITEM_TYPE_NAME_EPIC,
|
||||
WIDGET_TYPE_WEIGHT,
|
||||
WIDGET_TYPE_DEVELOPMENT,
|
||||
STATE_OPEN,
|
||||
WIDGET_TYPE_ERROR_TRACKING,
|
||||
|
|
@ -189,8 +188,6 @@ export default {
|
|||
updateError: undefined,
|
||||
workItem: {},
|
||||
updateInProgress: false,
|
||||
modalWorkItemIid: getParameterByName('work_item_iid'),
|
||||
modalWorkItemNamespaceFullPath: '',
|
||||
isReportModalOpen: false,
|
||||
reportedUrl: '',
|
||||
reportedUserId: 0,
|
||||
|
|
@ -384,12 +381,6 @@ export default {
|
|||
parentWorkItemConfidentiality() {
|
||||
return this.parentWorkItem?.confidential;
|
||||
},
|
||||
parentWorkItemType() {
|
||||
return this.parentWorkItem?.workItemType?.name;
|
||||
},
|
||||
workItemIconName() {
|
||||
return this.workItem.workItemType?.iconName;
|
||||
},
|
||||
hasDescriptionWidget() {
|
||||
return this.findWidget(WIDGET_TYPE_DESCRIPTION);
|
||||
},
|
||||
|
|
@ -429,9 +420,6 @@ export default {
|
|||
workItemNotes() {
|
||||
return this.findWidget(WIDGET_TYPE_NOTES);
|
||||
},
|
||||
workItemWeight() {
|
||||
return this.findWidget(WIDGET_TYPE_WEIGHT);
|
||||
},
|
||||
workItemDevelopment() {
|
||||
return this.findWidget(WIDGET_TYPE_DEVELOPMENT);
|
||||
},
|
||||
|
|
@ -843,17 +831,6 @@ export default {
|
|||
this.isValidDragDataType(event);
|
||||
if (this.isDesignUploadButtonInViewport) this.isEmptyStateVisible = true;
|
||||
},
|
||||
onDragLeave(event) {
|
||||
const emptyStateDesignDropzone =
|
||||
this.$refs.emptyStateDesignDropzone?.$el || this.$refs.emptyStateDesignDropzone;
|
||||
if (!emptyStateDesignDropzone.contains(event.relatedTarget)) {
|
||||
this.dragCounter -= 1;
|
||||
}
|
||||
|
||||
if (this.dragCounter === 0) {
|
||||
this.isEmptyStateVisible = false; // Hide dropzone
|
||||
}
|
||||
},
|
||||
onDragLeaveMain(event) {
|
||||
// Check if the drag is leaving the main container entirely
|
||||
const mainContainerRef = this.$refs.workItemDetail;
|
||||
|
|
@ -1127,7 +1104,6 @@ export default {
|
|||
</gl-intersection-observer>
|
||||
<work-item-create-branch-merge-request-split-button
|
||||
v-if="showCreateBranchMergeRequestSplitButton"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-iid="iid"
|
||||
:work-item-full-path="workItemFullPath"
|
||||
:work-item-type="workItem.workItemType.name"
|
||||
|
|
@ -1184,7 +1160,6 @@ export default {
|
|||
<template #empty-state>
|
||||
<design-dropzone
|
||||
v-if="isEmptyStateVisible && !isSaving && isDragDataValid && !isAddingNotes"
|
||||
ref="emptyStateDesignDropzone"
|
||||
class="gl-relative gl-mt-5"
|
||||
show-upload-design-overlay
|
||||
validate-design-upload-on-dragover
|
||||
|
|
|
|||
|
|
@ -62,19 +62,10 @@ export default {
|
|||
required: false,
|
||||
default: true,
|
||||
},
|
||||
showMergeRequestFlow: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
workItemIid: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
workItemId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
workItemType: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
|
|||
|
|
@ -32,10 +32,6 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
workItemId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectId: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
@ -54,7 +50,6 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
showBranchFlow: true,
|
||||
showMergeRequestFlow: false,
|
||||
showCreateBranchAndMrModal: false,
|
||||
checkingBranchAvailibility: true,
|
||||
showCreateOptions: true,
|
||||
|
|
@ -89,10 +84,9 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
openModal(createBranch = true, createMergeRequest = false) {
|
||||
openModal(createBranch = true) {
|
||||
this.toggleCreateModal(true);
|
||||
this.showBranchFlow = createBranch;
|
||||
this.showMergeRequestFlow = createMergeRequest;
|
||||
},
|
||||
toggleCreateModal(showOrhide) {
|
||||
this.showCreateBranchAndMrModal = showOrhide;
|
||||
|
|
@ -132,9 +126,7 @@ export default {
|
|||
<work-item-create-branch-merge-request-modal
|
||||
:show-modal="showCreateBranchAndMrModal"
|
||||
:show-branch-flow="showBranchFlow"
|
||||
:show-merge-request-flow="showMergeRequestFlow"
|
||||
:work-item-iid="workItemIid"
|
||||
:work-item-id="workItemId"
|
||||
:work-item-type="workItemType"
|
||||
:work-item-full-path="workItemFullPath"
|
||||
:is-confidential-work-item="isConfidentialWorkItem"
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ export default {
|
|||
workItemDevelopment: {},
|
||||
showCreateBranchAndMrModal: false,
|
||||
showBranchFlow: true,
|
||||
showMergeRequestFlow: false,
|
||||
showCreateOptions: true,
|
||||
};
|
||||
},
|
||||
|
|
@ -227,10 +226,9 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
openModal(createBranch = true, createMergeRequest = false) {
|
||||
openModal(createBranch = true) {
|
||||
this.toggleCreateModal(true);
|
||||
this.showBranchFlow = createBranch;
|
||||
this.showMergeRequestFlow = createMergeRequest;
|
||||
},
|
||||
toggleCreateModal(showOrhide) {
|
||||
this.showCreateBranchAndMrModal = showOrhide;
|
||||
|
|
@ -301,9 +299,7 @@ export default {
|
|||
v-if="!isLoading"
|
||||
:show-modal="showCreateBranchAndMrModal"
|
||||
:show-branch-flow="showBranchFlow"
|
||||
:show-merge-request-flow="showMergeRequestFlow"
|
||||
:work-item-iid="workItemIid"
|
||||
:work-item-id="workItemId"
|
||||
:work-item-type="workItemType"
|
||||
:work-item-full-path="workItemFullPath"
|
||||
:is-confidential-work-item="isConfidentialWorkItem"
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ module Ci
|
|||
UNLOCKABLE_STATUSES = (Ci::Pipeline.completed_statuses + [:manual]).freeze
|
||||
# UI only shows 100+. TODO: pass constant to UI for SSoT
|
||||
COUNT_FAILED_JOBS_LIMIT = 101
|
||||
INPUTS_LIMIT = 20
|
||||
|
||||
paginates_per 15
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ module Ci
|
|||
include BatchNullifyDependentAssociations
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
MAX_INPUTS = 20
|
||||
VALID_REF_FORMAT_REGEX = %r{\A(#{Gitlab::Git::TAG_REF_PREFIX}|#{Gitlab::Git::BRANCH_REF_PREFIX})[\S]+}
|
||||
|
||||
SORT_ORDERS = {
|
||||
|
|
@ -46,7 +45,7 @@ module Ci
|
|||
validates :inputs, nested_attributes_duplicates: { child_attributes: %i[name] }
|
||||
|
||||
validates :inputs, length: {
|
||||
maximum: MAX_INPUTS,
|
||||
maximum: Ci::Pipeline::INPUTS_LIMIT,
|
||||
message: ->(*) { _('exceeds the limit of %{count}.') }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ module Ci
|
|||
|
||||
def self.pluck_identifiers(pipeline_schedule_id, inputs_names)
|
||||
where(pipeline_schedule_id: pipeline_schedule_id, name: inputs_names)
|
||||
.limit(PipelineSchedule::MAX_INPUTS)
|
||||
.limit(Ci::Pipeline::INPUTS_LIMIT)
|
||||
.pluck(:id, :name)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -165,43 +165,38 @@ class Namespace < ApplicationRecord
|
|||
|
||||
delegate :name, to: :owner, allow_nil: true, prefix: true
|
||||
delegate :avatar_url, to: :owner, allow_nil: true
|
||||
delegate :prevent_sharing_groups_outside_hierarchy, :prevent_sharing_groups_outside_hierarchy=,
|
||||
to: :namespace_settings, allow_nil: true
|
||||
delegate :show_diff_preview_in_email, :show_diff_preview_in_email?, :show_diff_preview_in_email=,
|
||||
to: :namespace_settings
|
||||
delegate :runner_registration_enabled, :runner_registration_enabled?, :runner_registration_enabled=,
|
||||
to: :namespace_settings
|
||||
delegate :emails_enabled, :emails_enabled=,
|
||||
to: :namespace_settings, allow_nil: true
|
||||
delegate :allow_runner_registration_token,
|
||||
:allow_runner_registration_token=,
|
||||
to: :namespace_settings
|
||||
|
||||
delegate :maven_package_requests_forwarding,
|
||||
:pypi_package_requests_forwarding,
|
||||
:npm_package_requests_forwarding,
|
||||
to: :package_settings
|
||||
delegate :default_branch_protection_defaults, to: :namespace_settings, allow_nil: true
|
||||
delegate :archived, :archived=, to: :namespace_settings, allow_nil: true
|
||||
delegate :math_rendering_limits_enabled,
|
||||
:lock_math_rendering_limits_enabled,
|
||||
to: :namespace_settings, allow_nil: true
|
||||
delegate :math_rendering_limits_enabled?,
|
||||
:lock_math_rendering_limits_enabled?,
|
||||
to: :namespace_settings
|
||||
|
||||
delegate :add_creator, :deleted_at, :deleted_at=,
|
||||
to: :namespace_details
|
||||
delegate :resource_access_token_notify_inherited,
|
||||
:resource_access_token_notify_inherited=,
|
||||
:lock_resource_access_token_notify_inherited,
|
||||
:lock_resource_access_token_notify_inherited=,
|
||||
:resource_access_token_notify_inherited?,
|
||||
:resource_access_token_notify_inherited_locked?,
|
||||
:resource_access_token_notify_inherited_locked_by_ancestor?,
|
||||
:resource_access_token_notify_inherited_locked_by_application_setting?,
|
||||
to: :namespace_settings
|
||||
delegate :jwt_ci_cd_job_token_enabled?,
|
||||
:job_token_policies_enabled?,
|
||||
to: :namespace_settings
|
||||
|
||||
with_options to: :namespace_settings do
|
||||
delegate :show_diff_preview_in_email, :show_diff_preview_in_email?, :show_diff_preview_in_email=
|
||||
delegate :runner_registration_enabled, :runner_registration_enabled?, :runner_registration_enabled=
|
||||
delegate :allow_runner_registration_token, :allow_runner_registration_token=
|
||||
delegate :math_rendering_limits_enabled?, :lock_math_rendering_limits_enabled?
|
||||
delegate :resource_access_token_notify_inherited,
|
||||
:resource_access_token_notify_inherited=,
|
||||
:lock_resource_access_token_notify_inherited,
|
||||
:lock_resource_access_token_notify_inherited=,
|
||||
:resource_access_token_notify_inherited?,
|
||||
:resource_access_token_notify_inherited_locked?,
|
||||
:resource_access_token_notify_inherited_locked_by_ancestor?,
|
||||
:resource_access_token_notify_inherited_locked_by_application_setting?
|
||||
delegate :jwt_ci_cd_job_token_enabled?, :job_token_policies_enabled?
|
||||
|
||||
with_options allow_nil: true do
|
||||
delegate :prevent_sharing_groups_outside_hierarchy, :prevent_sharing_groups_outside_hierarchy=
|
||||
delegate :default_branch_protection_defaults
|
||||
delegate :archived, :archived=
|
||||
delegate :math_rendering_limits_enabled, :lock_math_rendering_limits_enabled
|
||||
delegate :emails_enabled, :emails_enabled=
|
||||
end
|
||||
end
|
||||
|
||||
before_create :sync_share_with_group_lock_with_parent
|
||||
before_update :sync_share_with_group_lock_with_parent, if: :parent_changed?
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ module Organizations
|
|||
has_one :organization_detail, inverse_of: :organization, autosave: true
|
||||
|
||||
has_many :organization_users, inverse_of: :organization
|
||||
has_many :organization_user_aliases, inverse_of: :organization
|
||||
# if considering disable_joins on the below see:
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140343#note_1705047949
|
||||
has_many :users, through: :organization_users, inverse_of: :organizations
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Organizations
|
||||
class OrganizationUserAlias < ApplicationRecord
|
||||
belongs_to :organization, inverse_of: :organization_user_aliases, optional: false
|
||||
belongs_to :user, inverse_of: :organization_user_aliases, optional: false
|
||||
|
||||
validates :username, presence: true, uniqueness: { scope: :organization_id }
|
||||
end
|
||||
end
|
||||
|
|
@ -8,7 +8,6 @@ module ResourceEvents
|
|||
belongs_to :issue
|
||||
|
||||
validates :issue, presence: true
|
||||
validates :namespace_id, presence: true
|
||||
|
||||
enum action: { add: 1, remove: 2 }
|
||||
|
||||
|
|
|
|||
|
|
@ -291,6 +291,7 @@ class User < ApplicationRecord
|
|||
belongs_to :created_by, class_name: 'User', optional: true
|
||||
|
||||
has_many :organization_users, class_name: 'Organizations::OrganizationUser', inverse_of: :user
|
||||
has_many :organization_user_aliases, class_name: 'Organizations::OrganizationUserAlias', inverse_of: :user
|
||||
|
||||
has_many :organizations, through: :organization_users, class_name: 'Organizations::Organization', inverse_of: :users,
|
||||
disable_joins: true
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
.merge-request{ data: { mr_action: mr_action, url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project), lock_version: @merge_request.lock_version, diffs_batch_cache_key: @diffs_batch_cache_key } }
|
||||
= render "projects/merge_requests/mr_title"
|
||||
#js-review-drawer
|
||||
#js-review-drawer{ data: review_bar_data(@merge_request, current_user) }
|
||||
#js-merge-sticky-header{ data: { data: sticky_header_data(@project, @merge_request).to_json } }
|
||||
.merge-request-details.issuable-details{ data: { id: @merge_request.project.id } }
|
||||
= render "projects/merge_requests/mr_box"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
api_type:
|
||||
attr: lock_web_based_commit_signing_enabled
|
||||
clusterwide: false
|
||||
column: lock_web_based_commit_signing_enabled
|
||||
db_type: boolean
|
||||
default: 'false'
|
||||
description:
|
||||
encrypted: false
|
||||
gitlab_com_different_than_default: false
|
||||
jihu: false
|
||||
not_null: true
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
api_type:
|
||||
attr: web_based_commit_signing_enabled
|
||||
clusterwide: false
|
||||
column: web_based_commit_signing_enabled
|
||||
db_type: boolean
|
||||
default: 'false'
|
||||
description:
|
||||
encrypted: false
|
||||
gitlab_com_different_than_default: false
|
||||
jihu: false
|
||||
not_null: true
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
---
|
||||
migration_job_name: BackfillIssueLinkIdOnRelatedEpicLinks
|
||||
description: Sets the foreign key of related_epic_links to its correlating issue_links record.
|
||||
description: Sets the foreign key of related_epic_links to its correlating issue_links
|
||||
record.
|
||||
feature_category: team_planning
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/174444
|
||||
milestone: '17.7'
|
||||
queued_migration_version: 20241202170820
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
finalized_by: '20250504231421'
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ feature_category: team_planning
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/174399
|
||||
milestone: '17.7'
|
||||
queued_migration_version: 20241202140053
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
finalized_by: '20250501231835'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
table_name: organization_user_aliases
|
||||
classes:
|
||||
- Organizations::OrganizationUserAlias
|
||||
feature_categories:
|
||||
- cell
|
||||
description: |
|
||||
Store aliases so that internally-managed bot users can be referenced inside an organization by a convenient alias rather than unique username
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/189052
|
||||
milestone: '18.0'
|
||||
gitlab_schema: gitlab_main_cell
|
||||
sharding_key:
|
||||
organization_id: organizations
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddWebBasedCommitSigningEnabledSetting < Gitlab::Database::Migration[2.2]
|
||||
include Gitlab::Database::MigrationHelpers::CascadingNamespaceSettings
|
||||
|
||||
milestone '18.0'
|
||||
|
||||
def up
|
||||
add_cascading_namespace_setting :web_based_commit_signing_enabled, :boolean, default: false, null: false
|
||||
end
|
||||
|
||||
def down
|
||||
remove_cascading_namespace_setting :web_based_commit_signing_enabled
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateOrganizationUserAliases < Gitlab::Database::Migration[2.2]
|
||||
milestone '18.0'
|
||||
|
||||
def change
|
||||
create_table :organization_user_aliases do |t| # rubocop:disable Lint/RedundantCopDisableDirective, Migration/EnsureFactoryForTable -- factory file sometimes incorrectly detected by rubocop as organizations_organization_user_aliases
|
||||
t.belongs_to :organization, null: false, index: false
|
||||
t.belongs_to :user, null: false
|
||||
|
||||
# rubocop:disable Migration/PreventStrings -- these columns' type should be the same as User#username and User#name
|
||||
t.string :username, null: false
|
||||
t.string :display_name
|
||||
# rubocop:enable Migration/PreventStrings
|
||||
|
||||
t.timestamps_with_timezone null: false
|
||||
|
||||
t.index [:organization_id, :user_id], unique: true,
|
||||
name: :unique_organization_user_alias_organization_id_user_id
|
||||
t.index [:organization_id, :username], unique: true,
|
||||
name: :unique_organization_user_alias_organization_id_username
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddFkOrganizationUserAliasesOrganizations < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
milestone '18.0'
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :organization_user_aliases, :organizations, column: :organization_id, on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key :organization_user_aliases, column: :organization_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddFkOrganizationUserAliasesUsers < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
milestone '18.0'
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :organization_user_aliases, :users, column: :user_id, on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key :organization_user_aliases, column: :user_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FinalizeHkBackfillIssueUserMentionsNamespaceId < Gitlab::Database::Migration[2.3]
|
||||
milestone '18.0'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
|
||||
|
||||
def up
|
||||
ensure_batched_background_migration_is_finished(
|
||||
job_class_name: 'BackfillIssueUserMentionsNamespaceId',
|
||||
table_name: :issue_user_mentions,
|
||||
column_name: :id,
|
||||
job_arguments: [:namespace_id, :issues, :namespace_id, :issue_id],
|
||||
finalize: true
|
||||
)
|
||||
end
|
||||
|
||||
def down; end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FinalizeHkBackfillIssueLinkIdOnRelatedEpicLinks < Gitlab::Database::Migration[2.3]
|
||||
milestone '18.0'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
def up
|
||||
ensure_batched_background_migration_is_finished(
|
||||
job_class_name: 'BackfillIssueLinkIdOnRelatedEpicLinks',
|
||||
table_name: :related_epic_links,
|
||||
column_name: :id,
|
||||
job_arguments: [],
|
||||
finalize: true
|
||||
)
|
||||
end
|
||||
|
||||
def down; end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
c3de065d1dfc14a277ce2cff951d1b6a7f9568a9d5387cbcd28838d891477a01
|
||||
|
|
@ -0,0 +1 @@
|
|||
bc8cab73c5dd80d0436f019c4ef2cd586281ae4363dcd4eebf4c8ec6ec973499
|
||||
|
|
@ -0,0 +1 @@
|
|||
8102e65b833f63d0d6e05856bbbc7990ce9c075a8d8987140a2b1a0f46586bbf
|
||||
|
|
@ -0,0 +1 @@
|
|||
2be27e4ece5f7985a586862dbbf5ac5fd2fad57077fc5a4164ef4f43b538eb83
|
||||
|
|
@ -0,0 +1 @@
|
|||
e38366e57b5d579aef08a56e2e492ca7bb23214484eadb2c853cab26aa9c4e1b
|
||||
|
|
@ -0,0 +1 @@
|
|||
e482b24cb8a8f36edfe22399372d2b921eeba5813342315a91505ecadf8fbcb5
|
||||
|
|
@ -8966,6 +8966,8 @@ CREATE TABLE application_settings (
|
|||
group_settings jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
model_prompt_cache_enabled boolean DEFAULT true NOT NULL,
|
||||
lock_model_prompt_cache_enabled boolean DEFAULT false NOT NULL,
|
||||
web_based_commit_signing_enabled boolean DEFAULT false NOT NULL,
|
||||
lock_web_based_commit_signing_enabled boolean DEFAULT false NOT NULL,
|
||||
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
|
||||
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
|
||||
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),
|
||||
|
|
@ -17925,6 +17927,8 @@ CREATE TABLE namespace_settings (
|
|||
model_prompt_cache_enabled boolean,
|
||||
lock_model_prompt_cache_enabled boolean DEFAULT false NOT NULL,
|
||||
disable_invite_members boolean DEFAULT false NOT NULL,
|
||||
web_based_commit_signing_enabled boolean,
|
||||
lock_web_based_commit_signing_enabled boolean DEFAULT false NOT NULL,
|
||||
CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)),
|
||||
CONSTRAINT check_namespace_settings_security_policies_is_hash CHECK ((jsonb_typeof(security_policies) = 'object'::text)),
|
||||
CONSTRAINT namespace_settings_unique_project_download_limit_alertlist_size CHECK ((cardinality(unique_project_download_limit_alertlist) <= 100)),
|
||||
|
|
@ -18618,6 +18622,25 @@ CREATE TABLE organization_settings (
|
|||
settings jsonb DEFAULT '{}'::jsonb NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE organization_user_aliases (
|
||||
id bigint NOT NULL,
|
||||
organization_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
username character varying NOT NULL,
|
||||
display_name character varying,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE organization_user_aliases_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE organization_user_aliases_id_seq OWNED BY organization_user_aliases.id;
|
||||
|
||||
CREATE TABLE organization_users (
|
||||
id bigint NOT NULL,
|
||||
organization_id bigint NOT NULL,
|
||||
|
|
@ -27439,6 +27462,8 @@ ALTER TABLE ONLY organization_cluster_agent_mappings ALTER COLUMN id SET DEFAULT
|
|||
|
||||
ALTER TABLE ONLY organization_push_rules ALTER COLUMN id SET DEFAULT nextval('organization_push_rules_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY organization_user_aliases ALTER COLUMN id SET DEFAULT nextval('organization_user_aliases_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY organization_users ALTER COLUMN id SET DEFAULT nextval('organization_users_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY organizations ALTER COLUMN id SET DEFAULT nextval('organizations_id_seq'::regclass);
|
||||
|
|
@ -30165,6 +30190,9 @@ ALTER TABLE ONLY organization_push_rules
|
|||
ALTER TABLE ONLY organization_settings
|
||||
ADD CONSTRAINT organization_settings_pkey PRIMARY KEY (organization_id);
|
||||
|
||||
ALTER TABLE ONLY organization_user_aliases
|
||||
ADD CONSTRAINT organization_user_aliases_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY organization_users
|
||||
ADD CONSTRAINT organization_users_pkey PRIMARY KEY (id);
|
||||
|
||||
|
|
@ -36214,6 +36242,8 @@ CREATE UNIQUE INDEX index_ops_strategies_user_lists_on_strategy_id_and_user_list
|
|||
|
||||
CREATE UNIQUE INDEX index_organization_push_rules_on_organization_id ON organization_push_rules USING btree (organization_id);
|
||||
|
||||
CREATE INDEX index_organization_user_aliases_on_user_id ON organization_user_aliases USING btree (user_id);
|
||||
|
||||
CREATE INDEX index_organization_users_on_org_id_access_level_user_id ON organization_users USING btree (organization_id, access_level, user_id);
|
||||
|
||||
CREATE INDEX index_organization_users_on_organization_id_and_id ON organization_users USING btree (organization_id, id);
|
||||
|
|
@ -38550,6 +38580,10 @@ CREATE INDEX unique_ml_model_versions_on_model_id_and_id ON ml_model_versions US
|
|||
|
||||
CREATE UNIQUE INDEX unique_namespace_cluster_agent_mappings_for_agent_association ON namespace_cluster_agent_mappings USING btree (namespace_id, cluster_agent_id);
|
||||
|
||||
CREATE UNIQUE INDEX unique_organization_user_alias_organization_id_user_id ON organization_user_aliases USING btree (organization_id, user_id);
|
||||
|
||||
CREATE UNIQUE INDEX unique_organization_user_alias_organization_id_username ON organization_user_aliases USING btree (organization_id, username);
|
||||
|
||||
CREATE UNIQUE INDEX unique_organizations_on_path_case_insensitive ON organizations USING btree (lower(path));
|
||||
|
||||
CREATE UNIQUE INDEX unique_packages_project_id_and_name_and_version_when_debian ON packages_packages USING btree (project_id, name, version) WHERE ((package_type = 9) AND (status <> 4));
|
||||
|
|
@ -42095,6 +42129,9 @@ ALTER TABLE ONLY packages_debian_file_metadata
|
|||
ALTER TABLE ONLY namespaces
|
||||
ADD CONSTRAINT fk_319256d87a FOREIGN KEY (file_template_project_id) REFERENCES projects(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY organization_user_aliases
|
||||
ADD CONSTRAINT fk_31b4eb5ec5 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY snippet_repository_storage_moves
|
||||
ADD CONSTRAINT fk_321e6c6235 FOREIGN KEY (snippet_organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
@ -43745,6 +43782,9 @@ ALTER TABLE ONLY user_project_callouts
|
|||
ALTER TABLE ONLY ml_model_metadata
|
||||
ADD CONSTRAINT fk_f68c7e109c FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY organization_user_aliases
|
||||
ADD CONSTRAINT fk_f709137eb7 FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY workspaces
|
||||
ADD CONSTRAINT fk_f78aeddc77 FOREIGN KEY (cluster_agent_id) REFERENCES cluster_agents(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ The following table lists the GitLab Duo features, and whether they are availabl
|
|||
| [Test Generation](../../user/gitlab_duo_chat/examples.md#write-tests-in-the-ide) | {{< icon name="check-circle-filled" >}} Yes | GitLab 17.9 and later |
|
||||
| [Refactor Code](../../user/gitlab_duo_chat/examples.md#refactor-code-in-the-ide) | {{< icon name="check-circle-filled" >}} Yes | GitLab 17.9 and later |
|
||||
| [Fix Code](../../user/gitlab_duo_chat/examples.md#fix-code-in-the-ide) | {{< icon name="check-circle-filled" >}} Yes | GitLab 17.9 and later |
|
||||
| [AI Impact Dashboard](../../user/analytics/ai_impact_analytics.md) | {{< icon name="check-circle-dashed" >}} Beta | GitLab 17.9 and later |
|
||||
| [AI Impact Dashboard](../../user/analytics/ai_impact_analytics.md) | {{< icon name="check-circle-filled" >}} Yes | GitLab 17.9 and later |
|
||||
| [Root Cause Analysis](../../user/gitlab_duo_chat/examples.md#troubleshoot-failed-cicd-jobs-with-root-cause-analysis) | {{< icon name="check-circle-dashed" >}} Beta | GitLab 17.10 and later |
|
||||
| [Vulnerability Explanation](../../user/application_security/vulnerabilities/_index.md#explaining-a-vulnerability) | {{< icon name="check-circle-dashed" >}} Beta | GitLab 17.11 and later |
|
||||
| [Vulnerability Resolution](../../user/application_security/vulnerabilities/_index.md#vulnerability-resolution) | {{< icon name="check-circle-dashed" >}} Beta | GitLab 17.11 and later |
|
||||
|
|
|
|||
|
|
@ -1483,7 +1483,7 @@ On the primary site:
|
|||
|
||||
1. On the left sidebar, at the bottom, select **Admin**.
|
||||
1. Select **Geo > Sites**.
|
||||
1. Look at the **primary site** and check the verification information. Take note that all *uploads* were verified:
|
||||
1. Look at the **primary site** and check the verification information. All *uploads* were verified:
|
||||

|
||||
1. Look at the **secondary site** and check the verification information. Notice that two *uploads* are still being synced, even though the secondary should use the same object storage. Meaning it should not have to synchronize any uploads:
|
||||

|
||||
|
|
|
|||
1058
doc/api/geo_sites.md
1058
doc/api/geo_sites.md
File diff suppressed because it is too large
Load Diff
|
|
@ -70,6 +70,9 @@ You can [also use the API](../../api/group_protected_environments.md#protect-a-s
|
|||
All jobs deploying to the environment are blocked and wait for approvals before running.
|
||||
Make sure the number of required approvals is less than the number of users allowed to deploy.
|
||||
|
||||
A user can give only one approval per deployment,
|
||||
even if the user is a member of multiple approver groups. [Issue 457541](https://gitlab.com/gitlab-org/gitlab/-/issues/457541) proposes to change this behavior so that the same user can give multiple approvals per deployment from different approver groups.
|
||||
|
||||
After a deployment job is approved, you must [run the job manually](../jobs/job_control.md#run-a-manual-job).
|
||||
|
||||
### Allow self-approval
|
||||
|
|
@ -114,6 +117,11 @@ To approve or reject a deployment:
|
|||
|
||||
You can also [use the API](../../api/deployments.md#approve-or-reject-a-blocked-deployment).
|
||||
|
||||
You can give only one approval per deployment, even if you are a member of multiple
|
||||
approver groups. [Issue 457541](https://gitlab.com/gitlab-org/gitlab/-/issues/457541)
|
||||
proposes to change this behavior so that the same user can give multiple
|
||||
approvals per deployment from different approver groups.
|
||||
|
||||
The corresponding deployment job does not run automatically after a deployment is approved.
|
||||
|
||||
### View the approval details of a deployment
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ title: Compute minutes
|
|||
|
||||
{{< /history >}}
|
||||
|
||||
The usage of instance runners by projects running CI/CD jobs is measured in Compute Minutes.
|
||||
The usage of instance runners by projects running CI/CD jobs is measured in compute minutes.
|
||||
|
||||
For some installation types, your [namespace](../../user/namespace/_index.md) has an [compute quota](instance_runner_compute_minutes.md#compute-quota-enforcement),
|
||||
which limits the available compute minutes you can use.
|
||||
|
|
@ -29,16 +29,16 @@ A compute quota can be applied to all [admin-managed instance runners](instance_
|
|||
- All instance runners on GitLab.com or GitLab Self-Managed
|
||||
- All self-hosted instance runners on GitLab Dedicated
|
||||
|
||||
The compute quota is disabled by default but can be enabled for top-level groups and user namespaces. On GitLab.com the quota is enabled
|
||||
by default to limit usage on Free namespaces. The limit is increased if a paid subscription is purchased.
|
||||
The compute quota is disabled by default but can be enabled for top-level groups and user namespaces.
|
||||
On GitLab.com, the quota is enabled by default to limit usage on Free namespaces. The limit is increased if a paid subscription is purchased.
|
||||
|
||||
GitLab-hosted instance runners on GitLab Dedicated cannot have the instance runner compute quota applied.
|
||||
|
||||
### Instance Runners
|
||||
### Instance runners
|
||||
|
||||
For instance runners on GitLab.com, GitLab Self-Managed, and self-hosted instance runners on GitLab Dedicated:
|
||||
|
||||
- You can view your usage in the [instance runner usage dashboard](instance_runner_compute_minutes.md#view-usage)
|
||||
- You can view your usage in the [instance runner usage dashboard](instance_runner_compute_minutes.md#view-usage).
|
||||
- When a quota is enabled:
|
||||
- You receive notifications when approaching your quota limits.
|
||||
- Enforcement measures are applied when you exceed your quota.
|
||||
|
|
@ -50,13 +50,13 @@ For GitLab.com:
|
|||
|
||||
### GitLab-hosted runners on GitLab Dedicated
|
||||
|
||||
GitLab-hosted Runners on GitLab Dedicated [are tracked separately](dedicated_hosted_runner_compute_minutes.md):
|
||||
GitLab-hosted runners on GitLab Dedicated [are tracked separately](dedicated_hosted_runner_compute_minutes.md):
|
||||
|
||||
- You can view your estimated usage in the [instance-level GitLab-hosted runner usage dashboard](dedicated_hosted_runner_compute_minutes.md#view-compute-usage).
|
||||
- You can view the estimated usage for your instance in the [GitLab-hosted runner usage dashboard](dedicated_hosted_runner_compute_minutes.md#view-compute-usage).
|
||||
- Usage billing is based on build duration logs collected from GitLab-hosted runners.
|
||||
- Quota enforcement and notifications are not available.
|
||||
|
||||
## Compute Minute Usage
|
||||
## Compute minute usage
|
||||
|
||||
### Compute usage calculation
|
||||
|
||||
|
|
@ -120,9 +120,9 @@ These cost factors apply to GitLab-hosted runners on GitLab.com and GitLab Dedic
|
|||
|
||||
Certain [discounts apply to GitLab.com](#cost-factors-for-gitlabcom) based on project type.
|
||||
|
||||
#### Cost Factors for GitLab.com
|
||||
#### Cost factors for GitLab.com
|
||||
|
||||
| Project Type | Cost Factor | Compute Minutes Used |
|
||||
| Project type | Cost factor | Compute minutes used |
|
||||
|--------------|-------------|---------------------|
|
||||
| Standard projects | [Based on runner type](#cost-factors-for-gitlab-hosted-runners) | 1 minute per (job duration / 60 × factor) |
|
||||
| Public projects in [GitLab for Open Source program](../../subscriptions/community_programs.md#gitlab-for-open-source) | `0.5` | 1 minute per 2 minutes of job time |
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ stage: Verify
|
|||
group: Hosted Runners
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
description: Compute minutes, usage tracking, quota management for GitLab-hosted runners on GitLab Dedicated.
|
||||
title: Compute minutes for GitLab-hosted runners on GitLab Dedicated
|
||||
title: Compute usage for GitLab-hosted runners on GitLab Dedicated
|
||||
---
|
||||
|
||||
{{< details >}}
|
||||
|
|
@ -15,10 +15,10 @@ title: Compute minutes for GitLab-hosted runners on GitLab Dedicated
|
|||
|
||||
A GitLab Dedicated instance can have both self-managed instance runners and GitLab-hosted instance runners.
|
||||
|
||||
As an administrator of a GitLab Dedicated instance, you can track and monitor compute minutes used by
|
||||
As an administrator of a GitLab Dedicated instance, you can track and monitor compute minutes used by
|
||||
namespaces running jobs on either type of instance runners.
|
||||
|
||||
For GitLab-hosted Runners:
|
||||
For GitLab-hosted runners:
|
||||
|
||||
- You can view your estimated usage in the [GitLab-hosted runner usage dashboard](#view-compute-usage).
|
||||
- Usage billing is based on build duration logs collected from GitLab-hosted runners.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ stage: Verify
|
|||
group: Pipeline Execution
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
description: Compute minutes, purchasing, usage tracking, quota management for instance runners on GitLab.com and GitLab Self-Managed.
|
||||
title: Compute minutes for instance runners
|
||||
title: Compute usage for instance runners
|
||||
---
|
||||
|
||||
{{< details >}}
|
||||
|
|
@ -14,13 +14,13 @@ title: Compute minutes for instance runners
|
|||
{{< /details >}}
|
||||
|
||||
The amount of compute minute usage that projects can consume to run jobs on admin-managed [instance runners](../runners/runners_scope.md#instance-runners)
|
||||
is limited. This limit is tracked with an instance runner compute quota on the GitLab server. Once a namespace exceeds quota, the [quota will be enforced](#enforcement).
|
||||
is limited. This limit is tracked with an instance runner compute quota on the GitLab server. When a namespace exceeds quota, the [quota is enforced](#enforcement).
|
||||
|
||||
Admin-managed instance runners are those [managed by the GitLab instance administrator](../../administration/cicd/compute_minutes.md).
|
||||
|
||||
{{< alert type="note" >}}
|
||||
|
||||
On GitLab.com instance runners are both admin-managed and GitLab-hosted since the instance is managed by GitLab.
|
||||
On GitLab.com instance runners are both admin-managed and GitLab-hosted because the instance is managed by GitLab.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ On GitLab.com instance runners are both admin-managed and GitLab-hosted since th
|
|||
|
||||
### Monthly reset
|
||||
|
||||
Compute minutes usage is reset to `0` monthly.
|
||||
Compute minutes usage is reset to `0` monthly.
|
||||
The compute quota is [reset to the monthly allocation](https://about.gitlab.com/pricing/).
|
||||
|
||||
For example, if you have a monthly quota of 10,000 compute minutes:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
stage: Secure
|
||||
stage: Application Security Testing
|
||||
group: Static Analysis
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
title: Configure CodeClimate-based Code Quality scanning (deprecated)
|
||||
|
|
|
|||
|
|
@ -41,9 +41,39 @@ See [Version Requirements](../integration/advanced_search/elasticsearch.md#versi
|
|||
|
||||
Developers making significant changes to Elasticsearch queries should test their features against all our supported versions.
|
||||
|
||||
## Setting up development environment
|
||||
## Setting up your development environment
|
||||
|
||||
See the [Elasticsearch GDK setup instructions](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/main/doc/howto/elasticsearch.md)
|
||||
- See the [Elasticsearch GDK setup instructions](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/main/doc/howto/elasticsearch.md)
|
||||
|
||||
- Ensure [Elasticsearch is running](#setting-up-your-development-environment):
|
||||
|
||||
```shell
|
||||
curl "http://localhost:9200"
|
||||
```
|
||||
|
||||
<!-- vale gitlab_base.Spelling = NO -->
|
||||
|
||||
- [Run Kibana](https://www.elastic.co/guide/en/kibana/current/install.html#_install_kibana_yourself) to interact
|
||||
with your local Elasticsearch cluster. Alternatively, you can use [Cerebro](https://github.com/lmenezes/cerebro) or a
|
||||
similar tool.
|
||||
|
||||
<!-- vale gitlab_base.Spelling = YES -->
|
||||
|
||||
- To tail the logs for Elasticsearch, run this command:
|
||||
|
||||
```shell
|
||||
tail -f log/elasticsearch.log
|
||||
```
|
||||
|
||||
## Development tips
|
||||
|
||||
- [Creating indices from scratch](advanced_search/tips.md#creating-all-indices-from-scratch-and-populating-with-local-data)
|
||||
- [Index data](advanced_search/tips.md#index-data)
|
||||
- [Updating dependent associations in the index](advanced_search/tips.md#dependent-association-index-updates)
|
||||
- [Kibana](advanced_search/tips.md#kibana)
|
||||
- [Running tests with Elasticsearch](advanced_search/tips.md#testing)
|
||||
- [Testing migrations](advanced_search/tips.md#advanced-search-migrations)
|
||||
- [Viewing index status](advanced_search/tips.md#viewing-index-status)
|
||||
|
||||
## Helpful Rake tasks
|
||||
|
||||
|
|
@ -118,28 +148,33 @@ during indexing and searching operations. Some of the benefits and tradeoffs to
|
|||
|
||||
<!-- vale gitlab_base.Spelling = NO -->
|
||||
|
||||
## Existing analyzers and tokenizers
|
||||
## Search DSL
|
||||
|
||||
This section covers the Search DSL (Domain Specific Language) supported by GitLab, which is compatible with both
|
||||
Elasticsearch and OpenSearch implementations.
|
||||
|
||||
### Existing analyzers and tokenizers
|
||||
|
||||
The following analyzers and tokenizers are defined in
|
||||
[`ee/lib/elastic/latest/config.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/elastic/latest/config.rb).
|
||||
|
||||
<!-- vale gitlab_base.Spelling = YES -->
|
||||
|
||||
### Analyzers
|
||||
#### Analyzers
|
||||
|
||||
#### `path_analyzer`
|
||||
##### `path_analyzer`
|
||||
|
||||
Used when indexing blobs' paths. Uses the `path_tokenizer` and the `lowercase` and `asciifolding` filters.
|
||||
|
||||
See the `path_tokenizer` explanation below for an example.
|
||||
|
||||
#### `sha_analyzer`
|
||||
##### `sha_analyzer`
|
||||
|
||||
Used in blobs and commits. Uses the `sha_tokenizer` and the `lowercase` and `asciifolding` filters.
|
||||
|
||||
See the `sha_tokenizer` explanation later below for an example.
|
||||
|
||||
#### `code_analyzer`
|
||||
##### `code_analyzer`
|
||||
|
||||
Used when indexing a blob's filename and content. Uses the `whitespace` tokenizer
|
||||
and the [`word_delimiter_graph`](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-word-delimiter-graph-tokenfilter.html),
|
||||
|
|
@ -149,9 +184,9 @@ The `whitespace` tokenizer was selected to have more control over how tokens are
|
|||
|
||||
See the `code` filter for an explanation on how tokens are split.
|
||||
|
||||
### Tokenizers
|
||||
#### Tokenizers
|
||||
|
||||
#### `sha_tokenizer`
|
||||
##### `sha_tokenizer`
|
||||
|
||||
This is a custom tokenizer that uses the
|
||||
[`edgeNGram` tokenizer](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/analysis-edgengram-tokenizer.html)
|
||||
|
|
@ -168,7 +203,7 @@ Example:
|
|||
- `240c29dc7`
|
||||
- `240c29dc7e`
|
||||
|
||||
#### `path_tokenizer`
|
||||
##### `path_tokenizer`
|
||||
|
||||
This is a custom tokenizer that uses the
|
||||
[`path_hierarchy` tokenizer](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/analysis-pathhierarchy-tokenizer.html)
|
||||
|
|
@ -183,7 +218,7 @@ Example:
|
|||
- `'path/application.js'`
|
||||
- `'application.js'`
|
||||
|
||||
## Gotchas
|
||||
### Gotchas
|
||||
|
||||
- Searches can have their own analyzers. Remember to check when editing analyzers.
|
||||
- `Character` filters (as opposed to token filters) always replace the original character. These filters can hinder exact searches.
|
||||
|
|
@ -192,24 +227,17 @@ Example:
|
|||
|
||||
If data cannot be added to one of the [existing indices in Elasticsearch](../integration/advanced_search/elasticsearch.md#advanced-search-index-scopes), follow these instructions to set up a new index and populate it.
|
||||
|
||||
### Recommendations
|
||||
### Recommended process for adding a new document type
|
||||
|
||||
- Ensure [Elasticsearch is running](#setting-up-development-environment):
|
||||
Create the following MRs and have them reviewed by a member of the Global Search team:
|
||||
|
||||
```shell
|
||||
curl "http://localhost:9200"
|
||||
```
|
||||
<!-- vale gitlab_base.Spelling = NO -->
|
||||
- [Run Kibana](https://www.elastic.co/guide/en/kibana/current/install.html#_install_kibana_yourself) to interact
|
||||
with your local Elasticsearch cluster. Alternatively, you can use [Cerebro](https://github.com/lmenezes/cerebro) or a similar tool.
|
||||
<!-- vale gitlab_base.Spelling = YES -->
|
||||
- To tail the logs for Elasticsearch, run this command:
|
||||
1. [Setup your development environment](#setting-up-your-development-environment)
|
||||
1. [Create the index](#create-the-index).
|
||||
1. [Create a new Elasticsearch reference](#create-a-new-elastic-reference).
|
||||
1. Perform [continuous updates](#continuous-updates) behind a feature flag. Enable the flag fully before the backfill.
|
||||
1. [Backfill the data](#backfilling-data).
|
||||
|
||||
```shell
|
||||
tail -f log/elasticsearch.log`
|
||||
```
|
||||
|
||||
See [Recommended process for adding a new document type](#recommended-process-for-adding-a-new-document-type) for how to structure the rollout.
|
||||
After indexing is done, the index is ready for search.
|
||||
|
||||
### Create the index
|
||||
|
||||
|
|
@ -338,7 +366,7 @@ The continuous update is done by calling `Elastic::ProcessBookkeepingService.tra
|
|||
|
||||
Add a new [Advanced Search migration](search/advanced_search_migration_styleguide.md) to backfill data by executing `scripts/elastic-migration` and following the instructions.
|
||||
|
||||
The backfill should execute `Elastic::ProcessInitialBookkeepingService.track!()` with an instance of the `Search::Elastic::Reference` created before for every document that should be indexed. The `BackfillEpics` migration can be used as an example.
|
||||
Use the [`MigrationDatabaseBackfillHelper`](search/advanced_search_migration_styleguide.md#searchelasticmigrationdatabasebackfillhelper). The [`BackfillWorkItems` migration](https://gitlab.com/gitlab-org/search-team/migration-graveyard/-/blob/09354f497698037fc21f5a65e5c2d0a70edd81eb/lib/migrate/20240816132114_backfill_work_items.rb) can be used as an example.
|
||||
|
||||
To test the backfill, run `Elastic::MigrationWorker.new.perform` in a console a couple of times and see that the index was populated.
|
||||
|
||||
|
|
@ -365,24 +393,26 @@ Indexes that contain a `project_id` field must use the [`Search::Elastic::Delete
|
|||
1. Add the indexed class to `excluded_classes` in [`ElasticDeleteProjectWorker`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/workers/elastic_delete_project_worker.rb)
|
||||
1. Update the worker to remove documents from the index
|
||||
|
||||
### Recommended process for adding a new document type
|
||||
|
||||
Create the following MRs and have them reviewed by a member of the Global Search team:
|
||||
|
||||
1. [Create the index](#create-the-index).
|
||||
1. [Create a new Elasticsearch reference](#create-a-new-elastic-reference).
|
||||
1. Perform [continuous updates](#continuous-updates) behind a feature flag. Enable the flag fully before the backfill.
|
||||
1. [Backfill the data](#backfilling-data).
|
||||
|
||||
After indexing is done, the index is ready for search.
|
||||
|
||||
### Adding a new scope to search service
|
||||
## Implementing search for a new document type
|
||||
|
||||
Search data is available in [`SearchController`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/controllers/search_controller.rb) and
|
||||
[Search API](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/api/search.rb). Both use the [`SearchService`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/services/search_service.rb) to return results.
|
||||
The `SearchService` can be used to return results outside of the `SearchController` and `Search API`.
|
||||
The `SearchService` can be used to return results outside the `SearchController` and `Search API`.
|
||||
|
||||
#### Search scopes
|
||||
### Recommended process for implementing search for a new document type
|
||||
|
||||
Create the following MRs and have them reviewed by a member of the Global Search team:
|
||||
|
||||
1. [Enable the new scope](#search-scopes).
|
||||
1. Create a [query builder](#building-a-query).
|
||||
1. Implement all [model requirements](#model-requirements).
|
||||
1. [Add the new scope to `Gitlab::Elastic::SearchResults`](#results-classes) behind a feature flag.
|
||||
1. Add specs which must include [permissions tests](#permissions-tests)
|
||||
1. [Test the new scope](#testing-scopes)
|
||||
1. Update documentation for [Advanced search](../user/search/advanced_search.md), [Search API](../api/search.md) and,
|
||||
[Roles and permissions](../user/permissions.md) (if applicable)
|
||||
|
||||
### Search scopes
|
||||
|
||||
The `SearchService` exposes searching at [global](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/services/search/global_service.rb),
|
||||
[group](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/services/search/group_service.rb), and [project](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/services/search/project_service.rb) levels.
|
||||
|
|
@ -404,7 +434,7 @@ Global search can be disabled for a scope. You can do the following changes for
|
|||
1. Add the setting checkbox in the Admin UI by creating an entry in `global_search_settings_checkboxes` method in [`ApplicationSettingsHelper`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/helpers/application_settings_helper.rb`).
|
||||
1. Add it to the `global_search_enabled_for_scope?` method in [`SearchService`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/services/search_service.rb).
|
||||
|
||||
#### Results classes
|
||||
### Results classes
|
||||
|
||||
The search results class available are:
|
||||
|
||||
|
|
@ -438,9 +468,18 @@ New scopes must add support to these methods within `Gitlab::Elastic::SearchResu
|
|||
- `failed?`
|
||||
- `error`
|
||||
|
||||
### Building a query
|
||||
## Query framework
|
||||
|
||||
The query builder framework is used to build Elasticsearch queries.
|
||||
The query builder framework is used to build Elasticsearch queries. We also support a legacy query framework implemented
|
||||
in the `Elastic::Latest::ApplicationClassProxy` class and classes that inherit it.
|
||||
|
||||
{{< alert type="note" >}}
|
||||
|
||||
New document types must use the query builder framework.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
### Building a query
|
||||
|
||||
A query is built using:
|
||||
|
||||
|
|
@ -1531,6 +1570,114 @@ it does not exist.
|
|||
|
||||
The model must define a `preload_search_data` scope to avoid N+1s.
|
||||
|
||||
## Updating an existing scope
|
||||
|
||||
Updates may include adding and removing document fields or changes to authorization. To update an existing
|
||||
scope, find the code used to generate queries and JSON for indexing.
|
||||
|
||||
- Queries are generated in `QueryBuilder` classes
|
||||
- Indexed documents are built in `Reference` classes
|
||||
|
||||
We also support a legacy `Proxy` framework:
|
||||
|
||||
- Queries are generated in `ClassProxy` classes
|
||||
- Indexed documents are built in `InstanceProxy` classes
|
||||
|
||||
Always aim to create new search filters in the `QueryBuilder` framework, even if they are used in the legacy framework.
|
||||
|
||||
### Adding a field
|
||||
|
||||
#### Add the field to the index
|
||||
|
||||
1. Add the field to the index mapping to add it newly created indices
|
||||
1. Create a migration to add the field to existing indices. Use the [`MigrationUpdateMappingsHelper`](search/advanced_search_migration_styleguide.md#searchelasticmigrationupdatemappingshelper)
|
||||
1. Populate the new field in the document JSON. The code must check the migration is complete using `::Elastic::DataMigrationService.migration_has_finished?`
|
||||
1. Bump the `SCHEMA_VERSION` for the document JSON. The format is year and week number: `YYYYWW`
|
||||
1. Create a migration to backfill the field in the index. Use the [`MigrationBackfillHelper`](search/advanced_search_migration_styleguide.md#searchelasticmigrationbackfillhelper)
|
||||
|
||||
#### If the new field is an associated record
|
||||
|
||||
1. Update specs for [`Elastic::ProcessBookkeepingService`](https://gitlab.com/gitlab-org/gitlab/blob/8ce9add3bc412a32e655322bfcd9dcc996670f82/ee/spec/services/elastic/process_bookkeeping_service_spec.rb) create associated records
|
||||
1. Update N+1 specs for `preload_search_data` to create associated data records
|
||||
1. Review [Updating dependent associations in the index](advanced_search/tips.md#dependent-association-index-updates)
|
||||
|
||||
#### Expose the field to the search service
|
||||
|
||||
1. Add the filter to the [`Search::Filter` concern](https://gitlab.com/gitlab-org/gitlab/-/blob/21bc3a986d27194c2387f4856ec1c5d5ef6fb4ff/app/services/concerns/search/filter.rb). The concern is used in the `Search::GlobalService`, `Search::GroupService` and `Search::ProjectService`.
|
||||
1. Pass the field for the scope by updating the `scope_options` method. The method is defined in `Gitlab::Elastic::SearchResults` with overrides in `Gitlab::Elastic::GroupSearchResults` and `Gitlab::Elastic::ProjectSearchResults`.
|
||||
1. Use the field in the [query builder](#building-a-query) by adding [an existing filter](#available-filters)
|
||||
or [creating a new one](#creating-a-filter).
|
||||
1. Track the filter usage in searches in the [`SearchController`](https://gitlab.com/gitlab-org/gitlab/-/blob/21bc3a986d27194c2387f4856ec1c5d5ef6fb4ff/app/controllers/search_controller.rb#L277)
|
||||
|
||||
### Changing mapping of an existing field
|
||||
|
||||
1. Update the field type in the index mapping to change it for newly created indices
|
||||
1. Bump the `SCHEMA_VERSION` for the document JSON. The format is year and week number: `YYYYWW`
|
||||
1. Create a migration to reindex all documents using [Zero downtime reindexing](search/advanced_search_migration_styleguide.md#zero-downtime-reindex-migration).
|
||||
Use the [`Search::Elastic::MigrationReindexTaskHelper`](search/advanced_search_migration_styleguide.md#searchelasticmigrationreindextaskhelper)
|
||||
|
||||
### Changing field content
|
||||
|
||||
1. Update the field content in the document JSON
|
||||
1. Bump the `SCHEMA_VERSION` for the document JSON. The format is year and week number: `YYYYWW`
|
||||
1. Create a migration to update documents. Use the [`MigrationReindexBasedOnSchemaVersion`](search/advanced_search_migration_styleguide.md#searchelasticmigrationreindexbasedonschemaversion)
|
||||
|
||||
### Cleaning up documents from an index
|
||||
|
||||
This may be used if documents are split from one index into separate indices or to remove data left in the index due to bugs.
|
||||
|
||||
1. Bump the `SCHEMA_VERSION` for the document JSON. The format is year and week number: `YYYYWW`
|
||||
1. Create a migration to index all records. Use the [`MigrationDatabaseBackfillHelper`](search/advanced_search_migration_styleguide.md#searchelasticmigrationdatabasebackfillhelper)
|
||||
1. Create a migration to remove all documents with the previous `SCHEMA_VERSION`. Use the [`MigrationDeleteBasedOnSchemaVersion`](search/advanced_search_migration_styleguide.md#searchelasticmigrationdeletebasedonschemaversion)
|
||||
|
||||
### Removing a field
|
||||
|
||||
The removal must be split across multiple milestones to support [multi-version compatibility](search/advanced_search_migration_styleguide.md#multi-version-compatibility).
|
||||
To avoid dynamic mapping errors, the field must be removed from all documents before a [Zero downtime reindexing](search/advanced_search_migration_styleguide.md#zero-downtime-reindex-migration).
|
||||
|
||||
Milestone `M`:
|
||||
|
||||
1. Remove the field from the index mapping to remove it from newly created indices
|
||||
1. Stop populating the field in the document JSON
|
||||
1. Bump the `SCHEMA_VERSION` for the document JSON. The format is year and week number: `YYYYWW`
|
||||
1. Remove any [filters which use the field](#available-filters) from the [query builder](#building-a-query)
|
||||
1. Update the `scope_options` method to remove the field for the scope you are updating. The method is defined in
|
||||
`Gitlab::Elastic::SearchResults` with overrides in `Gitlab::Elastic::GroupSearchResults` and
|
||||
`Gitlab::Elastic::ProjectSearchResults`.
|
||||
|
||||
If the field is not used by other scopes:
|
||||
|
||||
1. Remove the field from the [`Search::Filter` concern](https://gitlab.com/gitlab-org/gitlab/-/blob/21bc3a986d27194c2387f4856ec1c5d5ef6fb4ff/app/services/concerns/search/filter.rb).
|
||||
The concern is used in the `Search::GlobalService`, `Search::GroupService`, and `Search::ProjectService`.
|
||||
1. Remove filter tracking in searches in the [`SearchController`](https://gitlab.com/gitlab-org/gitlab/-/blob/21bc3a986d27194c2387f4856ec1c5d5ef6fb4ff/app/controllers/search_controller.rb#L277)
|
||||
|
||||
Milestone `M+1`:
|
||||
|
||||
1. Create a migration to remove the field from all documents in the index. Use the [`MigrationRemoveFieldsHelper`](search/advanced_search_migration_styleguide.md#searchelasticmigrationremovefieldshelper)
|
||||
1. Create a migration to reindex all documents with the field removed using [Zero downtime reindexing](search/advanced_search_migration_styleguide.md#zero-downtime-reindex-migration).
|
||||
Use the [`Search::Elastic::MigrationReindexTaskHelper`](search/advanced_search_migration_styleguide.md#searchelasticmigrationreindextaskhelper)
|
||||
|
||||
### Updating authorization
|
||||
|
||||
In the `QueryBuilder` framework, authorization is handled at the project level with the
|
||||
[`by_search_level_and_membership` filter](#by_search_level_and_membership) and at the group level
|
||||
with the [`by_search_level_and_group_membership` filter](#by_search_level_and_group_membership).
|
||||
|
||||
In the legacy `Proxy` framework, the authorization is handled inside the class.
|
||||
|
||||
Both frameworks use `Search::GroupsFinder` and `Search::ProjectsFinder` to query the groups and projects a user
|
||||
has direct access to search. Search relies upon group and project visibility level and feature access level settings
|
||||
for each scope. See [roles and permissions documentation](../user/permissions.md) for more information.
|
||||
|
||||
## Testing scopes
|
||||
|
||||
Test any scope in the Rails console
|
||||
|
||||
```ruby
|
||||
search_service = ::SearchService.new(User.first, { search: 'foo', scope: 'SCOPE_NAME' })
|
||||
search_service.search_objects
|
||||
```
|
||||
|
||||
### Permissions tests
|
||||
|
||||
Search code has a final security check in `SearchService#redact_unauthorized_results`. This prevents
|
||||
|
|
@ -1545,27 +1692,6 @@ in the EE specs:
|
|||
- `ee/spec/services/search/group_service_spec.rb`
|
||||
- `ee/spec/services/search/project_service_spec.rb`
|
||||
|
||||
### Testing the new scope
|
||||
|
||||
Test your new scope in the Rails console
|
||||
|
||||
```ruby
|
||||
search_service = ::SearchService.new(User.first, { search: 'foo', scope: 'SCOPE_NAME' })
|
||||
search_service.search_objects
|
||||
```
|
||||
|
||||
### Recommended process for implementing search for a new document type
|
||||
|
||||
Create the following MRs and have them reviewed by a member of the Global Search team:
|
||||
|
||||
1. [Enable the new scope](#search-scopes).
|
||||
1. Create a [query builder](#building-a-query).
|
||||
1. Implement all [model requirements](#model-requirements).
|
||||
1. [Add the new scope to `Gitlab::Elastic::SearchResults`](#results-classes) behind a feature flag.
|
||||
1. Add specs which must include [permissions tests](#permissions-tests)
|
||||
1. [Test the new scope](#testing-the-new-scope)
|
||||
1. Update documentation for [Advanced search](../user/search/advanced_search.md) and [Search API](../api/search.md) (if applicable)
|
||||
|
||||
## Zero-downtime reindexing with multiple indices
|
||||
|
||||
{{< alert type="note" >}}
|
||||
|
|
@ -1642,16 +1768,6 @@ header which allows us to track any
|
|||
[tasks](https://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html)
|
||||
in the cluster back the request in GitLab.
|
||||
|
||||
## Development tips
|
||||
|
||||
- [Creating indices from scratch](advanced_search/tips.md#creating-all-indices-from-scratch-and-populating-with-local-data)
|
||||
- [Index data](advanced_search/tips.md#index-data)
|
||||
- [Updating dependent associations in the index](advanced_search/tips.md#dependent-association-index-updates)
|
||||
- [Kibana](advanced_search/tips.md#kibana)
|
||||
- [Running tests with Elasticsearch](advanced_search/tips.md#testing)
|
||||
- [Testing migrations](advanced_search/tips.md#advanced-search-migrations)
|
||||
- [Viewing index status](advanced_search/tips.md#viewing-index-status)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Debugging Elasticsearch queries
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ The following process outlines the steps to get embeddings generated and stored
|
|||
|
||||
### Prerequisites
|
||||
|
||||
1. [Make sure Elasticsearch is running](../advanced_search.md#setting-up-development-environment).
|
||||
1. [Make sure Elasticsearch is running](../advanced_search.md#setting-up-your-development-environment).
|
||||
1. If you have an existing Elasticsearch setup, make sure the `AddEmbeddingToWorkItems` migration has been completed by executing the following until it returns:
|
||||
|
||||
```ruby
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ Our preferred approach to this problem is eventual consistency. With the loose f
|
|||
feature, we can configure delayed association cleanup without negatively affecting the
|
||||
application performance.
|
||||
|
||||
### How it works
|
||||
### How eventual consistency is implemented
|
||||
|
||||
In the previous example, a record in the `projects` table can have multiple `ci_pipeline`
|
||||
records. To keep the cleanup process separate from the actual parent record deletion,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ QueryRecorder is a tool for detecting the [N+1 queries problem](https://guides.r
|
|||
|
||||
As a rule, merge requests [should not increase query counts](../merge_request_concepts/performance.md#query-counts). If you find yourself adding something like `.includes(:author, :assignee)` to avoid having `N+1` queries, consider using QueryRecorder to enforce this with a test. Without this, a new feature which causes an additional model to be accessed can silently reintroduce the problem.
|
||||
|
||||
## How it works
|
||||
## How a QueryRecorder works
|
||||
|
||||
This style of test works by counting the number of SQL queries executed by ActiveRecord. First a control count is taken, then you add new records to the database and rerun the count. If the number of queries has significantly increased then an `N+1` queries problem exists.
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ The Single Instrumentation Layer allows to:
|
|||
- Use the same event definitions for both instrumentation and processing
|
||||
- Eliminate the need to write duplicate tracking code for the same event
|
||||
|
||||
## How it works
|
||||
## How a Single Instrumentation Layer works
|
||||
|
||||
[See example MR](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167415/diffs).
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ For a video tutorial, see the [Adding Service Ping metric via instrumentation cl
|
|||
- **Hardening**:
|
||||
Hardening a method is the process that ensures the method fails safe, returning a fallback value like -1.
|
||||
|
||||
## How it works
|
||||
## How metrics instrumentation works
|
||||
|
||||
A metric definition has the [`instrumentation_class`](metrics_dictionary.md) field, which can be set to a class.
|
||||
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ In addition, there are a few circumstances where we would always run the full RS
|
|||
|
||||
#### Have you encountered a problem with backend predictive tests?
|
||||
|
||||
If so, have a look at [the Engineering Productivity RUNBOOK on predictive tests](https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/blob/main/runbooks/predictive-test-selection.md) for instructions on how to act upon predictive tests issues. Additionally, if you identified any test selection gaps, let `@gl-dx/eng-prod` know so that we can take the necessary steps to optimize test selections.
|
||||
If so, have a look at [the Development Analytics RUNBOOK on predictive tests](https://gitlab.com/gitlab-org/quality/analytics/team/-/blob/main/runbooks/predictive-test-selection.md) for instructions on how to act upon predictive tests issues. Additionally, if you identified any test selection gaps, let `@gl-dx/development-analytics` know so that we can take the necessary steps to optimize test selections.
|
||||
|
||||
### Jest predictive jobs
|
||||
|
||||
|
|
@ -149,7 +149,7 @@ The `rules` definitions for full Jest tests are defined at `.frontend:rules:jest
|
|||
|
||||
#### Have you encountered a problem with frontend predictive tests?
|
||||
|
||||
If so, have a look at [the Engineering Productivity RUNBOOK on predictive tests](https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/blob/main/runbooks/predictive-test-selection.md) for instructions on how to act upon predictive tests issues.
|
||||
If so, have a look at [the Development analytics RUNBOOK on predictive tests](https://gitlab.com/gitlab-org/quality/analytics/team/-/blob/main/runbooks/predictive-test-selection.md) for instructions on how to act upon predictive tests issues.
|
||||
|
||||
### Fork pipelines
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ Instead you should use polling mechanism with ETag caching in Redis.
|
|||
- requests should return status code 304
|
||||
- there should be no SQL queries logged in `log/development.log`
|
||||
|
||||
## How it works
|
||||
## How polling with ETag caching works
|
||||
|
||||
Cache Miss:
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ In this example, the first time `#result` is called, it returns `nil`. However,
|
|||
it enqueues a background worker to call `#calculate_reactive_cache` and set an
|
||||
initial cache lifetime of 10 minutes.
|
||||
|
||||
## How it works
|
||||
## How ReactiveCaching works
|
||||
|
||||
The first time `#with_reactive_cache` is called, a background job is enqueued and
|
||||
`with_reactive_cache` returns `nil`. The background job calls `#calculate_reactive_cache`
|
||||
|
|
@ -307,7 +307,7 @@ self.reactive_cache_hard_limit = 5.megabytes
|
|||
|
||||
## Changing the cache keys
|
||||
|
||||
Due to [how reactive caching works](#how-it-works), changing parameters for the `calculate_reactive_cache` method is like
|
||||
Due to [how reactive caching works](#how-reactivecaching-works), changing parameters for the `calculate_reactive_cache` method is like
|
||||
changing parameters for a Sidekiq worker.
|
||||
So [the same rules](sidekiq/compatibility_across_updates.md#changing-the-arguments-for-a-worker)
|
||||
need to be followed.
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ Other problems that dynamic element validations solve are...
|
|||
- When our test is navigating to (or from) a page, we ensure that we are on the page we expect before
|
||||
test continuation.
|
||||
|
||||
## How it works
|
||||
## How dynamic element validation works
|
||||
|
||||
We interpret user actions on the page to have some sort of effect. These actions are
|
||||
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ requirements:
|
|||
are provided in the documentation. Customers pursuing FedRAMP must consider
|
||||
two-factor providers that are FedRAMP authorized and support FIPS
|
||||
requirements. FedRAMP authorized providers can be found on the [FedRAMP Marketplace](https://marketplace.fedramp.gov/products).
|
||||
When selecting a second factor, it is important to note that NIST and
|
||||
When selecting a second factor, NIST and
|
||||
FedRAMP are now indicating that phishing resistant authentication, such
|
||||
as WebAuthn, must be used (IA-2).
|
||||
|
||||
|
|
@ -641,8 +641,7 @@ Compliance programs based on NIST 800-53, such as FedRAMP, require FIPS
|
|||
compliance for all applicable cryptographic modules. GitLab has released
|
||||
FIPS versions of its container images and provides guidance on
|
||||
how to configure GitLab to meet FIPS compliance standards.
|
||||
It is important to note that
|
||||
certain features are not available or supported in FIPS mode.
|
||||
Certain features are not available or supported in FIPS mode.
|
||||
|
||||
While GitLab provides FIPS-compliant images, it is the responsibility of
|
||||
the customer to configure underlying infrastructure and evaluate the
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ Please follow the steps below on how to use this React Native Mobile App sample
|
|||
- Please use the Change Control Workflow with ServiceNow solution pack to configure the DevOps Change Velocity integration with GitLab to automate change request creation in ServiceNow for deployments require change controls. [Here](../../solutions/components/integrated_servicenow.md) is the documentation link to the change control workflow with ServiceNow solution component, and please work with your account team to get an access code to download the Change Control Workflow with ServiceNow solution package.
|
||||
- Copy the CI YAML files into your project:
|
||||
- `.gitlab-ci.yml`
|
||||
- `build-android.yml` in the pipelines directory. Please note that you will need to update the file path in `.gitlab-ci.yml` if the `build-android.yml` file is put in a different location other than /pipeline because the main `.gitlab-ci.yml` file references the `build-android.yml` file for the build job.
|
||||
- `build-ios.yml` in the pipelines directory. Please note that you will need to update the file path in `.gitlab-ci.yml` if the `build-ios.yml` file is put in a different location other than /pipeline because the main `.gitlab-ci.yml` file references the `build-ios.yml` file for the build job.
|
||||
- `build-android.yml` in the pipelines directory. You will need to update the file path in `.gitlab-ci.yml` if the `build-android.yml` file is put in a different location other than /pipeline because the main `.gitlab-ci.yml` file references the `build-android.yml` file for the build job.
|
||||
- `build-ios.yml` in the pipelines directory. You will need to update the file path in `.gitlab-ci.yml` if the `build-ios.yml` file is put in a different location other than /pipeline because the main `.gitlab-ci.yml` file references the `build-ios.yml` file for the build job.
|
||||
|
||||
```yaml
|
||||
include:
|
||||
|
|
@ -59,7 +59,7 @@ Please follow the steps below on how to use this React Native Mobile App sample
|
|||
|
||||
This pipeline is designed for a React Native project, handling both iOS and Android builds, test and deploy the Mobile App.
|
||||
|
||||
This project includes a simple reactCounter demo app for React Native build for both iOS and Android. Please note that shis version does not sign the artifacts yet, so we cannot upload to TestFlight or the Play Store.
|
||||
This project includes a simple reactCounter demo app for React Native build for both iOS and Android. This version does not sign the artifacts yet, so we cannot upload to TestFlight or the Play Store.
|
||||
|
||||
Each change uses a component for semantic versioning bumps, which has that version stored as an ephemeral variable used to commit
|
||||
generic packages to the package registry.
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ This guide is a comprehensive, end-to-end set of instructions for getting the de
|
|||
|
||||
#### Resources & Operating System
|
||||
|
||||
We will install GitLab, GitLab AI Gateway and Ollama each in their own separate virtual machine. While we used Ubuntu 24.0x in this guide, you have flexibility in choosing any Unix-based operating system that meets your organization's requirements and preferences. However, please note that using a Unix-based operating system is mandatory for this setup. This ensures system stability, security, and compatibility with the required software stack. This setup provides a good balance between cost and performance for testing and evaluation phases, though you may need to upgrade the GPU instance type when moving to production, depending on your usage requirements and team size.
|
||||
We will install GitLab, GitLab AI Gateway and Ollama each in their own separate virtual machine. While we used Ubuntu 24.0x in this guide, you have flexibility in choosing any Unix-based operating system that meets your organization's requirements and preferences. However, using a Unix-based operating system is mandatory for this setup. This ensures system stability, security, and compatibility with the required software stack. This setup provides a good balance between cost and performance for testing and evaluation phases, though you may need to upgrade the GPU instance type when moving to production, depending on your usage requirements and team size.
|
||||
|
||||
| | **GCP** | **AWS** | **OS** | **Disk** |
|
||||
|----------------|---------------|-------------|-----------|----------|
|
||||
|
|
@ -162,7 +162,7 @@ Designed for simplicity and performance, Ollama empowers users to harness the po
|
|||
|
||||
### AI Gateway
|
||||
|
||||
While the official installation guide is available [here](../../install/install_ai_gateway.md), here's a streamlined approach for setting up the AI Gateway. Note that, as of January 2025, the image `gitlab/model-gateway:self-hosted-v17.6.0-ee` has been verified to work with GitLab 17.7.
|
||||
While the official installation guide is available [here](../../install/install_ai_gateway.md), here's a streamlined approach for setting up the AI Gateway. As of January 2025, the image `gitlab/model-gateway:self-hosted-v17.6.0-ee` has been verified to work with GitLab 17.7.
|
||||
|
||||
1. Please ensure that ...
|
||||
|
||||
|
|
|
|||
|
|
@ -322,19 +322,19 @@ After you run the `rollout 100%` job, you cannot scale down, and must
|
|||
|
||||
Without `INCREMENTAL_ROLLOUT_MODE` and without `STAGING_ENABLED`:
|
||||
|
||||

|
||||

|
||||
|
||||
Without `INCREMENTAL_ROLLOUT_MODE` and with `STAGING_ENABLED`:
|
||||
|
||||

|
||||

|
||||
|
||||
With `INCREMENTAL_ROLLOUT_MODE` set to `manual` and without `STAGING_ENABLED`:
|
||||
|
||||

|
||||

|
||||
|
||||
With `INCREMENTAL_ROLLOUT_MODE` set to `manual` and with `STAGING_ENABLED`:
|
||||
|
||||

|
||||

|
||||
|
||||
## Timed incremental rollout to production
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
stage: Secure
|
||||
stage: Application Security Testing
|
||||
group: Dynamic Analysis
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
title: 'Tutorial: Perform fuzz testing in GitLab'
|
||||
|
|
|
|||
|
|
@ -462,7 +462,7 @@ to identify and assess the compatibility of your external integrations.
|
|||
|
||||
- The Linux package upgrades OpenSSL from v1.1.1w to v3.0.0.
|
||||
- Cloud Native GitLab (CNG) already upgraded to OpenSSL 3 in GitLab 16.7.0. If you are using Cloud Native GitLab, no
|
||||
action is needed. However, note that [Cloud Native Hybrid](../../administration/reference_architectures/_index.md#recommended-cloud-providers-and-services) installations
|
||||
action is needed. However, [Cloud Native Hybrid](../../administration/reference_architectures/_index.md#recommended-cloud-providers-and-services) installations
|
||||
use the Linux packages for stateful components, such as Gitaly. For those components, you will need to verify
|
||||
the TLS versions, ciphers, and certificates that are used work with the security level changes discussed below.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
stage: Secure
|
||||
stage: Application Security Testing
|
||||
group: Dynamic Analysis
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
title: API Security
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
stage: Secure
|
||||
stage: Application Security Testing
|
||||
group: Dynamic Analysis
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
title: API Discovery
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
stage: Secure
|
||||
stage: Application Security Testing
|
||||
group: Dynamic Analysis
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
title: Coverage-guided fuzz testing
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ To remove a snooze before its expiration time, remove the `snooze` section from
|
|||
|
||||
To use scheduled pipeline execution policies:
|
||||
|
||||
1. Store your CI/CD configuration in your security policy project or in a project accessible by the security policy bot.
|
||||
1. Store your CI/CD configuration in your security policy project.
|
||||
1. In your security policy project's **Settings** > **General** > **Visibility, project features, permissions** section, enable the **Grant security policy project access to CI/CD configuration** setting.
|
||||
1. Ensure your CI/CD configuration includes appropriate workflow rules for scheduled pipelines.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
stage: Secure
|
||||
stage: Application Security Testing
|
||||
group: Composition analysis
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
title: Operational container scanning
|
||||
|
|
|
|||
|
|
@ -132,4 +132,4 @@ To improve your security, try these features:
|
|||
| [Root Cause Analysis](../gitlab_duo_chat/examples.md#troubleshoot-failed-cicd-jobs-with-root-cause-analysis) | Ultimate | GitLab Duo Enterprise, GitLab Duo with Amazon Q | Generally available | Generally available | Generally available | Beta |
|
||||
| [Vulnerability Explanation](../application_security/vulnerabilities/_index.md#explaining-a-vulnerability) | Ultimate | GitLab Duo Enterprise, GitLab Duo with Amazon Q | Generally available | Generally available | Generally available | Beta |
|
||||
| [Vulnerability Resolution](../application_security/vulnerabilities/_index.md#vulnerability-resolution) | Ultimate | GitLab Duo Enterprise, GitLab Duo with Amazon Q | Generally available | Generally available | Generally available | Beta |
|
||||
| [AI Impact Dashboard](../analytics/ai_impact_analytics.md) | Ultimate | GitLab Duo Enterprise | Generally available | Generally available | N/A | Beta |
|
||||
| [AI Impact Dashboard](../analytics/ai_impact_analytics.md) | Ultimate | GitLab Duo Enterprise | Generally available | Generally available | N/A | Generally available |
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ Runway, is currently not available to external customers. GitLab is working on e
|
|||
|
||||
[View the available regions](https://gitlab-com.gitlab.io/gl-infra/platform/runway/runwayctl/manifest.schema.html#spec_regions).
|
||||
|
||||
For GitLab.com customers, it's important to note that the current routing mechanism is based on the location of the GitLab instance, not the user's location. As GitLab.com is currently single-homed in `us-east1`, requests to the AI gateway are routed to us-east4 in almost all cases. This means that the routing may not always result in the absolute nearest deployment for every user.
|
||||
For GitLab.com customers, the current routing mechanism is based on the location of the GitLab instance, not the user's location. As GitLab.com is currently single-homed in `us-east1`, requests to the AI gateway are routed to us-east4 in almost all cases. This means that the routing may not always result in the absolute nearest deployment for every user.
|
||||
|
||||
The IDE communicates directly with the AI gateway by default, bypassing the GitLab monolith. This direct connection improves routing efficiency. To change this, you can [configure direct and indirect connections](../project/repository/code_suggestions/_index.md#direct-and-indirect-connections).
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ The following factors influence where data is routed.
|
|||
|
||||
### AI gateway deployment regions
|
||||
|
||||
For the most up-to-date information on AI gateway deployment regions, please refer to the [AI-assist runway configuration file](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/blob/main/.runway/runway.yml?ref_type=heads#L12).
|
||||
For the most up-to-date information on AI gateway deployment regions, refer to the [AI-assist runway configuration file](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/blob/main/.runway/runway.yml?ref_type=heads#L12).
|
||||
|
||||
As of the last update (2023-11-21), GitLab deploys the AI gateway in the following regions:
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ As of the last update (2023-11-21), GitLab deploys the AI gateway in the followi
|
|||
- Europe (`europe-west2`, `europe-west3`, `europe-west9`)
|
||||
- Asia Pacific (`asia-northeast1`, `asia-northeast3`)
|
||||
|
||||
Please note that deployment regions may change frequently. For the most current information, always check the configuration file linked above.
|
||||
Deployment regions may change frequently. For the most current information, always check the configuration file linked above.
|
||||
|
||||
The exact location of the LLM models used by the AI gateway is determined by the third-party model providers. Currently, there is no guarantee that the models reside in the same geographical regions as the AI gateway deployments. This implies that data may flow back to the US or other regions where the model provider operates, even if the AI gateway processes the initial request in a different region.
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,11 @@ The project's [path also changes](../repository/_index.md#repository-path-change
|
|||
|
||||
New project-level labels are created for issues and merge requests if matching group labels don't already exist in the target namespace.
|
||||
|
||||
If a project contains issues assigned to an epic, and that epic is not available in the target
|
||||
group, GitLab creates a copy of the epic in the target group. When you transfer multiple projects
|
||||
with issues assigned to the same epic, GitLab creates a separate copy of that epic in the target
|
||||
group for each project.
|
||||
|
||||
{{< alert type="warning" >}}
|
||||
|
||||
Errors during the transfer process may lead to data loss of the project's components or dependencies of end users.
|
||||
|
|
|
|||
|
|
@ -8,13 +8,11 @@ module Gitlab
|
|||
class Config < Chain::Base
|
||||
include Chain::Helpers
|
||||
|
||||
INPUTS_LIMIT = 20
|
||||
|
||||
def perform!
|
||||
return unless @command.inputs
|
||||
return unless @command.inputs.size > INPUTS_LIMIT
|
||||
return unless @command.inputs.size > ::Ci::Pipeline::INPUTS_LIMIT
|
||||
|
||||
error("There cannot be more than #{INPUTS_LIMIT} inputs")
|
||||
error("There cannot be more than #{::Ci::Pipeline::INPUTS_LIMIT} inputs")
|
||||
end
|
||||
|
||||
def break?
|
||||
|
|
|
|||
|
|
@ -3690,6 +3690,9 @@ msgstr ""
|
|||
msgid "Add new webhook"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add optional summary content…"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add or remove a user."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17511,6 +17514,9 @@ msgstr ""
|
|||
msgid "Continue editing"
|
||||
msgstr ""
|
||||
|
||||
msgid "Continue review"
|
||||
msgstr ""
|
||||
|
||||
msgid "Continue to the next step"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -51240,6 +51246,9 @@ msgstr ""
|
|||
msgid "Review App|View latest app"
|
||||
msgstr ""
|
||||
|
||||
msgid "Review approval"
|
||||
msgstr ""
|
||||
|
||||
msgid "Review changes"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,10 @@ RSpec.describe 'CI configuration validation - branch pipelines', feature_categor
|
|||
it_behaves_like 'merge request pipeline'
|
||||
|
||||
it_behaves_like 'merge train pipeline'
|
||||
|
||||
it 'does not include a tier' do
|
||||
expect(jobs).not_to include('pipeline-tier-1')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when MR is created from "release-tools/update-gitaly" source branch' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :organization_user_alias, class: 'Organizations::OrganizationUserAlias' do
|
||||
user
|
||||
organization
|
||||
|
||||
sequence(:username) { |n| "user_alias_#{n}" }
|
||||
display_name { username.humanize.titleize }
|
||||
end
|
||||
end
|
||||
|
|
@ -5,6 +5,5 @@ FactoryBot.define do
|
|||
action { :add }
|
||||
issue
|
||||
user
|
||||
namespace_id { create(:namespace).id }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,26 +1,101 @@
|
|||
import { GlDrawer } from '@gitlab/ui';
|
||||
import Vue from 'vue';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import Vuex from 'vuex';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { PiniaVuePlugin } from 'pinia';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { mockTracking } from 'helpers/tracking_helper';
|
||||
import ReviewDrawer from '~/batch_comments/components/review_drawer.vue';
|
||||
import PreviewItem from '~/batch_comments/components/preview_item.vue';
|
||||
import { globalAccessorPlugin } from '~/pinia/plugins';
|
||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||
import { useNotes } from '~/notes/store/legacy_notes';
|
||||
import { useBatchComments } from '~/batch_comments/store';
|
||||
import markdownEditorEventHub from '~/vue_shared/components/markdown/eventhub';
|
||||
import { CLEAR_AUTOSAVE_ENTRY_EVENT } from '~/vue_shared/constants';
|
||||
import userCanApproveQuery from '~/batch_comments/queries/can_approve.query.graphql';
|
||||
|
||||
jest.mock('~/autosave');
|
||||
jest.mock('~/vue_shared/components/markdown/eventhub');
|
||||
|
||||
Vue.use(PiniaVuePlugin);
|
||||
Vue.use(Vuex);
|
||||
Vue.use(VueApollo);
|
||||
|
||||
describe('ReviewDrawer', () => {
|
||||
let wrapper;
|
||||
let pinia;
|
||||
let trackingSpy;
|
||||
let getCurrentUserLastNote;
|
||||
|
||||
const findDrawer = () => wrapper.findComponent(GlDrawer);
|
||||
const findDrawerHeading = () => wrapper.findByTestId('reviewer-drawer-heading');
|
||||
const findCommentTextarea = () => wrapper.findByTestId('comment-textarea');
|
||||
const findSubmitButton = () => wrapper.findByTestId('submit-review-button');
|
||||
const findForm = () => wrapper.findByTestId('submit-gl-form');
|
||||
const findPlaceholderField = () => wrapper.findByTestId('placeholder-input-field');
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMountExtended(ReviewDrawer, { pinia });
|
||||
const submitForm = async () => {
|
||||
await findPlaceholderField().vm.$emit('focus');
|
||||
|
||||
await findCommentTextarea().setValue('Hello world');
|
||||
|
||||
await findForm().vm.$emit('submit', { preventDefault: jest.fn() });
|
||||
};
|
||||
|
||||
const createComponent = ({ canApprove = true } = {}) => {
|
||||
getCurrentUserLastNote = Vue.observable({ id: 1 });
|
||||
|
||||
const store = new Vuex.Store({
|
||||
getters: {
|
||||
getNotesData: () => ({
|
||||
markdownDocsPath: '/markdown/docs',
|
||||
quickActionsDocsPath: '/quickactions/docs',
|
||||
}),
|
||||
getNoteableData: () => ({
|
||||
id: 1,
|
||||
preview_note_path: '/preview',
|
||||
}),
|
||||
noteableType: () => 'merge_request',
|
||||
getCurrentUserLastNote: () => getCurrentUserLastNote,
|
||||
getDiscussion: () => jest.fn(),
|
||||
},
|
||||
modules: {
|
||||
diffs: {
|
||||
namespaced: true,
|
||||
state: {
|
||||
projectPath: 'gitlab-org/gitlab',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const requestHandlers = [
|
||||
[
|
||||
userCanApproveQuery,
|
||||
() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
project: {
|
||||
id: 1,
|
||||
mergeRequest: {
|
||||
id: 1,
|
||||
userPermissions: {
|
||||
canApprove,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
];
|
||||
const apolloProvider = createMockApollo(requestHandlers);
|
||||
|
||||
trackingSpy = mockTracking(undefined, null, jest.spyOn);
|
||||
wrapper = mountExtended(ReviewDrawer, { pinia, store, apolloProvider });
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -52,6 +127,7 @@ describe('ReviewDrawer', () => {
|
|||
`('with draftsCount as $draftsCount', ({ draftsCount, heading }) => {
|
||||
it(`renders heading as ${heading}`, () => {
|
||||
useBatchComments().drafts = new Array(draftsCount).fill({});
|
||||
useBatchComments().drawerOpened = true;
|
||||
createComponent();
|
||||
expect(findDrawerHeading().text()).toBe(heading);
|
||||
});
|
||||
|
|
@ -59,6 +135,7 @@ describe('ReviewDrawer', () => {
|
|||
|
||||
it('renders list of preview items', () => {
|
||||
useBatchComments().drafts = [{ id: 1 }, { id: 2 }];
|
||||
useBatchComments().drawerOpened = true;
|
||||
createComponent();
|
||||
|
||||
const previewItems = wrapper.findAllComponents(PreviewItem);
|
||||
|
|
@ -72,10 +149,153 @@ describe('ReviewDrawer', () => {
|
|||
const draft = { id: 1, file_path: 'foo' };
|
||||
useLegacyDiffs().viewDiffsFileByFile = true;
|
||||
useBatchComments().drafts = [draft];
|
||||
useBatchComments().drawerOpened = true;
|
||||
createComponent();
|
||||
|
||||
await wrapper.findComponent(PreviewItem).vm.$emit('click', draft);
|
||||
|
||||
expect(useLegacyDiffs().goToFile).toHaveBeenCalledWith({ path: draft.file_path });
|
||||
});
|
||||
|
||||
it('calls publishReview with note data', async () => {
|
||||
useBatchComments().drawerOpened = true;
|
||||
|
||||
createComponent();
|
||||
|
||||
await submitForm();
|
||||
|
||||
expect(useBatchComments().publishReview).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
note: 'Hello world',
|
||||
approve: false,
|
||||
reviewer_state: 'reviewed',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('emits CLEAR_AUTOSAVE_ENTRY_EVENT with autosave key', async () => {
|
||||
useBatchComments().drawerOpened = true;
|
||||
|
||||
createComponent();
|
||||
|
||||
await findPlaceholderField().vm.$emit('focus');
|
||||
|
||||
findCommentTextarea().setValue('Hello world');
|
||||
|
||||
findForm().vm.$emit('submit', { preventDefault: jest.fn() });
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(markdownEditorEventHub.$emit).toHaveBeenCalledWith(
|
||||
CLEAR_AUTOSAVE_ENTRY_EVENT,
|
||||
'submit_review_dropdown/1',
|
||||
);
|
||||
});
|
||||
|
||||
it('clears textarea value', async () => {
|
||||
useBatchComments().drawerOpened = true;
|
||||
|
||||
createComponent();
|
||||
|
||||
await findPlaceholderField().vm.$emit('focus');
|
||||
|
||||
findCommentTextarea().setValue('Hello world');
|
||||
|
||||
findForm().vm.$emit('submit', { preventDefault: jest.fn() });
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findCommentTextarea().element.value).toBe('');
|
||||
});
|
||||
|
||||
it('tracks submit action', async () => {
|
||||
useBatchComments().drawerOpened = true;
|
||||
|
||||
createComponent();
|
||||
|
||||
await findPlaceholderField().vm.$emit('focus');
|
||||
|
||||
findCommentTextarea().setValue('Hello world');
|
||||
|
||||
findForm().vm.$emit('submit', { preventDefault: jest.fn() });
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'save_markdown', {
|
||||
label: 'markdown_editor',
|
||||
property: 'MergeRequest_review',
|
||||
});
|
||||
});
|
||||
|
||||
it('switches to the overview tab after submit', async () => {
|
||||
window.mrTabs = { tabShown: jest.fn() };
|
||||
|
||||
useBatchComments().drawerOpened = true;
|
||||
|
||||
createComponent();
|
||||
|
||||
await submitForm();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
getCurrentUserLastNote.id = 2;
|
||||
|
||||
await Vue.nextTick();
|
||||
|
||||
expect(window.mrTabs.tabShown).toHaveBeenCalledWith('show');
|
||||
});
|
||||
|
||||
it('sets submit dropdown to loading', async () => {
|
||||
useBatchComments().drawerOpened = true;
|
||||
|
||||
createComponent();
|
||||
|
||||
await submitForm();
|
||||
|
||||
expect(findSubmitButton().props('loading')).toBe(true);
|
||||
});
|
||||
|
||||
it.each`
|
||||
value
|
||||
${'approved'}
|
||||
${'reviewed'}
|
||||
${'requested_changes'}
|
||||
`('sends $value review state to api when submitting', async ({ value }) => {
|
||||
useBatchComments().drawerOpened = true;
|
||||
|
||||
createComponent();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
await findPlaceholderField().vm.$emit('focus');
|
||||
|
||||
await wrapper.find(`.custom-control-input[value="${value}"]`).trigger('change');
|
||||
|
||||
findCommentTextarea().setValue('Hello world');
|
||||
|
||||
findForm().vm.$emit('submit', { preventDefault: jest.fn() });
|
||||
|
||||
expect(useBatchComments().publishReview).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reviewer_state: value,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it.each`
|
||||
canApprove | exists | existsText
|
||||
${true} | ${undefined} | ${'shows'}
|
||||
${false} | ${'disabled'} | ${'hides'}
|
||||
`(
|
||||
'$existsText approve checkbox if can_approve is $canApprove',
|
||||
async ({ canApprove, exists }) => {
|
||||
useBatchComments().drawerOpened = true;
|
||||
|
||||
createComponent({ canApprove });
|
||||
|
||||
await findPlaceholderField().vm.$emit('focus');
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.findAll('input').at(1).attributes('disabled')).toBe(exists);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ describe('Delete milestone modal', () => {
|
|||
const mockProps = {
|
||||
issueCount: 1,
|
||||
mergeRequestCount: 2,
|
||||
milestoneId: 3,
|
||||
milestoneTitle: 'my milestone title',
|
||||
milestoneUrl: `${TEST_HOST}/delete_milestone_modal.vue/milestone`,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -220,7 +220,6 @@ describe('moreActionsDropdown', () => {
|
|||
const provideData = {
|
||||
issueCount: 1,
|
||||
mergeRequestCount: 2,
|
||||
milestoneId: 1,
|
||||
milestoneTitle: 'Milestone 1',
|
||||
milestoneUrl: '/milestone-url',
|
||||
};
|
||||
|
|
@ -233,7 +232,6 @@ describe('moreActionsDropdown', () => {
|
|||
visible: false,
|
||||
issueCount: provideData.issueCount,
|
||||
mergeRequestCount: provideData.mergeRequestCount,
|
||||
milestoneId: provideData.milestoneId,
|
||||
milestoneTitle: provideData.milestoneTitle,
|
||||
milestoneUrl: provideData.milestoneUrl,
|
||||
});
|
||||
|
|
@ -251,7 +249,6 @@ describe('moreActionsDropdown', () => {
|
|||
visible: true,
|
||||
issueCount: provideData.issueCount,
|
||||
mergeRequestCount: provideData.mergeRequestCount,
|
||||
milestoneId: provideData.milestoneId,
|
||||
milestoneTitle: provideData.milestoneTitle,
|
||||
milestoneUrl: provideData.milestoneUrl,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -126,22 +126,6 @@ describe('NamespaceStorageApp', () => {
|
|||
});
|
||||
|
||||
describe('Namespace project list', () => {
|
||||
describe('when customSortKey is set', () => {
|
||||
it('disables sorting', () => {
|
||||
createComponent({ provide: { customSortKey: 'custom' } });
|
||||
|
||||
expect(findProjectList().props('enableSortableFields')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when customSortKey is not set', () => {
|
||||
it('enables sorting', () => {
|
||||
createComponent({ provide: { customSortKey: undefined } });
|
||||
|
||||
expect(findProjectList().props('enableSortableFields')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the 2 projects', async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ const createComponent = ({ provide = {}, props = {} } = {}) => {
|
|||
helpLinks: storageTypeHelpPaths,
|
||||
isLoading: false,
|
||||
sortBy: 'storage',
|
||||
enableSortableFields: false,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
|
|
@ -57,42 +56,6 @@ describe('ProjectList', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Sorting', () => {
|
||||
it('will allow sorting for fields that have sorting enabled', () => {
|
||||
createComponent({
|
||||
props: {
|
||||
enableSortableFields: true,
|
||||
},
|
||||
});
|
||||
expect(findTable().props('fields')).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ key: 'storage', sortable: true }),
|
||||
expect.objectContaining({ key: 'repository', sortable: true }),
|
||||
expect.objectContaining({ key: 'buildArtifacts', sortable: true }),
|
||||
expect.objectContaining({ key: 'lfsObjects', sortable: true }),
|
||||
expect.objectContaining({ key: 'packages', sortable: true }),
|
||||
expect.objectContaining({ key: 'wiki', sortable: true }),
|
||||
expect.objectContaining({ key: 'containerRegistry', sortable: true }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('will disable sorting by storage field', () => {
|
||||
createComponent();
|
||||
expect(findTable().props('fields')).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ key: 'storage', sortable: false }),
|
||||
expect.objectContaining({ key: 'repository', sortable: false }),
|
||||
expect.objectContaining({ key: 'buildArtifacts', sortable: false }),
|
||||
expect.objectContaining({ key: 'lfsObjects', sortable: false }),
|
||||
expect.objectContaining({ key: 'packages', sortable: false }),
|
||||
expect.objectContaining({ key: 'wiki', sortable: false }),
|
||||
expect.objectContaining({ key: 'containerRegistry', sortable: false }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Project items are rendered', () => {
|
||||
describe.each(projectList)('$name', (project) => {
|
||||
let tableText;
|
||||
|
|
|
|||
|
|
@ -49,10 +49,8 @@ describe('CreateBranchMergeRequestModal', () => {
|
|||
});
|
||||
|
||||
const createWrapper = ({
|
||||
workItemId = 'gid://gitlab/WorkItem/1',
|
||||
workItemIid = '1',
|
||||
showBranchFlow = true,
|
||||
showMergeRequestFlow = false,
|
||||
showModal = true,
|
||||
workItemType = 'Issue',
|
||||
workItemFullPath = 'fullPath',
|
||||
|
|
@ -65,11 +63,9 @@ describe('CreateBranchMergeRequestModal', () => {
|
|||
wrapper = shallowMount(WorkItemCreateBranchMergeRequestModal, {
|
||||
apolloProvider: mockApollo,
|
||||
propsData: {
|
||||
workItemId,
|
||||
workItemIid,
|
||||
workItemType,
|
||||
showBranchFlow,
|
||||
showMergeRequestFlow,
|
||||
showModal,
|
||||
workItemFullPath,
|
||||
projectId,
|
||||
|
|
@ -213,7 +209,7 @@ describe('CreateBranchMergeRequestModal', () => {
|
|||
|
||||
describe('Merge request creation', () => {
|
||||
it('redirects to the the create merge branch request url with the correct parameters', async () => {
|
||||
createWrapper({ showBranchFlow: false, showMergeRequestFlow: true });
|
||||
createWrapper({ showBranchFlow: false });
|
||||
await waitForPromises();
|
||||
|
||||
jest.spyOn(axios, 'post');
|
||||
|
|
@ -246,11 +242,7 @@ describe('CreateBranchMergeRequestModal', () => {
|
|||
|
||||
describe('confidential merge request', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
showBranchFlow: false,
|
||||
showMergeRequestFlow: true,
|
||||
isConfidentialWorkItem: true,
|
||||
});
|
||||
createWrapper({ showBranchFlow: false, isConfidentialWorkItem: true });
|
||||
return waitForPromises();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ describe('WorkItemCreateBranchMergeRequestSplitButton', () => {
|
|||
propsData: {
|
||||
workItemFullPath: 'group/project',
|
||||
workItemType: 'issue',
|
||||
workItemId: '1',
|
||||
workItemIid: '100',
|
||||
projectId: 'gid://gitlab/Project/7',
|
||||
isConfidentialWorkItem: false,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::Config, feature_category:
|
|||
end
|
||||
|
||||
before do
|
||||
stub_const("#{described_class}::INPUTS_LIMIT", 1)
|
||||
stub_const('Ci::Pipeline::INPUTS_LIMIT', 1)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Organizations::OrganizationUserAlias, type: :model, feature_category: :cell do
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:organization).inverse_of(:organization_user_aliases).required }
|
||||
it { is_expected.to belong_to(:user).inverse_of(:organization_user_aliases).required }
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
subject { build(:organization_user_alias) }
|
||||
|
||||
it { is_expected.to validate_presence_of(:username) }
|
||||
it { is_expected.to validate_uniqueness_of(:username).scoped_to(:organization_id) }
|
||||
end
|
||||
end
|
||||
|
|
@ -13,6 +13,5 @@ RSpec.describe ResourceEvents::IssueAssignmentEvent, feature_category: :value_st
|
|||
describe 'validations' do
|
||||
it { is_expected.to be_valid }
|
||||
it { is_expected.to validate_presence_of(:issue) }
|
||||
it { is_expected.to validate_presence_of(:namespace_id) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -989,7 +989,6 @@ failure_categories:
|
|||
- "Could not find shared context"
|
||||
- "Could not find shared examples"
|
||||
- "is not available on an example group"
|
||||
- "WebMock::NetConnectNotAllowedError"
|
||||
causes:
|
||||
- "Using test doubles outside proper lifecycle"
|
||||
- "Missing or incorrectly referenced shared contexts/examples"
|
||||
|
|
|
|||
Loading…
Reference in New Issue