Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8a0aaba672
commit
52fa21ddae
|
|
@ -5,13 +5,11 @@ import { mapGetters as mapVuexGetters } from 'vuex';
|
|||
import { GlButton, GlTooltipDirective as GlTooltip, GlModal } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
import toast from '~/vue_shared/plugins/global_toast';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { SET_REVIEW_BAR_RENDERED } from '~/batch_comments/stores/modules/batch_comments/mutation_types';
|
||||
import { useBatchComments } from '~/batch_comments/store';
|
||||
import { REVIEW_BAR_VISIBLE_CLASS_NAME } from '../constants';
|
||||
import PreviewDropdown from './preview_dropdown.vue';
|
||||
import SubmitDropdown from './submit_dropdown.vue';
|
||||
import SubmitDrawer from './submit_drawer.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -19,12 +17,10 @@ export default {
|
|||
GlButton,
|
||||
PreviewDropdown,
|
||||
SubmitDropdown,
|
||||
SubmitDrawer,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
data() {
|
||||
return {
|
||||
discarding: false,
|
||||
|
|
@ -68,26 +64,24 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="review-bar-component js-review-bar" data-testid="review_bar_component">
|
||||
<div class="review-bar-content gl-flex gl-justify-end" data-testid="review-bar-content">
|
||||
<submit-drawer v-if="glFeatures.improvedReviewExperience" />
|
||||
<template v-else>
|
||||
<gl-button
|
||||
v-gl-tooltip
|
||||
icon="remove"
|
||||
variant="danger"
|
||||
category="tertiary"
|
||||
class="gl-mr-3"
|
||||
:title="__('Discard review')"
|
||||
:aria-label="__('Discard review')"
|
||||
:loading="discarding"
|
||||
data-testid="discard-review-btn"
|
||||
@click="showDiscardModal = true"
|
||||
/>
|
||||
<preview-dropdown />
|
||||
<submit-dropdown />
|
||||
</template>
|
||||
<gl-button
|
||||
v-gl-tooltip
|
||||
icon="remove"
|
||||
variant="danger"
|
||||
category="tertiary"
|
||||
class="gl-mr-3"
|
||||
:title="__('Discard review')"
|
||||
:aria-label="__('Discard review')"
|
||||
:loading="discarding"
|
||||
data-testid="discard-review-btn"
|
||||
@click="showDiscardModal = true"
|
||||
/>
|
||||
<preview-dropdown />
|
||||
<submit-dropdown />
|
||||
</div>
|
||||
<gl-modal
|
||||
v-model="showDiscardModal"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
<script>
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapActions as mapVuexActions } from 'vuex';
|
||||
import { mapActions, mapState } from 'pinia';
|
||||
import { GlDrawer } from '@gitlab/ui';
|
||||
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';
|
||||
|
||||
export default {
|
||||
name: 'ReviewDrawer',
|
||||
components: { GlDrawer, PreviewItem },
|
||||
computed: {
|
||||
...mapState(useBatchComments, ['sortedDrafts', 'draftsCount', 'drawerOpened']),
|
||||
getDrawerHeaderHeight() {
|
||||
if (!this.drawerOpened) return '0';
|
||||
|
||||
return getContentWrapperHeight();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapVuexActions('diffs', ['goToFile']),
|
||||
...mapActions(useBatchComments, ['scrollToDraft', 'setDrawerOpened']),
|
||||
async onClickDraft(draft) {
|
||||
if (this.viewDiffsFileByFile) {
|
||||
await this.goToFile({ path: draft.file_path });
|
||||
}
|
||||
|
||||
if (draft.position && !this.isOnLatestDiff(draft)) {
|
||||
const url = new URL(setUrlParams({ commit_id: draft.position.head_sha }));
|
||||
url.hash = `note_${draft.id}`;
|
||||
visitUrl(url.toString());
|
||||
} else {
|
||||
await this.scrollToDraft(draft);
|
||||
}
|
||||
},
|
||||
},
|
||||
DRAWER_Z_INDEX,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-drawer
|
||||
:header-height="getDrawerHeaderHeight"
|
||||
:z-index="$options.DRAWER_Z_INDEX"
|
||||
:open="drawerOpened"
|
||||
class="merge-request-review-drawer"
|
||||
data-testid="review-drawer-toggle"
|
||||
@close="setDrawerOpened(false)"
|
||||
>
|
||||
<template #title>
|
||||
<h4 class="gl-m-0">{{ __('Submit your review') }}</h4>
|
||||
</template>
|
||||
<div>
|
||||
<h5 class="h6 gl-mb-5 gl-mt-0" data-testid="reviewer-drawer-heading">
|
||||
<template v-if="draftsCount > 0">
|
||||
{{ n__('%d pending comment', '%d pending comments', draftsCount) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ __('No pending comments') }}
|
||||
</template>
|
||||
</h5>
|
||||
<preview-item
|
||||
v-for="draft in sortedDrafts"
|
||||
:key="draft.id"
|
||||
:draft="draft"
|
||||
class="gl-mb-3 gl-block"
|
||||
@click="onClickDraft"
|
||||
/>
|
||||
</div>
|
||||
</gl-drawer>
|
||||
</template>
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
<script>
|
||||
import { GlButton, GlDrawer } from '@gitlab/ui';
|
||||
import { mapState, mapActions } from 'pinia';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapActions as mapVuexActions } from 'vuex';
|
||||
import { setUrlParams, visitUrl } from '~/lib/utils/url_utility';
|
||||
import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
|
||||
import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
|
||||
import { useBatchComments } from '~/batch_comments/store';
|
||||
import DraftsCount from './drafts_count.vue';
|
||||
import PreviewItem from './preview_item.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlButton,
|
||||
GlDrawer,
|
||||
DraftsCount,
|
||||
PreviewItem,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
open: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(useBatchComments, ['sortedDrafts', 'draftsCount']),
|
||||
getDrawerHeaderHeight() {
|
||||
if (!this.open) return '0';
|
||||
|
||||
return getContentWrapperHeight();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapVuexActions('diffs', ['goToFile']),
|
||||
...mapActions(useBatchComments, ['scrollToDraft']),
|
||||
async onClickDraft(draft) {
|
||||
if (this.viewDiffsFileByFile) {
|
||||
await this.goToFile({ path: draft.file_path });
|
||||
}
|
||||
|
||||
if (draft.position && !this.isOnLatestDiff(draft)) {
|
||||
const url = new URL(setUrlParams({ commit_id: draft.position.head_sha }));
|
||||
url.hash = `note_${draft.id}`;
|
||||
visitUrl(url.toString());
|
||||
} else {
|
||||
await this.scrollToDraft(draft);
|
||||
}
|
||||
},
|
||||
},
|
||||
DRAWER_Z_INDEX,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<gl-button variant="confirm" data-testid="review-drawer-toggle" @click="open = !open">
|
||||
{{ __('Your review') }}
|
||||
<drafts-count
|
||||
v-if="draftsCount > 0"
|
||||
variant="info"
|
||||
data-testid="reviewer-drawer-drafts-count-badge"
|
||||
/>
|
||||
</gl-button>
|
||||
<gl-drawer
|
||||
:header-height="getDrawerHeaderHeight"
|
||||
:z-index="$options.DRAWER_Z_INDEX"
|
||||
:open="open"
|
||||
class="merge-request-review-drawer"
|
||||
data-testid="review-drawer-toggle"
|
||||
@close="open = false"
|
||||
>
|
||||
<template #title>
|
||||
<h4 class="gl-m-0">{{ __('Submit your review') }}</h4>
|
||||
</template>
|
||||
<div>
|
||||
<h5 class="h6 gl-mb-5 gl-mt-0" data-testid="reviewer-drawer-heading">
|
||||
<template v-if="draftsCount > 0">
|
||||
{{ n__('%d pending comment', '%d pending comments', draftsCount) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ __('No pending comments') }}
|
||||
</template>
|
||||
</h5>
|
||||
<preview-item
|
||||
v-for="draft in sortedDrafts"
|
||||
:key="draft.id"
|
||||
:draft="draft"
|
||||
class="gl-mb-3 gl-block"
|
||||
@click="onClickDraft"
|
||||
/>
|
||||
</div>
|
||||
</gl-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<script>
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { mapActions, mapState } from 'pinia';
|
||||
import { useBatchComments } from '~/batch_comments/store';
|
||||
import DraftsCount from './drafts_count.vue';
|
||||
|
||||
export default {
|
||||
name: 'SubmitReviewButton',
|
||||
components: {
|
||||
GlButton,
|
||||
DraftsCount,
|
||||
},
|
||||
computed: {
|
||||
...mapState(useBatchComments, ['draftsCount', 'draftsCount', 'isReviewer']),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useBatchComments, ['setDrawerOpened']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="draftsCount > 0 || isReviewer">
|
||||
<gl-button variant="confirm" data-testid="review-drawer-toggle" @click="setDrawerOpened(true)">
|
||||
{{ __('Your review') }}
|
||||
<drafts-count
|
||||
v-if="draftsCount > 0"
|
||||
variant="info"
|
||||
data-testid="reviewer-drawer-drafts-count-badge"
|
||||
/>
|
||||
</gl-button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -37,7 +37,7 @@ export const initReviewBar = () => {
|
|||
...mapActions(useBatchComments, ['fetchDrafts']),
|
||||
},
|
||||
render(createElement) {
|
||||
if (this.draftsCount === 0) return null;
|
||||
if (window.gon?.features?.improvedReviewExperience || this.draftsCount === 0) return null;
|
||||
|
||||
return createElement('review-bar');
|
||||
},
|
||||
|
|
|
|||
|
|
@ -192,3 +192,7 @@ export function discardDrafts() {
|
|||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function setDrawerOpened(opened) {
|
||||
this.drawerOpened = opened;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ export const useBatchComments = defineStore('batchComments', {
|
|||
shouldAnimateReviewButton: false,
|
||||
reviewBarRendered: false,
|
||||
isMergeRequest: false,
|
||||
drawerOpened: false,
|
||||
// TODO: this gets populated from the sidebar_reviewers.vue, we should have a separate store for this
|
||||
isReviewer: false,
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ export default {
|
|||
|
||||
<slot name="action-buttons" :is-collapsible="isCollapsible"></slot>
|
||||
|
||||
<div v-if="isCollapsible" class="gl-border-l gl-ml-3 gl-pl-3">
|
||||
<div v-if="isCollapsible" class="gl-border-l gl-ml-3 gl-border-l-section gl-pl-3">
|
||||
<gl-button
|
||||
data-testid="report-section-expand-button"
|
||||
category="tertiary"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
<script>
|
||||
import { GlAlert, GlButton } from '@gitlab/ui';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapState } from 'vuex';
|
||||
|
||||
import { mapState } from 'pinia';
|
||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||
import { EVT_EXPAND_ALL_FILES } from '../constants';
|
||||
import eventHub from '../event_hub';
|
||||
|
||||
|
|
@ -24,7 +23,7 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState('diffs', ['diffFiles']),
|
||||
...mapState(useLegacyDiffs, ['diffFiles']),
|
||||
shouldDisplay() {
|
||||
return !this.isDismissed && this.diffFiles.length > 1;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<script>
|
||||
import { GlTooltipDirective, GlIcon, GlLink, GlButtonGroup, GlButton, GlSprintf } from '@gitlab/ui';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import { mapActions, mapState } from 'pinia';
|
||||
import { __ } from '~/locale';
|
||||
import { setUrlParams } from '~/lib/utils/url_utility';
|
||||
import {
|
||||
|
|
@ -12,6 +11,7 @@ import {
|
|||
import { shouldDisableShortcuts } from '~/behaviors/shortcuts/shortcuts_toggle';
|
||||
import { sanitize } from '~/lib/dompurify';
|
||||
import FileBrowserToggle from '~/diffs/components/file_browser_toggle.vue';
|
||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||
import CompareDropdownLayout from './compare_dropdown_layout.vue';
|
||||
|
||||
export default {
|
||||
|
|
@ -35,11 +35,13 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('diffs', [
|
||||
...mapState(useLegacyDiffs, [
|
||||
'commit',
|
||||
'startVersion',
|
||||
'latestVersionPath',
|
||||
'diffCompareDropdownTargetVersions',
|
||||
'diffCompareDropdownSourceVersions',
|
||||
]),
|
||||
...mapState('diffs', ['commit', 'startVersion', 'latestVersionPath']),
|
||||
hasSourceVersions() {
|
||||
return this.diffCompareDropdownSourceVersions.length > 0;
|
||||
},
|
||||
|
|
@ -92,7 +94,7 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['moveToNeighboringCommit']),
|
||||
...mapActions(useLegacyDiffs, ['moveToNeighboringCommit']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapActions } from 'vuex';
|
||||
import { mapActions } from 'pinia';
|
||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||
import DiffDiscussionReply from './diff_discussion_reply.vue';
|
||||
import DiffDiscussions from './diff_discussions.vue';
|
||||
import DiffLineNoteForm from './diff_line_note_form.vue';
|
||||
|
|
@ -42,7 +42,7 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['showCommentForm']),
|
||||
...mapActions(useLegacyDiffs, ['showCommentForm']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<script>
|
||||
import { GlLoadingIcon, GlButton } from '@gitlab/ui';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import { mapGetters as mapVuexGetters } from 'vuex';
|
||||
import { mapActions, mapState } from 'pinia';
|
||||
import { sprintf } from '~/locale';
|
||||
import { createAlert } from '~/alert';
|
||||
import { mapParallel } from 'ee_else_ce/diffs/components/diff_row_utils';
|
||||
|
|
@ -14,6 +15,7 @@ import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_prev
|
|||
import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_diffable.vue';
|
||||
import NoteForm from '~/notes/components/note_form.vue';
|
||||
import eventHub from '~/notes/event_hub';
|
||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||
import { IMAGE_DIFF_POSITION_TYPE } from '../constants';
|
||||
import { SAVING_THE_COMMENT_FAILED, SOMETHING_WENT_WRONG } from '../i18n';
|
||||
import { getDiffMode } from '../store/utils';
|
||||
|
|
@ -62,9 +64,13 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState('diffs', ['projectPath']),
|
||||
...mapGetters('diffs', ['isInlineView', 'getCommentFormForDiffFile', 'diffLines']),
|
||||
...mapGetters(['getNoteableData', 'noteableType', 'getUserData']),
|
||||
...mapState(useLegacyDiffs, [
|
||||
'projectPath',
|
||||
'isInlineView',
|
||||
'getCommentFormForDiffFile',
|
||||
'diffLines',
|
||||
]),
|
||||
...mapVuexGetters(['getNoteableData', 'noteableType', 'getUserData']),
|
||||
diffMode() {
|
||||
return getDiffMode(this.diffFile);
|
||||
},
|
||||
|
|
@ -132,7 +138,7 @@ export default {
|
|||
});
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['saveDiffDiscussion', 'closeDiffFileCommentForm']),
|
||||
...mapActions(useLegacyDiffs, ['saveDiffDiscussion', 'closeDiffFileCommentForm']),
|
||||
handleSaveNote(note, parentElement, errorCallback) {
|
||||
this.saveDiffDiscussion({
|
||||
note,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script>
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapActions } from 'vuex';
|
||||
import { mapActions } from 'pinia';
|
||||
import DesignNotePin from '~/vue_shared/components/design_management/design_note_pin.vue';
|
||||
import NoteableDiscussion from '~/notes/components/noteable_discussion.vue';
|
||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -38,7 +38,7 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['toggleFileDiscussion']),
|
||||
...mapActions(useLegacyDiffs, ['toggleFileDiscussion']),
|
||||
isExpanded(discussion) {
|
||||
return this.shouldCollapseDiscussions ? discussion.expandedOnDiff : true;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<script>
|
||||
import { GlTooltipDirective, GlIcon, GlLoadingIcon } from '@gitlab/ui';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapActions } from 'vuex';
|
||||
import { mapActions } from 'pinia';
|
||||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||
import { createAlert } from '~/alert';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||
import { UNFOLD_COUNT, INLINE_DIFF_LINES_KEY } from '../constants';
|
||||
import * as utils from '../store/utils';
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['loadMoreLines']),
|
||||
...mapActions(useLegacyDiffs, ['loadMoreLines']),
|
||||
getPrevLineNumber(oldLineNumber, newLineNumber) {
|
||||
const index = utils.getPreviousLineIndex(this.file, {
|
||||
oldLineNumber,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
import { GlButton, GlLoadingIcon, GlSprintf, GlAlert, GlLink } from '@gitlab/ui';
|
||||
import { escape } from 'lodash';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import { mapGetters as mapVuexGetters } from 'vuex';
|
||||
import { mapActions, mapState } from 'pinia';
|
||||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||
import { IdState } from 'vendor/vue-virtual-scroller';
|
||||
import DiffContent from 'jh_else_ce/diffs/components/diff_content.vue';
|
||||
|
|
@ -19,8 +20,8 @@ import notesEventHub from '~/notes/event_hub';
|
|||
import DiffFileDrafts from '~/batch_comments/components/diff_file_drafts.vue';
|
||||
import NoteForm from '~/notes/components/note_form.vue';
|
||||
import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
|
||||
|
||||
import { fileContentsId } from '~/diffs/components/diff_row_utils';
|
||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||
import {
|
||||
DIFF_FILE_AUTOMATIC_COLLAPSE,
|
||||
DIFF_FILE_MANUAL_COLLAPSE,
|
||||
|
|
@ -125,9 +126,15 @@ export default {
|
|||
genericError: SOMETHING_WENT_WRONG,
|
||||
},
|
||||
computed: {
|
||||
...mapState('diffs', ['currentDiffFileId', 'conflictResolutionPath', 'canMerge']),
|
||||
...mapGetters(['isLoggedIn', 'isNotesFetched', 'getNoteableData', 'noteableType']),
|
||||
...mapGetters('diffs', ['getDiffFileDiscussions', 'isVirtualScrollingEnabled', 'linkedFile']),
|
||||
...mapState(useLegacyDiffs, [
|
||||
'currentDiffFileId',
|
||||
'conflictResolutionPath',
|
||||
'canMerge',
|
||||
'getDiffFileDiscussions',
|
||||
'isVirtualScrollingEnabled',
|
||||
'linkedFile',
|
||||
]),
|
||||
...mapVuexGetters(['isLoggedIn', 'isNotesFetched', 'getNoteableData', 'noteableType']),
|
||||
autosaveKey() {
|
||||
if (!this.isLoggedIn) return '';
|
||||
|
||||
|
|
@ -302,7 +309,7 @@ export default {
|
|||
eventHub.$off(EVT_EXPAND_ALL_FILES, this.expandAllListener);
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', [
|
||||
...mapActions(useLegacyDiffs, [
|
||||
'loadCollapsedDiff',
|
||||
'assignDiscussionsToDiff',
|
||||
'prefetchFileNeighbors',
|
||||
|
|
|
|||
|
|
@ -13,20 +13,20 @@ import {
|
|||
} from '@gitlab/ui';
|
||||
import { escape } from 'lodash';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import { mapGetters as mapVuexGetters } from 'vuex';
|
||||
import { mapActions, mapState } from 'pinia';
|
||||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||
import { scrollToElement } from '~/lib/utils/common_utils';
|
||||
import { truncateSha } from '~/lib/utils/text_utility';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
||||
import { createFileUrl, fileContentsId } from '~/diffs/components/diff_row_utils';
|
||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||
import { DIFF_FILE_AUTOMATIC_COLLAPSE } from '../constants';
|
||||
import { DIFF_FILE_HEADER } from '../i18n';
|
||||
import { collapsedType, isCollapsed } from '../utils/diff_file';
|
||||
import { reviewable } from '../utils/file_reviews';
|
||||
|
||||
import DiffStats from './diff_stats.vue';
|
||||
|
||||
export default {
|
||||
|
|
@ -99,9 +99,8 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState('diffs', ['latestDiff']),
|
||||
...mapGetters('diffs', ['diffHasExpandedDiscussions', 'diffHasDiscussions']),
|
||||
...mapGetters(['getNoteableData']),
|
||||
...mapState(useLegacyDiffs, ['latestDiff', 'diffHasExpandedDiscussions', 'diffHasDiscussions']),
|
||||
...mapVuexGetters(['getNoteableData']),
|
||||
diffContentIDSelector() {
|
||||
return fileContentsId(this.diffFile);
|
||||
},
|
||||
|
|
@ -204,7 +203,7 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', [
|
||||
...mapActions(useLegacyDiffs, [
|
||||
'toggleFileDiscussionWrappers',
|
||||
'toggleFullDiff',
|
||||
'setCurrentFileHash',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapGetters, mapState, mapActions } from 'vuex';
|
||||
import { mapState as mapVuexState, mapActions as mapVuexActions } from 'vuex';
|
||||
import { mapState, mapActions } from 'pinia';
|
||||
import { throttle } from 'lodash';
|
||||
import { IdState } from 'vendor/vue-virtual-scroller';
|
||||
import DraftNote from '~/batch_comments/components/draft_note.vue';
|
||||
|
|
@ -9,6 +10,7 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
|||
import { getCommentedLines } from '~/notes/components/multiline_comment_utils';
|
||||
import { hide } from '~/tooltips';
|
||||
import { countLinesInBetween } from '~/diffs/utils/diff_file';
|
||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||
import { pickDirection } from '../utils/diff_line';
|
||||
import DiffCommentCell from './diff_comment_cell.vue';
|
||||
import DiffExpansionCell from './diff_expansion_cell.vue';
|
||||
|
|
@ -69,9 +71,14 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('diffs', ['commitId', 'fileLineCoverage']),
|
||||
...mapState('diffs', ['highlightedRow', 'coverageLoaded', 'selectedCommentPosition']),
|
||||
...mapState({
|
||||
...mapState(useLegacyDiffs, [
|
||||
'commitId',
|
||||
'fileLineCoverage',
|
||||
'highlightedRow',
|
||||
'coverageLoaded',
|
||||
'selectedCommentPosition',
|
||||
]),
|
||||
...mapVuexState({
|
||||
selectedCommentPosition: ({ notes }) => notes.selectedCommentPosition,
|
||||
selectedCommentPositionHover: ({ notes }) => notes.selectedCommentPositionHover,
|
||||
}),
|
||||
|
|
@ -95,8 +102,12 @@ export default {
|
|||
this.onDragOverThrottled = throttle((line) => this.onDragOver(line), 100, { leading: true });
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['setSelectedCommentPosition']),
|
||||
...mapActions('diffs', ['showCommentForm', 'setHighlightedRow', 'toggleLineDiscussions']),
|
||||
...mapVuexActions(['setSelectedCommentPosition']),
|
||||
...mapActions(useLegacyDiffs, [
|
||||
'showCommentForm',
|
||||
'setHighlightedRow',
|
||||
'toggleLineDiscussions',
|
||||
]),
|
||||
showCommentLeft(line) {
|
||||
return line.left && !line.right;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script>
|
||||
import { isArray } from 'lodash';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import { mapActions, mapState } from 'pinia';
|
||||
import imageDiffMixin from 'ee_else_ce/diffs/mixins/image_diff';
|
||||
import DesignNotePin from '~/vue_shared/components/design_management/design_note_pin.vue';
|
||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||
|
||||
function calcPercent(pos, renderedSize) {
|
||||
return (100 * pos) / renderedSize;
|
||||
|
|
@ -54,16 +54,22 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('diffs', ['getDiffFileByHash', 'getCommentFormForDiffFile']),
|
||||
...mapState(useLegacyDiffs, ['getDiffFileByHash', 'getCommentFormForDiffFile']),
|
||||
currentCommentForm() {
|
||||
return this.getCommentFormForDiffFile(this.fileHash);
|
||||
},
|
||||
allDiscussions() {
|
||||
return isArray(this.discussions) ? this.discussions : [this.discussions];
|
||||
},
|
||||
formPosition() {
|
||||
return {
|
||||
left: `${this.currentCommentForm.xPercent}%`,
|
||||
top: `${this.currentCommentForm.yPercent}%`,
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['openDiffFileCommentForm']),
|
||||
...mapActions(useLegacyDiffs, ['openDiffFileCommentForm']),
|
||||
getImageDimensions() {
|
||||
return {
|
||||
width: Math.round(this.$parent.width),
|
||||
|
|
@ -131,12 +137,6 @@ export default {
|
|||
@click="clickedToggle(discussion)"
|
||||
/>
|
||||
|
||||
<design-note-pin
|
||||
v-if="canComment && currentCommentForm"
|
||||
:position="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
left: `${currentCommentForm.xPercent}%`,
|
||||
top: `${currentCommentForm.yPercent}%`,
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
/>
|
||||
<design-note-pin v-if="canComment && currentCommentForm" :position="formPosition" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
<script>
|
||||
import { GlSprintf, GlEmptyState } from '@gitlab/ui';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mapGetters as mapVuexGetters } from 'vuex';
|
||||
import { mapState } from 'pinia';
|
||||
import { s__, __ } from '~/locale';
|
||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
|
|
@ -21,11 +23,11 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('diffs', [
|
||||
...mapState(useLegacyDiffs, [
|
||||
'diffCompareDropdownTargetVersions',
|
||||
'diffCompareDropdownSourceVersions',
|
||||
]),
|
||||
...mapGetters(['getNoteableData']),
|
||||
...mapVuexGetters(['getNoteableData']),
|
||||
selectedSourceVersion() {
|
||||
return this.diffCompareDropdownSourceVersions.find((x) => x.selected);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ export default {
|
|||
v-for="(entry, index) in entries"
|
||||
:key="`stacktrace-entry-${index}`"
|
||||
:lines="entry.context"
|
||||
:file-path="entry.filename || entry.abs_path"
|
||||
:error-line="entry.lineNo"
|
||||
:file-path="entry.filename || entry.abs_path || entry.absolutePath"
|
||||
:error-line="entry.lineNo || entry.lineNumber"
|
||||
:error-fn="entry.function"
|
||||
:error-column="entry.colNo"
|
||||
:error-column="entry.colNo || entry.columnNumber"
|
||||
:expanded="isFirstEntry(index)"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -68,10 +68,10 @@ export default {
|
|||
this.isExpanded = !this.isExpanded;
|
||||
},
|
||||
lineNum(line) {
|
||||
return line[0];
|
||||
return line[0] ?? line.lineNumber;
|
||||
},
|
||||
lineCode(line) {
|
||||
return line[1];
|
||||
return line[1] ?? line.line;
|
||||
},
|
||||
},
|
||||
userColorScheme: window.gon.user_color_scheme,
|
||||
|
|
@ -124,18 +124,16 @@ export default {
|
|||
|
||||
<table v-if="isExpanded" :class="$options.userColorScheme" class="code js-syntax-highlight">
|
||||
<tbody>
|
||||
<template v-for="(line, index) in lines">
|
||||
<tr :key="`stacktrace-line-${index}`" class="line_holder">
|
||||
<td class="diff-line-num" :class="{ old: isHighlighted(lineNum(line)) }">
|
||||
{{ lineNum(line) }}
|
||||
</td>
|
||||
<td
|
||||
v-safe-html="lineCode(line)"
|
||||
class="line_content"
|
||||
:class="{ old: isHighlighted(lineNum(line)) }"
|
||||
></td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr v-for="(line, index) in lines" :key="`stacktrace-line-${index}`" class="line_holder">
|
||||
<td class="diff-line-num" :class="{ old: isHighlighted(lineNum(line)) }">
|
||||
{{ lineNum(line) }}
|
||||
</td>
|
||||
<td
|
||||
v-safe-html="lineCode(line)"
|
||||
class="line_content"
|
||||
:class="{ old: isHighlighted(lineNum(line)) }"
|
||||
></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -24,9 +24,6 @@ export default {
|
|||
userId: this.currentUserId,
|
||||
};
|
||||
},
|
||||
skip() {
|
||||
return !this.realtimeEnabled;
|
||||
},
|
||||
result({ data: { userMergeRequestUpdated: mergeRequest } }) {
|
||||
if (!mergeRequest) return;
|
||||
|
||||
|
|
@ -52,7 +49,7 @@ export default {
|
|||
CollapsibleSection,
|
||||
MergeRequest,
|
||||
},
|
||||
inject: ['mergeRequestsSearchDashboardPath', 'realtimeEnabled'],
|
||||
inject: ['mergeRequestsSearchDashboardPath'],
|
||||
props: {
|
||||
tabs: {
|
||||
type: Array,
|
||||
|
|
|
|||
|
|
@ -148,7 +148,8 @@ export default {
|
|||
});
|
||||
},
|
||||
updateCurrentMergeRequestIds() {
|
||||
this.currentMergeRequestIds = this.mergeRequests.nodes.map((mergeRequest) => mergeRequest.id);
|
||||
this.currentMergeRequestIds =
|
||||
this.mergeRequests?.nodes?.map((mergeRequest) => mergeRequest.id) ?? [];
|
||||
},
|
||||
resetNewMergeRequestIds() {
|
||||
this.updateCurrentMergeRequestIds();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import assigneeQuery from './queries/assignee.query.graphql';
|
|||
import assigneeCountQuery from './queries/assignee_count.query.graphql';
|
||||
import assigneeOrReviewerQuery from './queries/assignee_or_reviewer.query.graphql';
|
||||
import assigneeOrReviewerCountQuery from './queries/assignee_or_reviewer_count.query.graphql';
|
||||
import authorOrAssigneeQuery from './queries/author_or_assignee.query.graphql';
|
||||
import authorOrAssigneeCountQuery from './queries/author_or_assignee_count.query.graphql';
|
||||
|
||||
export const QUERIES = {
|
||||
reviewRequestedMergeRequests: { dataQuery: reviewerQuery, countQuery: reviewerCountQuery },
|
||||
|
|
@ -12,4 +14,8 @@ export const QUERIES = {
|
|||
dataQuery: assigneeOrReviewerQuery,
|
||||
countQuery: assigneeOrReviewerCountQuery,
|
||||
},
|
||||
authorOrAssigneeMergeRequests: {
|
||||
dataQuery: authorOrAssigneeQuery,
|
||||
countQuery: authorOrAssigneeCountQuery,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -118,7 +118,6 @@ export function initMergeRequestDashboard(el) {
|
|||
apolloProvider,
|
||||
provide: {
|
||||
mergeRequestsSearchDashboardPath: el.dataset.mergeRequestsSearchDashboardPath,
|
||||
realtimeEnabled: parseBoolean(el.dataset.realtimeEnabled),
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(App, {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
|
||||
#import "./merge_request.fragment.graphql"
|
||||
|
||||
query requestingReviewAuthorOrAssignee(
|
||||
$state: MergeRequestState = opened
|
||||
$reviewState: MergeRequestReviewState
|
||||
$reviewStates: [MergeRequestReviewState!]
|
||||
$reviewerWildcardId: ReviewerWildcardId
|
||||
$mergedAfter: Time
|
||||
$not: MergeRequestsResolverNegatedParams
|
||||
$perPage: Int!
|
||||
$afterCursor: String
|
||||
$sort: MergeRequestSort = UPDATED_DESC
|
||||
) {
|
||||
currentUser {
|
||||
id
|
||||
mergeRequests: authoredMergeRequests(
|
||||
includeAssigned: true
|
||||
state: $state
|
||||
reviewState: $reviewState
|
||||
reviewStates: $reviewStates
|
||||
reviewerWildcardId: $reviewerWildcardId
|
||||
mergedAfter: $mergedAfter
|
||||
not: $not
|
||||
first: $perPage
|
||||
after: $afterCursor
|
||||
sort: $sort
|
||||
) {
|
||||
pageInfo {
|
||||
...PageInfo
|
||||
}
|
||||
nodes {
|
||||
...MergeRequestDashboardFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
query requestingReviewAuthorOrAssigneeCount(
|
||||
$state: MergeRequestState = opened
|
||||
$reviewState: MergeRequestReviewState
|
||||
$reviewStates: [MergeRequestReviewState!]
|
||||
$reviewerWildcardId: ReviewerWildcardId
|
||||
$mergedAfter: Time
|
||||
) {
|
||||
currentUser {
|
||||
id
|
||||
mergeRequests: authoredMergeRequests(
|
||||
includeAssigned: true
|
||||
state: $state
|
||||
reviewState: $reviewState
|
||||
reviewStates: $reviewStates
|
||||
reviewerWildcardId: $reviewerWildcardId
|
||||
mergedAfter: $mergedAfter
|
||||
) {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
export const badgeState = Vue.observable({
|
||||
state: '',
|
||||
updateStatus: null,
|
||||
});
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
<script>
|
||||
import Vue from 'vue';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapGetters } from 'vuex';
|
||||
import HiddenBadge from '~/issuable/components/hidden_badge.vue';
|
||||
|
|
@ -9,11 +8,7 @@ import { TYPE_ISSUE, TYPE_MERGE_REQUEST, WORKSPACE_PROJECT } from '~/issues/cons
|
|||
import { fetchPolicies } from '~/lib/graphql';
|
||||
import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
|
||||
import ImportedBadge from '~/vue_shared/components/imported_badge.vue';
|
||||
|
||||
export const badgeState = Vue.observable({
|
||||
state: '',
|
||||
updateStatus: null,
|
||||
});
|
||||
import { badgeState } from '~/merge_requests/badge_state';
|
||||
|
||||
export default {
|
||||
TYPE_ISSUE,
|
||||
|
|
|
|||
|
|
@ -24,8 +24,9 @@ import DiscussionCounter from '~/notes/components/discussion_counter.vue';
|
|||
import TodoWidget from '~/sidebar/components/todo_toggle/sidebar_todo_widget.vue';
|
||||
import SubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import SubmitReviewButton from '~/batch_comments/components/submit_review_button.vue';
|
||||
import { badgeState } from '~/merge_requests/badge_state';
|
||||
import titleSubscription from '../queries/title.subscription.graphql';
|
||||
import { badgeState } from './merge_request_header.vue';
|
||||
|
||||
export default {
|
||||
TYPE_MERGE_REQUEST,
|
||||
|
|
@ -53,6 +54,7 @@ export default {
|
|||
},
|
||||
},
|
||||
components: {
|
||||
SubmitReviewButton,
|
||||
GlIntersectionObserver,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
|
|
@ -257,6 +259,7 @@ export default {
|
|||
</ul>
|
||||
<div class="gl-ml-auto gl-hidden gl-items-center lg:gl-flex">
|
||||
<discussion-counter :blocks-merge="blocksMerge" hide-options />
|
||||
<submit-review-button v-if="glFeatures.improvedReviewExperience" class="gl-mr-3" />
|
||||
<div v-if="isSignedIn" :class="{ 'gl-flex gl-gap-3': isNotificationsTodosButtons }">
|
||||
<todo-widget
|
||||
:issuable-id="issuableId"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import {
|
|||
} from '~/lib/utils/text_utility';
|
||||
import { sprintf } from '~/locale';
|
||||
import { InternalEvents } from '~/tracking';
|
||||
import { badgeState } from '~/merge_requests/components/merge_request_header.vue';
|
||||
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
|
||||
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
|
||||
import HelpIcon from '~/vue_shared/components/help_icon/help_icon.vue';
|
||||
|
|
@ -21,6 +20,7 @@ import { trackSavedUsingEditor } from '~/vue_shared/components/markdown/tracking
|
|||
import glAbilitiesMixin from '~/vue_shared/mixins/gl_abilities_mixin';
|
||||
import { fetchUserCounts } from '~/super_sidebar/user_counts_fetch';
|
||||
|
||||
import { badgeState } from '~/merge_requests/badge_state';
|
||||
import * as constants from '../constants';
|
||||
import eventHub from '../event_hub';
|
||||
import { COMMENT_FORM } from '../i18n';
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
import { mapActions, mapState } from 'vuex';
|
||||
import { InternalEvents } from '~/tracking';
|
||||
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
||||
import { sprintf, __ } from '~/locale';
|
||||
import { __ } from '~/locale';
|
||||
import { SORT_DIRECTION_UI } from '~/search/sort/constants';
|
||||
import {
|
||||
MR_FILTER_OPTIONS,
|
||||
|
|
@ -62,12 +62,6 @@ export default {
|
|||
if (length === MR_FILTER_OPTIONS.length) {
|
||||
return __('All activity');
|
||||
}
|
||||
if (length > 1) {
|
||||
return sprintf(__('%{strongStart}%{firstSelected}%{strongEnd} +%{length} more'), {
|
||||
firstSelected: firstSelected.text,
|
||||
length: length - 1,
|
||||
});
|
||||
}
|
||||
|
||||
return firstSelected.text;
|
||||
},
|
||||
|
|
@ -77,6 +71,13 @@ export default {
|
|||
sortDirectionData() {
|
||||
return this.isSortAsc ? SORT_DIRECTION_UI.asc : SORT_DIRECTION_UI.desc;
|
||||
},
|
||||
firstSelected() {
|
||||
return MR_FILTER_OPTIONS.find(({ value }) => this.mergeRequestFilters[0] === value);
|
||||
},
|
||||
multipleSelected() {
|
||||
const { length } = this.mergeRequestFilters;
|
||||
return length > 1 && length !== MR_FILTER_OPTIONS.length;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['updateMergeRequestFilters', 'setDiscussionSortDirection']),
|
||||
|
|
@ -128,6 +129,7 @@ export default {
|
|||
},
|
||||
},
|
||||
MR_FILTER_OPTIONS,
|
||||
multipleSelectedPhrase: __('%{strongStart}%{firstSelected}%{strongEnd} +%{length} more'),
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -161,7 +163,13 @@ export default {
|
|||
>
|
||||
<template #toggle>
|
||||
<gl-button class="!gl-rounded-br-none !gl-rounded-tr-none">
|
||||
<gl-sprintf :message="selectedFilterText">
|
||||
<gl-sprintf v-if="multipleSelected" :message="$options.multipleSelectedPhrase">
|
||||
<template #length>{{ mergeRequestFilters.length - 1 }}</template>
|
||||
<template #strong>
|
||||
<strong>{{ firstSelected.text }}</strong>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
<gl-sprintf v-else :message="selectedFilterText">
|
||||
<template #strong="{ content }">
|
||||
<strong>{{ content }}</strong>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { flattenDeep, clone } from 'lodash';
|
|||
import { match } from '~/diffs/utils/diff_file';
|
||||
import { isInMRPage } from '~/lib/utils/common_utils';
|
||||
import { doesHashExistInUrl } from '~/lib/utils/url_utility';
|
||||
import { badgeState } from '~/merge_requests/components/merge_request_header.vue';
|
||||
import { useBatchComments } from '~/batch_comments/store';
|
||||
import { badgeState } from '~/merge_requests/badge_state';
|
||||
import * as constants from '../../constants';
|
||||
import { collapseSystemNotes } from '../../stores/collapse_utils';
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { flattenDeep, clone } from 'lodash';
|
|||
import { match } from '~/diffs/utils/diff_file';
|
||||
import { isInMRPage } from '~/lib/utils/common_utils';
|
||||
import { doesHashExistInUrl } from '~/lib/utils/url_utility';
|
||||
import { badgeState } from '~/merge_requests/components/merge_request_header.vue';
|
||||
import { badgeState } from '~/merge_requests/badge_state';
|
||||
import * as constants from '../constants';
|
||||
import { collapseSystemNotes } from './collapse_utils';
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
<script>
|
||||
import { GlModal } from '@gitlab/ui';
|
||||
import { __, n__, s__ } from '~/locale';
|
||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
|
||||
export default {
|
||||
name: 'ApproveUsersModal',
|
||||
components: {
|
||||
GlModal,
|
||||
},
|
||||
expose: ['show', 'hide'],
|
||||
inject: ['beforeSubmitHook', 'beforeSubmitHookContexts', 'pendingUserCount'],
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
actionPrimary() {
|
||||
return {
|
||||
text: n__(
|
||||
'ApplicationSettings|Proceed and approve %d user',
|
||||
'ApplicationSettings|Proceed and approve %d users',
|
||||
this.pendingUserCount,
|
||||
),
|
||||
attributes: {
|
||||
variant: 'confirm',
|
||||
},
|
||||
};
|
||||
},
|
||||
modal() {
|
||||
return this.$refs[this.id];
|
||||
},
|
||||
text() {
|
||||
return n__(
|
||||
'ApplicationSettings|By changing this setting, you can also automatically approve %d user who is pending approval.',
|
||||
'ApplicationSettings|By changing this setting, you can also automatically approve %d users who are pending approval.',
|
||||
this.pendingUserCount,
|
||||
);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.beforeSubmitHook(this.verifyApproveUsers);
|
||||
},
|
||||
methods: {
|
||||
show() {
|
||||
this.modal.show();
|
||||
},
|
||||
hide() {
|
||||
this.modal.hide();
|
||||
},
|
||||
verifyApproveUsers() {
|
||||
const context = this.beforeSubmitHookContexts[this.id];
|
||||
if (!context?.shouldPreventSubmit) return false;
|
||||
try {
|
||||
const shouldPrevent = context.shouldPreventSubmit();
|
||||
if (shouldPrevent) this.show();
|
||||
return shouldPrevent;
|
||||
} catch (error) {
|
||||
Sentry.captureException(error, {
|
||||
tags: { vue_component: 'before_submit_approve_users_modal' },
|
||||
});
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
modal: {
|
||||
actionCancel: {
|
||||
text: __('Cancel'),
|
||||
},
|
||||
actionSecondary: {
|
||||
text: s__('ApplicationSettings|Proceed without auto-approval'),
|
||||
attributes: {
|
||||
category: 'secondary',
|
||||
variant: 'confirm',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-modal
|
||||
:ref="id"
|
||||
:modal-id="id"
|
||||
:action-cancel="$options.modal.actionCancel"
|
||||
:action-primary="actionPrimary"
|
||||
:action-secondary="$options.modal.actionSecondary"
|
||||
:title="s__('ApplicationSettings|Change setting and approve pending users?')"
|
||||
@hide="$emit('hide')"
|
||||
@primary="$emit('primary')"
|
||||
@secondary="$emit('secondary')"
|
||||
>{{ text }}</gl-modal
|
||||
>
|
||||
</template>
|
||||
|
|
@ -7,11 +7,11 @@ import {
|
|||
GlFormRadioGroup,
|
||||
GlSprintf,
|
||||
GlLink,
|
||||
GlModal,
|
||||
} from '@gitlab/ui';
|
||||
import csrf from '~/lib/utils/csrf';
|
||||
import { __, n__, s__, sprintf } from '~/locale';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import BeforeSubmitApproveUsersModal from './before_submit_approve_users_modal.vue';
|
||||
import SignupCheckbox from './signup_checkbox.vue';
|
||||
|
||||
const DENYLIST_TYPE_RAW = 'raw';
|
||||
|
|
@ -23,6 +23,7 @@ export default {
|
|||
DENYLIST_TYPE_RAW,
|
||||
DENYLIST_TYPE_FILE,
|
||||
components: {
|
||||
BeforeSubmitApproveUsersModal,
|
||||
GlButton,
|
||||
GlFormGroup,
|
||||
GlFormInput,
|
||||
|
|
@ -31,7 +32,6 @@ export default {
|
|||
GlSprintf,
|
||||
GlLink,
|
||||
SignupCheckbox,
|
||||
GlModal,
|
||||
SeatControlSection: () =>
|
||||
import(
|
||||
'ee_component/pages/admin/application_settings/general/components/seat_control_section.vue'
|
||||
|
|
@ -42,6 +42,14 @@ export default {
|
|||
),
|
||||
},
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
provide() {
|
||||
return {
|
||||
beforeSubmitHook: this.addBeforeSubmitHook,
|
||||
beforeSubmitHookContexts: {
|
||||
[this.approveUsersModalId]: { shouldPreventSubmit: () => this.shouldShowUserApprovalModal },
|
||||
},
|
||||
};
|
||||
},
|
||||
inject: [
|
||||
'host',
|
||||
'settingsPath',
|
||||
|
|
@ -64,7 +72,7 @@ export default {
|
|||
],
|
||||
data() {
|
||||
return {
|
||||
showModal: false,
|
||||
beforeSubmitHooks: [],
|
||||
canUsersBeAccidentallyApproved: false,
|
||||
form: {
|
||||
signupEnabled: this.signupEnabled,
|
||||
|
|
@ -89,10 +97,12 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
approveUsersModalId() {
|
||||
return 'before-submit-approve-users-modal';
|
||||
},
|
||||
shouldShowUserApprovalModal() {
|
||||
if (!this.hasPendingUsers) return false;
|
||||
if (this.hasSignupApprovalBeenToggledOff) return true;
|
||||
|
||||
return this.canUsersBeAccidentallyApproved;
|
||||
},
|
||||
hasPendingUsers() {
|
||||
|
|
@ -119,56 +129,14 @@ export default {
|
|||
},
|
||||
);
|
||||
},
|
||||
approveUsersModal() {
|
||||
const { pendingUserCount } = this;
|
||||
|
||||
return {
|
||||
id: 'signup-settings-modal',
|
||||
text: n__(
|
||||
'ApplicationSettings|By changing this setting, you can also automatically approve %d user who is pending approval.',
|
||||
'ApplicationSettings|By changing this setting, you can also automatically approve %d users who are pending approval.',
|
||||
pendingUserCount,
|
||||
),
|
||||
actionPrimary: {
|
||||
text: n__(
|
||||
'ApplicationSettings|Proceed and approve %d user',
|
||||
'ApplicationSettings|Proceed and approve %d users',
|
||||
pendingUserCount,
|
||||
),
|
||||
attributes: {
|
||||
variant: 'confirm',
|
||||
},
|
||||
},
|
||||
actionSecondary: {
|
||||
text: s__('ApplicationSettings|Proceed without auto-approval'),
|
||||
attributes: {
|
||||
category: 'secondary',
|
||||
variant: 'confirm',
|
||||
},
|
||||
},
|
||||
actionCancel: {
|
||||
text: __('Cancel'),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
showModal(value) {
|
||||
if (value === true) {
|
||||
this.$refs[this.approveUsersModal.id].show();
|
||||
} else {
|
||||
this.$refs[this.approveUsersModal.id].hide();
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
submitButtonHandler() {
|
||||
if (this.shouldShowUserApprovalModal) {
|
||||
this.showModal = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
addBeforeSubmitHook(hook) {
|
||||
this.beforeSubmitHooks.push(hook);
|
||||
},
|
||||
handleFormSubmit() {
|
||||
const shouldPreventSubmit = this.beforeSubmitHooks.some((hook) => hook());
|
||||
if (shouldPreventSubmit) return;
|
||||
this.submitForm();
|
||||
},
|
||||
setFormValue({ name, value }) {
|
||||
|
|
@ -193,9 +161,6 @@ export default {
|
|||
this.form.shouldProceedWithAutoApproval = false;
|
||||
this.submitForm();
|
||||
},
|
||||
modalHideHandler() {
|
||||
this.showModal = false;
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
buttonText: s__('ApplicationSettings|Save changes'),
|
||||
|
|
@ -286,7 +251,6 @@ export default {
|
|||
>
|
||||
<gl-form-radio value="off">
|
||||
{{ $options.i18n.emailConfirmationSettingsOffLabel }}
|
||||
|
||||
<template #help> {{ $options.i18n.emailConfirmationSettingsOffHelpText }} </template>
|
||||
</gl-form-radio>
|
||||
|
||||
|
|
@ -298,7 +262,6 @@ export default {
|
|||
|
||||
<gl-form-radio value="hard">
|
||||
{{ $options.i18n.emailConfirmationSettingsHardLabel }}
|
||||
|
||||
<template #help> {{ $options.i18n.emailConfirmationSettingsHardHelpText }} </template>
|
||||
</gl-form-radio>
|
||||
</gl-form-radio-group>
|
||||
|
|
@ -466,23 +429,14 @@ export default {
|
|||
<gl-button
|
||||
data-testid="save-changes-button"
|
||||
variant="confirm"
|
||||
@click.prevent="submitButtonHandler"
|
||||
@click.prevent="handleFormSubmit"
|
||||
>{{ $options.i18n.buttonText }}</gl-button
|
||||
>
|
||||
{{ $options.i18n.buttonText }}
|
||||
</gl-button>
|
||||
|
||||
<gl-modal
|
||||
:ref="approveUsersModal.id"
|
||||
:modal-id="approveUsersModal.id"
|
||||
:action-cancel="approveUsersModal.actionCancel"
|
||||
:action-primary="approveUsersModal.actionPrimary"
|
||||
:action-secondary="approveUsersModal.actionSecondary"
|
||||
:title="s__('ApplicationSettings|Change setting and approve pending users?')"
|
||||
<before-submit-approve-users-modal
|
||||
:id="approveUsersModalId"
|
||||
@primary="submitFormWithAutoApproval"
|
||||
@secondary="submitFormWithoutAutoApproval"
|
||||
@hide="modalHideHandler"
|
||||
>
|
||||
{{ approveUsersModal.text }}
|
||||
</gl-modal>
|
||||
/>
|
||||
</form>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { apolloProvider } from '~/graphql_shared/issuable_client';
|
|||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { initMrMoreDropdown } from '~/mr_more_dropdown';
|
||||
import { pinia } from '~/pinia/instance';
|
||||
import ReviewDrawer from '~/batch_comments/components/review_drawer.vue';
|
||||
import initShow from './init_merge_request_show';
|
||||
import getStateQuery from './queries/get_state.query.graphql';
|
||||
|
||||
|
|
@ -20,33 +21,7 @@ const tabData = Vue.observable({
|
|||
tabs: [],
|
||||
});
|
||||
|
||||
export function initMrPage() {
|
||||
initMrNotes();
|
||||
initShow(store, pinia);
|
||||
initMrMoreDropdown();
|
||||
startCodeReviewMessaging({ signalBus: diffsEventHub });
|
||||
|
||||
const changesCountBadge = document.querySelector('.js-changes-tab-count');
|
||||
diffsEventHub.$on(EVT_MR_DIFF_GENERATED, (mergeRequestDiffGenerated) => {
|
||||
const { fileCount } = mergeRequestDiffGenerated.diffStatsSummary;
|
||||
|
||||
if (changesCountBadge.textContent === '-') {
|
||||
changesCountBadge.textContent = fileCount;
|
||||
|
||||
const DIFF_TAB_INDEX = 3;
|
||||
const diffTab = tabData.tabs ? tabData.tabs[tabData.tabs.length - 1] : [];
|
||||
|
||||
const hasDiffTab = diffTab?.length >= DIFF_TAB_INDEX + 1;
|
||||
if (hasDiffTab) {
|
||||
diffTab[DIFF_TAB_INDEX] = fileCount;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
requestIdleCallback(() => {
|
||||
initSidebarBundle(store, pinia);
|
||||
|
||||
const initMrStickyHeader = () => {
|
||||
const el = document.getElementById('js-merge-sticky-header');
|
||||
|
||||
if (el) {
|
||||
|
|
@ -101,4 +76,52 @@ requestIdleCallback(() => {
|
|||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const initReviewDrawer = () => {
|
||||
if (!window.gon?.features?.improvedReviewExperience) return;
|
||||
|
||||
// Review drawer has to be located outside the MR sticky/non-sticky header
|
||||
// Otherwise it will disappear when header switches between sticky/non-sticky components
|
||||
const el = document.querySelector('#js-review-drawer');
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
pinia,
|
||||
store,
|
||||
render(h) {
|
||||
return h(ReviewDrawer);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export function initMrPage() {
|
||||
initMrNotes();
|
||||
initShow(store, pinia);
|
||||
initMrMoreDropdown();
|
||||
startCodeReviewMessaging({ signalBus: diffsEventHub });
|
||||
|
||||
const changesCountBadge = document.querySelector('.js-changes-tab-count');
|
||||
diffsEventHub.$on(EVT_MR_DIFF_GENERATED, (mergeRequestDiffGenerated) => {
|
||||
const { fileCount } = mergeRequestDiffGenerated.diffStatsSummary;
|
||||
|
||||
if (changesCountBadge.textContent === '-') {
|
||||
changesCountBadge.textContent = fileCount;
|
||||
|
||||
const DIFF_TAB_INDEX = 3;
|
||||
const diffTab = tabData.tabs ? tabData.tabs[tabData.tabs.length - 1] : [];
|
||||
|
||||
const hasDiffTab = diffTab?.length >= DIFF_TAB_INDEX + 1;
|
||||
if (hasDiffTab) {
|
||||
diffTab[DIFF_TAB_INDEX] = fileCount;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
requestIdleCallback(() => {
|
||||
initSidebarBundle(store, pinia);
|
||||
initMrStickyHeader();
|
||||
initReviewDrawer();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { InternalEvents } from '~/tracking';
|
|||
import { isGid, getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { fetchUserCounts } from '~/super_sidebar/user_counts_fetch';
|
||||
import ReviewerDrawer from '~/merge_requests/components/reviewers/reviewer_drawer.vue';
|
||||
import { useBatchComments } from '~/batch_comments/store';
|
||||
import eventHub from '../../event_hub';
|
||||
import getMergeRequestReviewersQuery from '../../queries/get_merge_request_reviewers.query.graphql';
|
||||
import mergeRequestReviewersUpdatedSubscription from '../../queries/merge_request_reviewers.subscription.graphql';
|
||||
|
|
@ -130,6 +131,18 @@ export default {
|
|||
canUpdate() {
|
||||
return this.issuable.userPermissions?.adminMergeRequest || false;
|
||||
},
|
||||
isReviewer() {
|
||||
const { username } = this.store?.currentUser || {};
|
||||
return this.reviewers.some((reviewer) => reviewer.username === username) || false;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isReviewer: {
|
||||
handler(value) {
|
||||
useBatchComments().isReviewer = value;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.store = new Store();
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { __ } from '~/locale';
|
|||
import { apolloProvider } from '~/graphql_shared/issuable_client';
|
||||
import Translate from '~/vue_shared/translate';
|
||||
import UserSelect from '~/vue_shared/components/user_select/user_select.vue';
|
||||
import SubmitReviewButton from '~/batch_comments/components/submit_review_button.vue';
|
||||
import CollapsedAssigneeList from './components/assignees/collapsed_assignee_list.vue';
|
||||
import SidebarAssigneesWidget from './components/assignees/sidebar_assignees_widget.vue';
|
||||
import CopyEmailToClipboard from './components/copy/copy_email_to_clipboard.vue';
|
||||
|
|
@ -47,6 +48,21 @@ function getSidebarOptions(sidebarOptEl = document.querySelector('.js-sidebar-op
|
|||
return JSON.parse(sidebarOptEl.innerHTML);
|
||||
}
|
||||
|
||||
function mountSubmitReviewButton(pinia) {
|
||||
if (!window.gon?.features?.improvedReviewExperience) return;
|
||||
|
||||
const el = document.querySelector('#js-submit-review-button');
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
pinia,
|
||||
render(h) {
|
||||
return h(SubmitReviewButton, { attrs: { class: 'gl-mr-3' } });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function mountSidebarTodoWidget() {
|
||||
const el = document.querySelector('.js-sidebar-todo-widget-root');
|
||||
|
||||
|
|
@ -126,7 +142,7 @@ function mountSidebarAssigneesWidget() {
|
|||
});
|
||||
}
|
||||
|
||||
function mountSidebarReviewers(mediator) {
|
||||
function mountSidebarReviewers(mediator, pinia) {
|
||||
const el = document.querySelector('.js-sidebar-reviewers-root');
|
||||
|
||||
if (!el) {
|
||||
|
|
@ -145,6 +161,7 @@ function mountSidebarReviewers(mediator) {
|
|||
el,
|
||||
name: 'SidebarReviewersRoot',
|
||||
apolloProvider,
|
||||
pinia,
|
||||
provide: {
|
||||
issuableIid: String(iid),
|
||||
issuableId: String(id),
|
||||
|
|
@ -725,7 +742,7 @@ export function mountAssigneesDropdown() {
|
|||
export function mountSidebar(mediator, store, pinia) {
|
||||
mountSidebarTodoWidget();
|
||||
mountSidebarAssigneesWidget();
|
||||
mountSidebarReviewers(mediator);
|
||||
mountSidebarReviewers(mediator, pinia);
|
||||
mountSidebarCrmContacts();
|
||||
mountSidebarLabelsWidget();
|
||||
mountSidebarMilestoneWidget();
|
||||
|
|
@ -739,6 +756,7 @@ export function mountSidebar(mediator, store, pinia) {
|
|||
mountSidebarSeverityWidget();
|
||||
mountSidebarEscalationStatus();
|
||||
mountMoveIssueButton();
|
||||
mountSubmitReviewButton(pinia);
|
||||
}
|
||||
|
||||
export { getSidebarOptions };
|
||||
|
|
|
|||
|
|
@ -436,7 +436,7 @@ export default {
|
|||
</div>
|
||||
<div
|
||||
v-if="!glFeatures.mrReportsTab && isCollapsible && !isSummaryLoading"
|
||||
class="gl-border-l gl-ml-3 gl-h-6 gl-pl-3"
|
||||
class="gl-border-l gl-ml-3 gl-h-6 gl-border-l-section gl-pl-3"
|
||||
>
|
||||
<gl-button
|
||||
v-gl-tooltip
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_ke
|
|||
import { STATUS_CLOSED, STATUS_MERGED, STATUS_OPEN } from '~/issues/constants';
|
||||
import { formatDate, getTimeago, newDate, timeagoLanguageCode } from '~/lib/utils/datetime_utility';
|
||||
import { machine } from '~/lib/utils/finite_state_machine';
|
||||
import { badgeState } from '~/merge_requests/components/merge_request_header.vue';
|
||||
import { cleanLeadingSeparator } from '~/lib/utils/url_utility';
|
||||
import { badgeState } from '~/merge_requests/badge_state';
|
||||
import {
|
||||
MT_MERGE_STRATEGY,
|
||||
MWCP_MERGE_STRATEGY,
|
||||
|
|
|
|||
|
|
@ -79,7 +79,6 @@ import WorkItemDescription from './work_item_description.vue';
|
|||
import WorkItemNotes from './work_item_notes.vue';
|
||||
import WorkItemAwardEmoji from './work_item_award_emoji.vue';
|
||||
import WorkItemRelationships from './work_item_relationships/work_item_relationships.vue';
|
||||
import WorkItemErrorTracking from './work_item_error_tracking.vue';
|
||||
import WorkItemStickyHeader from './work_item_sticky_header.vue';
|
||||
import WorkItemAncestors from './work_item_ancestors/work_item_ancestors.vue';
|
||||
import WorkItemTitle from './work_item_title.vue';
|
||||
|
|
@ -91,6 +90,8 @@ import DesignUploadButton from './design_management/upload_button.vue';
|
|||
import WorkItemDevelopment from './work_item_development/work_item_development.vue';
|
||||
import WorkItemCreateBranchMergeRequestSplitButton from './work_item_development/work_item_create_branch_merge_request_split_button.vue';
|
||||
|
||||
const WorkItemErrorTracking = () => import('~/work_items/components/work_item_error_tracking.vue');
|
||||
|
||||
const defaultWorkspacePermissions = {
|
||||
createDesign: false,
|
||||
moveDesign: false,
|
||||
|
|
@ -413,8 +414,8 @@ export default {
|
|||
workItemAwardEmoji() {
|
||||
return this.findWidget(WIDGET_TYPE_AWARD_EMOJI);
|
||||
},
|
||||
workItemErrorTrackingIdentifier() {
|
||||
return this.findWidget(WIDGET_TYPE_ERROR_TRACKING)?.identifier;
|
||||
workItemErrorTracking() {
|
||||
return this.findWidget(WIDGET_TYPE_ERROR_TRACKING) ?? {};
|
||||
},
|
||||
workItemHierarchy() {
|
||||
return this.findWidget(WIDGET_TYPE_HIERARCHY);
|
||||
|
|
@ -1147,9 +1148,9 @@ export default {
|
|||
</aside>
|
||||
|
||||
<work-item-error-tracking
|
||||
v-if="workItemErrorTrackingIdentifier"
|
||||
v-if="workItemErrorTracking.identifier"
|
||||
:full-path="workItemFullPath"
|
||||
:identifier="workItemErrorTrackingIdentifier"
|
||||
:iid="iid"
|
||||
/>
|
||||
|
||||
<design-widget
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
<script>
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import { createAlert } from '~/alert';
|
||||
import { GlAlert } from '@gitlab/ui';
|
||||
import Stacktrace from '~/error_tracking/components/stacktrace.vue';
|
||||
import service from '~/error_tracking/services';
|
||||
import Poll from '~/lib/utils/poll';
|
||||
import { __ } from '~/locale';
|
||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
import CrudComponent from '~/vue_shared/components/crud_component.vue';
|
||||
import workItemErrorTrackingQuery from '../graphql/work_item_error_tracking.query.graphql';
|
||||
import { findErrorTrackingWidget } from '../utils';
|
||||
|
||||
const POLL_INTERVAL = 2000;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
CrudComponent,
|
||||
GlAlert,
|
||||
Stacktrace,
|
||||
},
|
||||
props: {
|
||||
|
|
@ -16,70 +20,98 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
identifier: {
|
||||
iid: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
errorMessage: '',
|
||||
errorTracking: {},
|
||||
loading: false,
|
||||
stackTraceData: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
stackTraceEntries() {
|
||||
return this.stackTraceData.stack_trace_entries?.toReversed() ?? [];
|
||||
},
|
||||
stackTracePath() {
|
||||
return `/${this.fullPath}/-/error_tracking/${this.identifier}/stack_trace.json`;
|
||||
apollo: {
|
||||
errorTracking: {
|
||||
query: workItemErrorTrackingQuery,
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.fullPath,
|
||||
iid: this.iid,
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
return findErrorTrackingWidget(data.namespace.workItem) ?? {};
|
||||
},
|
||||
error(error) {
|
||||
this.errorMessage = __('Failed to load stack trace.');
|
||||
Sentry.captureException(error);
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.startPolling(this.stackTracePath);
|
||||
computed: {
|
||||
isLoading() {
|
||||
return this.$apollo.queries.errorTracking.loading || this.loading;
|
||||
},
|
||||
stackTrace() {
|
||||
return this.errorTracking.stackTrace?.nodes.toReversed() ?? [];
|
||||
},
|
||||
status() {
|
||||
return this.errorTracking.status;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// The backend fetches data in the background, so the data won't be available immediately.
|
||||
// The backend returns 'RETRY' until it receives data so we need to keep polling until then.
|
||||
status(status) {
|
||||
switch (status) {
|
||||
case 'RETRY':
|
||||
this.$apollo.queries.errorTracking.startPolling(POLL_INTERVAL);
|
||||
this.loading = true;
|
||||
break;
|
||||
case 'NOT_FOUND':
|
||||
this.errorMessage = __('Sentry issue not found.');
|
||||
this.stopPolling();
|
||||
break;
|
||||
case 'ERROR':
|
||||
this.errorMessage = __('Error tracking service responded with an error.');
|
||||
this.stopPolling();
|
||||
break;
|
||||
case 'SUCCESS':
|
||||
this.stopPolling();
|
||||
break;
|
||||
default:
|
||||
this.stopPolling();
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.stackTracePoll?.stop();
|
||||
// Stop polling, for example when closing a work item drawer
|
||||
this.stopPolling();
|
||||
},
|
||||
methods: {
|
||||
startPolling(endpoint) {
|
||||
this.loading = true;
|
||||
|
||||
this.stackTracePoll = new Poll({
|
||||
resource: service,
|
||||
method: 'getSentryData',
|
||||
data: { endpoint },
|
||||
successCallback: ({ data }) => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.stackTraceData = data.error;
|
||||
this.stackTracePoll.stop();
|
||||
this.loading = false;
|
||||
},
|
||||
errorCallback: () => {
|
||||
createAlert({ message: __('Failed to load stacktrace.') });
|
||||
this.loading = false;
|
||||
},
|
||||
});
|
||||
|
||||
this.stackTracePoll.makeRequest();
|
||||
stopPolling() {
|
||||
this.$apollo.queries.errorTracking.stopPolling();
|
||||
this.loading = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div :class="{ 'gl-border-b-0': loading }" class="card card-slim gl-mb-0 gl-mt-5">
|
||||
<div class="card-header gl-border-b-0">
|
||||
<h2 class="card-title gl-my-2 gl-text-base">{{ __('Stack trace') }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="loading" class="card gl-mb-0">
|
||||
<gl-loading-icon class="gl-my-3" />
|
||||
</div>
|
||||
<stacktrace v-else :entries="stackTraceEntries" />
|
||||
</div>
|
||||
<crud-component
|
||||
anchor-id="stack-trace"
|
||||
is-collapsible
|
||||
:is-loading="isLoading"
|
||||
persist-collapsed-state
|
||||
:title="__('Stack trace')"
|
||||
>
|
||||
<template #default>
|
||||
<gl-alert v-if="errorMessage" :dismissible="false" variant="danger">
|
||||
{{ errorMessage }}
|
||||
</gl-alert>
|
||||
<stacktrace v-if="stackTrace.length" :entries="stackTrace" />
|
||||
</template>
|
||||
</crud-component>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
query workItemErrorTracking($fullPath: ID!, $iid: String!) {
|
||||
namespace(fullPath: $fullPath) {
|
||||
id
|
||||
workItem(iid: $iid) {
|
||||
id
|
||||
widgets {
|
||||
... on WorkItemWidgetErrorTracking {
|
||||
type
|
||||
identifier
|
||||
stackTrace {
|
||||
nodes {
|
||||
absolutePath
|
||||
columnNumber
|
||||
context {
|
||||
line
|
||||
lineNumber
|
||||
}
|
||||
filename
|
||||
function
|
||||
lineNumber
|
||||
}
|
||||
}
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import {
|
|||
WIDGET_TYPE_CURRENT_USER_TODOS,
|
||||
WIDGET_TYPE_DESCRIPTION,
|
||||
WIDGET_TYPE_DESIGNS,
|
||||
WIDGET_TYPE_ERROR_TRACKING,
|
||||
WIDGET_TYPE_HEALTH_STATUS,
|
||||
WIDGET_TYPE_HIERARCHY,
|
||||
WIDGET_TYPE_LABELS,
|
||||
|
|
@ -53,6 +54,9 @@ export const findDescriptionWidget = (workItem) =>
|
|||
export const findDesignsWidget = (workItem) =>
|
||||
workItem?.widgets?.find((widget) => widget.type === WIDGET_TYPE_DESIGNS);
|
||||
|
||||
export const findErrorTrackingWidget = (workItem) =>
|
||||
workItem?.widgets?.find((widget) => widget.type === WIDGET_TYPE_ERROR_TRACKING);
|
||||
|
||||
export const findHealthStatusWidget = (workItem) =>
|
||||
workItem?.widgets?.find((widget) => widget.type === WIDGET_TYPE_HEALTH_STATUS);
|
||||
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ module GraphqlTriggers
|
|||
end
|
||||
|
||||
def self.user_merge_request_updated(user, merge_request)
|
||||
return unless Feature.enabled?(:merge_request_dashboard_realtime, user, type: :wip)
|
||||
return unless Feature.enabled?(:merge_request_dashboard, user, type: :wip)
|
||||
|
||||
GitlabSchema.subscriptions.trigger(:user_merge_request_updated, { user_id: user.to_gid }, merge_request)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,5 +13,17 @@ module Resolvers
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
# We call this resolver from `IssueType` where object is an `Issue` instance, and we also call this resolver
|
||||
# from `Widgets::DevelopmentType`, in which case the object is a connection type, so
|
||||
# we need to get its respective work item.
|
||||
def object
|
||||
case super
|
||||
when ::GraphQL::Pagination::Connection
|
||||
super.try(:parent)&.work_item
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ module Types
|
|||
value 'STORAGE_SIZE_ASC', 'Sort by total storage size, ascending order.', value: :storage_size_asc
|
||||
value 'STORAGE_SIZE_DESC', 'Sort by total storage size, descending order.', value: :storage_size_desc
|
||||
|
||||
value 'PATH_ASC', 'Sort by path, ascending order.', value: :path_asc
|
||||
value 'PATH_DESC', 'Sort by path, descending order.', value: :path_desc
|
||||
|
||||
value 'REPOSITORY_SIZE_ASC', 'Sort by total repository size, ascending order.', value: :repository_size_asc
|
||||
value 'REPOSITORY_SIZE_DESC', 'Sort by total repository size, descending order.', value: :repository_size_desc
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ module Types
|
|||
field :evidences, Types::EvidenceType.connection_type, null: true,
|
||||
description: 'Evidence for the release.'
|
||||
field :historical_release, GraphQL::Types::Boolean, null: true, method: :historical_release?,
|
||||
description: 'Indicates the release is an historical release.'
|
||||
description: 'Indicates the release is a historical release.'
|
||||
field :id, ::Types::GlobalIDType[Release],
|
||||
null: false,
|
||||
description: 'Global ID of the release.'
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ module Types
|
|||
class ClosingMergeRequestType < BaseObject
|
||||
graphql_name 'WorkItemClosingMergeRequest'
|
||||
|
||||
connection_type_class Types::WorkItems::Widgets::Connections::ClosingMergeRequestsConnectionType
|
||||
|
||||
authorize :read_merge_request_closing_issue
|
||||
|
||||
field :from_mr_description, GraphQL::Types::Boolean,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module WorkItems
|
||||
module Widgets
|
||||
module Connections
|
||||
# rubocop: disable Graphql/AuthorizeTypes -- counts are looking up authorized data already
|
||||
class ClosingMergeRequestsConnectionType < GraphQL::Types::Relay::BaseConnection
|
||||
graphql_name 'ClosingMergeRequestsConnectionType'
|
||||
description 'Connection details for closing merge requests data'
|
||||
|
||||
field :count,
|
||||
null: true,
|
||||
description: 'Number of merge requests that close the work item on merge.',
|
||||
resolver: Resolvers::MergeRequestsCountResolver
|
||||
end
|
||||
# rubocop: enable Graphql/AuthorizeTypes
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -475,6 +475,9 @@ module MergeRequestsHelper
|
|||
end
|
||||
|
||||
def merge_request_dashboard_role_based_data
|
||||
is_author_or_assignee = ::Feature.enabled?(:merge_request_dashboard_author_or_assignee, current_user,
|
||||
type: :gitlab_com_derisk)
|
||||
|
||||
{
|
||||
tabs: [
|
||||
{
|
||||
|
|
@ -507,7 +510,7 @@ module MergeRequestsHelper
|
|||
id: 'assigned',
|
||||
title: _('Assigned (Active)'),
|
||||
helpContent: _(''),
|
||||
query: 'assignedMergeRequests',
|
||||
query: is_author_or_assignee ? 'authorOrAssigneeMergeRequests' : 'assignedMergeRequests',
|
||||
variables: {
|
||||
reviewStates: %w[REQUESTED_CHANGES REVIEWED],
|
||||
perPage: 10
|
||||
|
|
@ -518,7 +521,7 @@ module MergeRequestsHelper
|
|||
title: _('Assigned (Inactive)'),
|
||||
hideCount: true,
|
||||
helpContent: _(''),
|
||||
query: 'assignedMergeRequests',
|
||||
query: is_author_or_assignee ? 'authorOrAssigneeMergeRequests' : 'assignedMergeRequests',
|
||||
variables: {
|
||||
reviewStates: %w[APPROVED UNAPPROVED UNREVIEWED REVIEW_STARTED],
|
||||
perPage: 10
|
||||
|
|
@ -547,7 +550,7 @@ module MergeRequestsHelper
|
|||
id: 'merged_recently_assigned',
|
||||
title: _('Assigned'),
|
||||
helpContent: _(''),
|
||||
query: 'assignedMergeRequests',
|
||||
query: is_author_or_assignee ? 'authorOrAssigneeMergeRequests' : 'assignedMergeRequests',
|
||||
variables: {
|
||||
state: 'merged',
|
||||
mergedAfter: 2.weeks.ago.to_time.iso8601,
|
||||
|
|
@ -562,6 +565,9 @@ module MergeRequestsHelper
|
|||
end
|
||||
|
||||
def merge_request_dashboard_data
|
||||
is_author_or_assignee = ::Feature.enabled?(:merge_request_dashboard_author_or_assignee, current_user,
|
||||
type: :gitlab_com_derisk)
|
||||
|
||||
if Feature.enabled?(:mr_dashboard_list_type_toggle, current_user, type: :beta) &&
|
||||
current_user.merge_request_dashboard_list_type == 'role_based'
|
||||
return merge_request_dashboard_role_based_data
|
||||
|
|
@ -578,7 +584,7 @@ module MergeRequestsHelper
|
|||
id: 'returned_to_you',
|
||||
title: _('Returned to you'),
|
||||
helpContent: _('Reviewers left feedback, or requested changes from you, on these merge requests.'),
|
||||
query: 'assignedMergeRequests',
|
||||
query: is_author_or_assignee ? 'authorOrAssigneeMergeRequests' : 'assignedMergeRequests',
|
||||
variables: {
|
||||
reviewStates: %w[REVIEWED REQUESTED_CHANGES]
|
||||
}
|
||||
|
|
@ -594,9 +600,16 @@ module MergeRequestsHelper
|
|||
},
|
||||
{
|
||||
id: 'assigned_to_you',
|
||||
title: _('Assigned to you'),
|
||||
helpContent: _("You're assigned to these merge requests, but they don't have reviewers yet."),
|
||||
query: 'assignedMergeRequests',
|
||||
title: is_author_or_assignee ? _('Your merge requests') : _('Assigned to you'),
|
||||
|
||||
helpContent: if is_author_or_assignee
|
||||
_("Merge requests you authored or are assigned to, " \
|
||||
"without reviewers.")
|
||||
else
|
||||
_("You're assigned to these merge requests, but they don't have reviewers yet.")
|
||||
end,
|
||||
|
||||
query: is_author_or_assignee ? 'authorOrAssigneeMergeRequests' : 'assignedMergeRequests',
|
||||
variables: {
|
||||
reviewerWildcardId: 'NONE'
|
||||
}
|
||||
|
|
@ -605,11 +618,11 @@ module MergeRequestsHelper
|
|||
[
|
||||
{
|
||||
id: 'waiting_for_assignee',
|
||||
title: _('Waiting for assignee'),
|
||||
title: is_author_or_assignee ? _('Waiting for author or assignee') : _('Waiting for assignee'),
|
||||
hideCount: true,
|
||||
helpContent: _(
|
||||
'Your assigned merge requests that are waiting for approvals, ' \
|
||||
'and reviews you have requested changes for.'
|
||||
"Your reviews you've requested changes for " \
|
||||
"or commented on."
|
||||
),
|
||||
query: 'reviewRequestedMergeRequests',
|
||||
variables: {
|
||||
|
|
@ -620,11 +633,8 @@ module MergeRequestsHelper
|
|||
id: 'waiting_for_approvals',
|
||||
title: _('Waiting for approvals'),
|
||||
hideCount: true,
|
||||
helpContent: _(
|
||||
'Your assigned merge requests that are waiting for approvals, ' \
|
||||
'and reviews you have requested changes for.'
|
||||
),
|
||||
query: 'assignedMergeRequests',
|
||||
helpContent: _('Your merge requests that are waiting for approvals.'),
|
||||
query: is_author_or_assignee ? 'authorOrAssigneeMergeRequests' : 'assignedMergeRequests',
|
||||
variables: {
|
||||
reviewStates: %w[UNREVIEWED UNAPPROVED REVIEW_STARTED],
|
||||
not: {
|
||||
|
|
@ -646,8 +656,8 @@ module MergeRequestsHelper
|
|||
id: 'approved_by_others',
|
||||
title: _('Approved by others'),
|
||||
hideCount: true,
|
||||
helpContent: _('Includes all merge requests you are assigned to and a reviewer has approved.'),
|
||||
query: 'assignedMergeRequests',
|
||||
helpContent: _('Your merge requests with approvals by all assigned reviewers.'),
|
||||
query: is_author_or_assignee ? 'authorOrAssigneeMergeRequests' : 'assignedMergeRequests',
|
||||
variables: {
|
||||
reviewState: 'APPROVED',
|
||||
not: {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,14 @@ class DeployToken < ApplicationRecord
|
|||
active.find_by(name: GITLAB_DEPLOY_TOKEN_NAME)
|
||||
end
|
||||
|
||||
def self.prefix_for_deploy_token
|
||||
return DEPLOY_TOKEN_PREFIX unless Feature.enabled?(:custom_prefix_for_all_token_types, :instance)
|
||||
|
||||
# Manually remove gl - we'll add this from the configuration.
|
||||
# Once the feature flag has been removed, we can change DEPLOY_TOKEN_PREFIX to `ft-`
|
||||
::Authn::TokenField::PrefixHelper.prepend_instance_prefix(DEPLOY_TOKEN_PREFIX.delete_prefix('gl'))
|
||||
end
|
||||
|
||||
def valid_for_dependency_proxy?
|
||||
group_type? &&
|
||||
active? &&
|
||||
|
|
@ -143,7 +151,7 @@ class DeployToken < ApplicationRecord
|
|||
end
|
||||
|
||||
def prefix_for_deploy_token
|
||||
DEPLOY_TOKEN_PREFIX
|
||||
self.class.prefix_for_deploy_token
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@ module Members
|
|||
validate_access!(access_requester) unless skip_authorization
|
||||
|
||||
access_requester.access_level = params[:access_level] if params[:access_level]
|
||||
limit_to_guest_if_billable_promotion_restricted(access_requester)
|
||||
|
||||
access_requester.accept_request(current_user)
|
||||
|
||||
after_execute(member: access_requester, skip_log_audit_event: skip_log_audit_event)
|
||||
|
||||
access_requester
|
||||
success({ member: access_requester })
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -30,6 +32,10 @@ module Members
|
|||
end
|
||||
end
|
||||
|
||||
def limit_to_guest_if_billable_promotion_restricted(access_requester)
|
||||
# override in EE
|
||||
end
|
||||
|
||||
def can_approve_access_requester?(access_requester)
|
||||
can?(current_user, :admin_member_access_request, access_requester.source)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -33,10 +33,8 @@
|
|||
merge_requests_search_dashboard_path: merge_requests_search_dashboard_path(assignee_username: current_user.username),
|
||||
initial_data: merge_request_dashboard_data.to_json,
|
||||
list_type: current_user.merge_request_dashboard_list_type,
|
||||
realtime_enabled: Feature.enabled?(:merge_request_dashboard_realtime, current_user, type: :wip).to_s,
|
||||
list_type_toggle_enabled: Feature.enabled?(:mr_dashboard_list_type_toggle, current_user, type: :beta).to_s
|
||||
} }
|
||||
= gl_loading_icon(size: 'lg')
|
||||
|
||||
- if !merge_request_dashboard_enabled?(current_user) || current_page?(merge_requests_search_dashboard_path)
|
||||
- if merge_request_dashboard_enabled?(current_user)
|
||||
|
|
|
|||
|
|
@ -23,6 +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-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"
|
||||
|
|
@ -50,6 +51,7 @@
|
|||
= gl_badge_tag tab_count_display(@merge_request, @diffs_count), { class: 'js-changes-tab-count', data: { gid: @merge_request.to_gid.to_s } }
|
||||
.gl-flex.gl-flex-wrap.gl-items-center.justify-content-lg-end
|
||||
#js-vue-discussion-counter{ data: { blocks_merge: @project.only_allow_merge_if_all_discussions_are_resolved?.to_s } }
|
||||
#js-submit-review-button
|
||||
- if !!@issuable_sidebar.dig(:current_user, :id)
|
||||
.gl-flex.gl-gap-3
|
||||
.js-sidebar-todo-widget-root{ data: { project_path: @issuable_sidebar[:project_full_path], iid: @issuable_sidebar[:iid], id: @issuable_sidebar[:id] } }
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
name: merge_request_dashboard_realtime
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/512629
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/179560
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/516000
|
||||
milestone: '17.9'
|
||||
name: merge_request_dashboard_author_or_assignee
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/520163
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/185925
|
||||
rollout_issue_url:
|
||||
milestone: '17.11'
|
||||
group: group::code review
|
||||
type: wip
|
||||
type: gitlab_com_derisk
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MigrateGlobalSearchSettingsInApplicationSettingsV2 < Gitlab::Database::Migration[2.2]
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
milestone '17.11'
|
||||
|
||||
class ApplicationSetting < MigrationRecord
|
||||
self.table_name = 'application_settings'
|
||||
end
|
||||
|
||||
def up
|
||||
ApplicationSetting.reset_column_information
|
||||
|
||||
application_setting = ApplicationSetting.last
|
||||
return unless application_setting
|
||||
|
||||
# rubocop:disable Gitlab/FeatureFlagWithoutActor -- Does not execute in user context
|
||||
search_settings = application_setting.search
|
||||
search_settings[:global_search_block_anonymous_searches_enabled] =
|
||||
Feature.enabled?(:block_anonymous_global_searches)
|
||||
|
||||
if Gitlab.ee?
|
||||
search_settings[:global_search_limited_indexing_enabled] =
|
||||
Feature.enabled?(:advanced_global_search_for_limited_indexing)
|
||||
end
|
||||
# rubocop:enable Gitlab/FeatureFlagWithoutActor
|
||||
|
||||
application_setting.update_columns(search: search_settings, updated_at: Time.current)
|
||||
end
|
||||
|
||||
def down
|
||||
application_setting = ApplicationSetting.last
|
||||
return unless application_setting
|
||||
|
||||
search_settings_hash = application_setting.search
|
||||
search_settings_hash.delete('global_search_block_anonymous_searches_enabled')
|
||||
search_settings_hash.delete('global_search_limited_indexing_enabled')
|
||||
application_setting.update_column(:search, search_settings_hash)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
86af367e4028b2497e9cf28dc7e5b93012df947a82e1fed2d94636296a680964
|
||||
|
|
@ -180,7 +180,7 @@ To simplify the diagram, some necessary components are omitted.
|
|||
A **secondary** site needs two different PostgreSQL databases:
|
||||
|
||||
- A read-only database instance that streams data from the main GitLab database.
|
||||
- A [read/write database instance(tracking database)](#geo-tracking-database) used internally by the **secondary** site to record what data has been replicated.
|
||||
- A [read/write database instance (tracking database)](#geo-tracking-database) used internally by the **secondary** site to record what data has been replicated.
|
||||
|
||||
The **secondary** sites also run an additional daemon: [Geo Log Cursor](#geo-log-cursor).
|
||||
|
||||
|
|
|
|||
|
|
@ -132,7 +132,10 @@ By default, GitLab uses `gl` as the instance prefix.
|
|||
|
||||
{{< alert type="note" >}}
|
||||
|
||||
Custom token prefixes apply only to feed tokens.
|
||||
Custom token prefixes apply only to the following tokens:
|
||||
|
||||
- [Feed tokens](../../security/tokens/_index.md#feed-token)
|
||||
- [Deploy tokens](../../user/project/deploy_tokens/_index.md)
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -281,7 +281,7 @@ GET /projects/:id/snippets/:snippet_id/discussions
|
|||
| Attribute | Type | Required | Description |
|
||||
| ------------------- | ---------------- | ---------- | ------------|
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths). |
|
||||
| `snippet_id` | integer | yes | The ID of an snippet. |
|
||||
| `snippet_id` | integer | yes | The ID of a snippet. |
|
||||
|
||||
```json
|
||||
[
|
||||
|
|
@ -384,7 +384,7 @@ Parameters:
|
|||
| --------------- | -------------- | -------- | ----------- |
|
||||
| `discussion_id` | integer | yes | The ID of a discussion item. |
|
||||
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths). |
|
||||
| `snippet_id` | integer | yes | The ID of an snippet. |
|
||||
| `snippet_id` | integer | yes | The ID of a snippet. |
|
||||
|
||||
```shell
|
||||
curl --request POST \
|
||||
|
|
@ -407,7 +407,7 @@ Parameters:
|
|||
| --------------- | -------------- | -------- | ----------- |
|
||||
| `body` | string | yes | The content of a discussion. |
|
||||
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths). |
|
||||
| `snippet_id` | integer | yes | The ID of an snippet. |
|
||||
| `snippet_id` | integer | yes | The ID of a snippet. |
|
||||
| `created_at` | string | no | Date time string, ISO 8601 formatted, such as `2016-03-11T03:45:40Z`. Requires administrator or project/group owner rights. |
|
||||
|
||||
```shell
|
||||
|
|
@ -432,7 +432,7 @@ Parameters:
|
|||
| `discussion_id` | integer | yes | The ID of a thread. |
|
||||
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths). |
|
||||
| `note_id` | integer | yes | The ID of a thread note. |
|
||||
| `snippet_id` | integer | yes | The ID of an snippet. |
|
||||
| `snippet_id` | integer | yes | The ID of a snippet. |
|
||||
| `created_at` | string | no | Date time string, ISO 8601 formatted, such as `2016-03-11T03:45:40Z`. Requires administrator or project/group owner rights. |
|
||||
|
||||
```shell
|
||||
|
|
@ -457,7 +457,7 @@ Parameters:
|
|||
| `discussion_id` | integer | yes | The ID of a thread. |
|
||||
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths). |
|
||||
| `note_id` | integer | yes | The ID of a thread note. |
|
||||
| `snippet_id` | integer | yes | The ID of an snippet. |
|
||||
| `snippet_id` | integer | yes | The ID of a snippet. |
|
||||
|
||||
```shell
|
||||
curl --request PUT \
|
||||
|
|
@ -480,7 +480,7 @@ Parameters:
|
|||
| `discussion_id` | integer | yes | The ID of a discussion. |
|
||||
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths). |
|
||||
| `note_id` | integer | yes | The ID of a discussion note. |
|
||||
| `snippet_id` | integer | yes | The ID of an snippet. |
|
||||
| `snippet_id` | integer | yes | The ID of a snippet. |
|
||||
|
||||
```shell
|
||||
curl --request DELETE \
|
||||
|
|
@ -976,7 +976,7 @@ curl --request POST \
|
|||
changes in the file changed the line number. For the discussion about a fix, see
|
||||
[issue 32516](https://gitlab.com/gitlab-org/gitlab/-/issues/325161).
|
||||
- If you specify incorrect `base`, `head`, `start`, or `SHA` parameters, you might run
|
||||
into the bug described in [issue #296829)](https://gitlab.com/gitlab-org/gitlab/-/issues/296829).
|
||||
into the bug described in [issue #296829](https://gitlab.com/gitlab-org/gitlab/-/issues/296829).
|
||||
|
||||
To create a new thread:
|
||||
|
||||
|
|
|
|||
|
|
@ -962,6 +962,6 @@ Example response:
|
|||
|
||||
{{< alert type="note" >}}
|
||||
|
||||
The `health_status` parameter can only be in an "Healthy" or "Unhealthy" state, while the `health` parameter can be empty, "Healthy", or contain the actual error message.
|
||||
The `health_status` parameter can only be in a "Healthy" or "Unhealthy" state, while the `health` parameter can be empty, "Healthy", or contain the actual error message.
|
||||
|
||||
{{< /alert >}}
|
||||
|
|
|
|||
|
|
@ -977,6 +977,6 @@ Example response:
|
|||
|
||||
{{< alert type="note" >}}
|
||||
|
||||
The `health_status` parameter can only be in an "Healthy" or "Unhealthy" state, while the `health` parameter can be empty, "Healthy", or contain the actual error message.
|
||||
The `health_status` parameter can only be in a "Healthy" or "Unhealthy" state, while the `health` parameter can be empty, "Healthy", or contain the actual error message.
|
||||
|
||||
{{< /alert >}}
|
||||
|
|
|
|||
|
|
@ -19736,6 +19736,7 @@ The connection type for [`WorkItemClosingMergeRequest`](#workitemclosingmergereq
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="workitemclosingmergerequestconnectioncount"></a>`count` | [`Int`](#int) | Number of merge requests that close the work item on merge. |
|
||||
| <a id="workitemclosingmergerequestconnectionedges"></a>`edges` | [`[WorkItemClosingMergeRequestEdge]`](#workitemclosingmergerequestedge) | A list of edges. |
|
||||
| <a id="workitemclosingmergerequestconnectionnodes"></a>`nodes` | [`[WorkItemClosingMergeRequest]`](#workitemclosingmergerequest) | A list of nodes. |
|
||||
| <a id="workitemclosingmergerequestconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
|
@ -27203,7 +27204,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="groupapprovalpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="groupapprovalpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
| <a id="groupapprovalpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Group.autocompleteUsers`
|
||||
|
||||
|
|
@ -28124,7 +28125,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="grouppipelineexecutionpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="grouppipelineexecutionpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
| <a id="grouppipelineexecutionpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Group.projectComplianceRequirementsStatus`
|
||||
|
||||
|
|
@ -28321,7 +28322,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="groupscanexecutionpoliciesactionscantypes"></a>`actionScanTypes` | [`[SecurityReportTypeEnum!]`](#securityreporttypeenum) | Filters policies by the action scan type. Only these scan types are supported: `dast`, `secret_detection`, `cluster_image_scanning`, `container_scanning`, `sast`, `sast_iac`, `dependency_scanning`. |
|
||||
| <a id="groupscanexecutionpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="groupscanexecutionpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
| <a id="groupscanexecutionpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Group.scanResultPolicies`
|
||||
|
||||
|
|
@ -28343,7 +28344,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="groupscanresultpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="groupscanresultpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
| <a id="groupscanresultpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Group.securityPolicyProjectSuggestions`
|
||||
|
||||
|
|
@ -28552,7 +28553,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="groupvulnerabilitymanagementpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="groupvulnerabilitymanagementpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
| <a id="groupvulnerabilitymanagementpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Group.vulnerabilitySeveritiesCount`
|
||||
|
||||
|
|
@ -32528,7 +32529,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="namespaceapprovalpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="namespaceapprovalpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
| <a id="namespaceapprovalpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Namespace.complianceFrameworks`
|
||||
|
||||
|
|
@ -32628,7 +32629,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="namespacepipelineexecutionpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="namespacepipelineexecutionpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
| <a id="namespacepipelineexecutionpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Namespace.projects`
|
||||
|
||||
|
|
@ -32697,7 +32698,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="namespacescanexecutionpoliciesactionscantypes"></a>`actionScanTypes` | [`[SecurityReportTypeEnum!]`](#securityreporttypeenum) | Filters policies by the action scan type. Only these scan types are supported: `dast`, `secret_detection`, `cluster_image_scanning`, `container_scanning`, `sast`, `sast_iac`, `dependency_scanning`. |
|
||||
| <a id="namespacescanexecutionpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="namespacescanexecutionpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
| <a id="namespacescanexecutionpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Namespace.scanResultPolicies`
|
||||
|
||||
|
|
@ -32719,7 +32720,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="namespacescanresultpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="namespacescanresultpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
| <a id="namespacescanresultpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Namespace.vulnerabilityManagementPolicies`
|
||||
|
||||
|
|
@ -32741,7 +32742,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="namespacevulnerabilitymanagementpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="namespacevulnerabilitymanagementpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
| <a id="namespacevulnerabilitymanagementpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Namespace.workItem`
|
||||
|
||||
|
|
@ -34602,7 +34603,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="projectapprovalpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="projectapprovalpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
| <a id="projectapprovalpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Project.autocompleteUsers`
|
||||
|
||||
|
|
@ -35889,7 +35890,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="projectpipelineexecutionpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="projectpipelineexecutionpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
| <a id="projectpipelineexecutionpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Project.pipelineSchedules`
|
||||
|
||||
|
|
@ -36113,7 +36114,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="projectscanexecutionpoliciesactionscantypes"></a>`actionScanTypes` | [`[SecurityReportTypeEnum!]`](#securityreporttypeenum) | Filters policies by the action scan type. Only these scan types are supported: `dast`, `secret_detection`, `cluster_image_scanning`, `container_scanning`, `sast`, `sast_iac`, `dependency_scanning`. |
|
||||
| <a id="projectscanexecutionpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="projectscanexecutionpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
| <a id="projectscanexecutionpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Project.scanResultPolicies`
|
||||
|
||||
|
|
@ -36135,7 +36136,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="projectscanresultpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="projectscanresultpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
| <a id="projectscanresultpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Project.securityExclusion`
|
||||
|
||||
|
|
@ -36463,7 +36464,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="projectvulnerabilitymanagementpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="projectvulnerabilitymanagementpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
| <a id="projectvulnerabilitymanagementpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Project.vulnerabilitySeveritiesCount`
|
||||
|
||||
|
|
@ -37270,7 +37271,7 @@ Represents a release.
|
|||
| <a id="releasedescription"></a>`description` | [`String`](#string) | Description (also known as "release notes") of the release. |
|
||||
| <a id="releasedescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of `description`. |
|
||||
| <a id="releaseevidences"></a>`evidences` | [`ReleaseEvidenceConnection`](#releaseevidenceconnection) | Evidence for the release. (see [Connections](#connections)) |
|
||||
| <a id="releasehistoricalrelease"></a>`historicalRelease` | [`Boolean`](#boolean) | Indicates the release is an historical release. |
|
||||
| <a id="releasehistoricalrelease"></a>`historicalRelease` | [`Boolean`](#boolean) | Indicates the release is a historical release. |
|
||||
| <a id="releaseid"></a>`id` | [`ReleaseID!`](#releaseid) | Global ID of the release. |
|
||||
| <a id="releaselinks"></a>`links` | [`ReleaseLinks`](#releaselinks) | Links of the release. |
|
||||
| <a id="releasemilestones"></a>`milestones` | [`MilestoneConnection`](#milestoneconnection) | Milestones associated to the release. (see [Connections](#connections)) |
|
||||
|
|
@ -43746,6 +43747,8 @@ Values for sorting projects.
|
|||
| <a id="namespaceprojectsortlfs_objects_size_desc"></a>`LFS_OBJECTS_SIZE_DESC` | Sort by total LFS object size, descending order. |
|
||||
| <a id="namespaceprojectsortpackages_size_asc"></a>`PACKAGES_SIZE_ASC` | Sort by total package size, ascending order. |
|
||||
| <a id="namespaceprojectsortpackages_size_desc"></a>`PACKAGES_SIZE_DESC` | Sort by total package size, descending order. |
|
||||
| <a id="namespaceprojectsortpath_asc"></a>`PATH_ASC` | Sort by path, ascending order. |
|
||||
| <a id="namespaceprojectsortpath_desc"></a>`PATH_DESC` | Sort by path, descending order. |
|
||||
| <a id="namespaceprojectsortrepository_size_asc"></a>`REPOSITORY_SIZE_ASC` | Sort by total repository size, ascending order. |
|
||||
| <a id="namespaceprojectsortrepository_size_desc"></a>`REPOSITORY_SIZE_DESC` | Sort by total repository size, descending order. |
|
||||
| <a id="namespaceprojectsortsimilarity"></a>`SIMILARITY` | Most similar to the search query. |
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ Example response:
|
|||
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/407775) in GitLab 16.1.
|
||||
- Specify a service account user username or name was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/144841) in GitLab 16.10.
|
||||
- Specify a service account user email address was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181456) in GitLab 17.9 [with a flag](../administration/feature_flags.md) named `group_service_account_custom_email`.
|
||||
- [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/186476) in GitLab 17.11. Feature flag `group_service_account_custom_email` removed.
|
||||
|
||||
{{< /history >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ While GitLab offers a [built-in deployment solution](_index.md), you might prefe
|
|||
GitLab can receive deployment events from these external tools and allows you to track the deployments within GitLab.
|
||||
For example, the following features are available by setting up tracking:
|
||||
|
||||
- [See when an merge request has been deployed, and to which environment](../../user/project/merge_requests/widgets.md#post-merge-pipeline-status).
|
||||
- [See when a merge request has been deployed, and to which environment](../../user/project/merge_requests/widgets.md#post-merge-pipeline-status).
|
||||
- [Filter merge requests by environment or deployment date](../../user/project/merge_requests/_index.md#by-environment-or-deployment-date).
|
||||
- [DevOps Research and Assessment (DORA) metrics](../../user/analytics/dora_metrics.md).
|
||||
- [View environments and deployments](_index.md#view-environments-and-deployments).
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ Some terms on MLflow are named differently in GitLab:
|
|||
|
||||
### Setting up for testing
|
||||
|
||||
To test the an script with MLflow with GitLab as the backend:
|
||||
To test the script with MLflow with GitLab as the backend:
|
||||
|
||||
1. Install MLflow:
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class that we use for all of our database migrations. This subclass is `Gitlab::
|
|||
includes all the helpers that developers can use. You can see many use cases of helpers built
|
||||
in-house in [Avoiding downtime in migrations](avoiding_downtime_in_migrations.md).
|
||||
|
||||
Sometimes, we need to add or modify existing an helper's functionality without having a reverse effect on all the
|
||||
Sometimes, we need to add or modify existing a helper's functionality without having a reverse effect on all the
|
||||
previous database migrations. That's why we introduced versioning to `Gitlab::Database::Migration`. Now,
|
||||
each database migration can inherit the latest version of this class at the time of the writing the database migration.
|
||||
After we add a new feature, those old database migrations are no longer affected. We usually
|
||||
|
|
|
|||
|
|
@ -468,7 +468,7 @@ RSpec.describe MergeRequests::UpdateHeadPipelineWorker do
|
|||
let(:event) { pipeline_created_event }
|
||||
end
|
||||
|
||||
# This shared example ensures that an published event is ignored. This might be useful for
|
||||
# This shared example ensures that a published event is ignored. This might be useful for
|
||||
# conditional dispatch testing.
|
||||
it_behaves_like 'ignores the published event' do
|
||||
let(:event) { pipeline_created_event }
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ See the Vuex documentation for examples of [committing mutations from components
|
|||
|
||||
When a request is made we often want to show a loading state to the user.
|
||||
|
||||
Instead of creating an mutation to toggle the loading state, we should:
|
||||
Instead of creating a mutation to toggle the loading state, we should:
|
||||
|
||||
1. A mutation with type `REQUEST_SOMETHING`, to toggle the loading state
|
||||
1. A mutation with type `RECEIVE_SOMETHING_SUCCESS`, to handle the success callback
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ An example of a single event selection rule which updates a unique count metric
|
|||
|
||||
### Filters
|
||||
|
||||
Filters are used to constrain which events cause an metric to increase.
|
||||
Filters are used to constrain which events cause a metric to increase.
|
||||
|
||||
This filter includes only `pull_package` events with `label: rubygems`:
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ We use pytest for testing Python code. To learn more about writing and running t
|
|||
1. **[Python Testing with pytest (Book)](https://pragprog.com/titles/bopytest2/python-testing-with-pytest-second-edition/)**
|
||||
This book is a comprehensive guide to testing Python code with pytest. It covers everything from the basics of writing tests to advanced topics like fixtures, plugins, and test organization.
|
||||
|
||||
1. **[Python Function to flowchart)](https://gitlab.com/srayner/funcgraph/)**
|
||||
1. **[Python Function to flowchart](https://gitlab.com/srayner/funcgraph/)**
|
||||
This project takes any Python function and automatically creates a visual flowchart showing how the code works.
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ separate Redis clusters helps for two reasons:
|
|||
- Each has different persistence requirements.
|
||||
- Load isolation.
|
||||
|
||||
For example, the cache instance can behave like an least-recently used
|
||||
For example, the cache instance can behave like a least-recently used
|
||||
(LRU) cache by setting the `maxmemory` configuration option. That option
|
||||
should not be set for the queues or persistent clusters because data
|
||||
would be evicted from memory at random times. This would cause jobs to
|
||||
|
|
|
|||
|
|
@ -779,7 +779,7 @@ To create a new Git tag to rebuild the analyzer, follow these steps:
|
|||
|
||||
This should be done on the **18th of each month**. Though, this is a soft deadline and there is no harm in doing it within a few days after.
|
||||
|
||||
First, create an new issue for a release with a script from this repo: `./scripts/release_issue.rb MAJOR.MINOR`.
|
||||
First, create a new issue for a release with a script from this repo: `./scripts/release_issue.rb MAJOR.MINOR`.
|
||||
This issue will guide you through the whole release process. In general, you have to perform the following tasks:
|
||||
|
||||
- Check the list of supported technologies in GitLab documentation.
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ difficult to achieve locally. Ordering issues are easier to reproduce by repeate
|
|||
any table has more than 500 columns. It could pass in the merge request, but fail later in
|
||||
`master` if the order of tests changes.
|
||||
- [Example 2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91016/diffs): A test asserts
|
||||
that trying to find a record with an nonexistent ID returns an error message. The test uses an
|
||||
that trying to find a record with a nonexistent ID returns an error message. The test uses an
|
||||
hardcoded ID that's supposed to not exist (for example, `42`). If the test is run early in the test
|
||||
suite, it might pass as not enough records were created before it, but as soon as it would run
|
||||
later in the suite, there could be a record that actually has the ID `42`, hence the test would
|
||||
|
|
|
|||
|
|
@ -114,14 +114,14 @@ participant Rails
|
|||
participant Gitaly
|
||||
|
||||
Note left of Git on client: git clone/fetch
|
||||
Git on client->>+Workhorse: GET /foo/bar.git/info/refs/?service=git-upload-pack
|
||||
Git on client->>+Workhorse: GET /foo/bar.git/info/refs?service=git-upload-pack
|
||||
Workhorse->>+Rails: GET Repositories::GitHttpController#info_refs
|
||||
Note right of Rails: Access check/Log activity
|
||||
Rails-->>Workhorse: 200 OK, Gitlab::Workhorse.git_http_ok
|
||||
Workhorse->>+Gitaly: SmartHTTPService.InfoRefsUploadPack gRPC request
|
||||
Gitaly -->>-Workhorse: SmartHTTPService.InfoRefsUploadPack gRPC response
|
||||
Workhorse-->>-Git on client: send info-refs response
|
||||
Git on client->>+Workhorse: GET /foo/bar.git/info/refs/?service=git-upload-pack
|
||||
Git on client->>+Workhorse: GET /foo/bar.git/info/refs?service=git-upload-pack
|
||||
Workhorse->>+Rails: GET Repositories::GitHttpController#git_receive_pack
|
||||
Note right of Rails: Access check/Update statistics
|
||||
Rails-->>Workhorse: 200 OK, Gitlab::Workhorse.git_http_ok
|
||||
|
|
@ -141,14 +141,14 @@ participant Rails
|
|||
participant Gitaly
|
||||
|
||||
Note left of Git on client: git push
|
||||
Git on client->>+Workhorse: GET /foo/bar.git/info/refs/?service=git-receive-pack
|
||||
Git on client->>+Workhorse: GET /foo/bar.git/info/refs?service=git-receive-pack
|
||||
Workhorse->>+Rails: GET Repositories::GitHttpController#info_refs
|
||||
Note right of Rails: Access check/Log activity
|
||||
Rails-->>Workhorse: 200 OK, Gitlab::Workhorse.git_http_ok
|
||||
Workhorse->>+Gitaly: SmartHTTPService.InfoRefsReceivePack gRPC request
|
||||
Gitaly -->>-Workhorse: SmartHTTPService.InfoRefsReceivePack gRPC response
|
||||
Workhorse-->>-Git on client: send info-refs response
|
||||
Git on client->>+Workhorse: GET /foo/bar.git/info/refs/?service=git-receive-pack
|
||||
Git on client->>+Workhorse: GET /foo/bar.git/info/refs?service=git-receive-pack
|
||||
Workhorse->>+Rails: GET Repositories::GitHttpController#git_receive_pack
|
||||
Note right of Rails: Access check/Update statistics
|
||||
Rails-->>Workhorse: 200 OK, Gitlab::Workhorse.git_http_ok
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ Users with phone numbers from unsupported countries can try [credit card verific
|
|||
|
||||
### Partially supported countries
|
||||
|
||||
A user might not receive a one-time password (OTP) if their phone number is from an partially supported country. Whether a message is delivered depends on country enforcement and regulation.
|
||||
A user might not receive a one-time password (OTP) if their phone number is from a partially supported country. Whether a message is delivered depends on country enforcement and regulation.
|
||||
|
||||
The following countries are partially supported:
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ Sign in with an existing Amazon account or create a new one.
|
|||
|
||||
## Create a Kubernetes cluster
|
||||
|
||||
To create an new cluster on Amazon EKS:
|
||||
To create a new cluster on Amazon EKS:
|
||||
|
||||
- Follow the steps in [Create an Amazon EKS cluster](../../../user/infrastructure/clusters/connect/new_eks_cluster.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -378,7 +378,7 @@ Some scanners behave differently in a `scan` action than they do in a regular CI
|
|||
scan.
|
||||
|
||||
- Static Application Security Testing (SAST): Runs only if the repository contains
|
||||
[files supported by SAST)](../sast/_index.md#supported-languages-and-frameworks).
|
||||
[files supported by SAST](../sast/_index.md#supported-languages-and-frameworks).
|
||||
- Secret detection:
|
||||
- Only rules with the default ruleset are supported.
|
||||
[Replacing](../secret_detection/pipeline/configure.md#replace-the-default-ruleset) or [extending](../secret_detection/pipeline/configure.md#extend-the-default-ruleset)
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ Different features are available in different [GitLab tiers](https://about.gitla
|
|||
|
||||
Pipeline secret detection is optimized to balance coverage and run time.
|
||||
Only the current state of the repository and future commits are scanned for secrets.
|
||||
To identify secrets already present in the repository's history, run an historic scan once
|
||||
To identify secrets already present in the repository's history, run a historic scan once
|
||||
after enabling pipeline secret detection. Scan results are available only after the pipeline is completed.
|
||||
|
||||
Exactly what is scanned for secrets depends on the type of pipeline,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ Each dashboard provides a unique viewpoint of your security posture.
|
|||
### Project Security Dashboard
|
||||
|
||||
The Project Security Dashboard shows the total number of vulnerabilities detected over time,
|
||||
with up to 365 days of historical data for a given project. The dashboard is an historical view of open vulnerabilities in the default branch. Open vulnerabilities are those of only `Needs triage` or `Confirmed` status (`Dismissed` or `Resolved` vulnerabilities are excluded).
|
||||
with up to 365 days of historical data for a given project. The dashboard is a historical view of open vulnerabilities in the default branch. Open vulnerabilities are those of only `Needs triage` or `Confirmed` status (`Dismissed` or `Resolved` vulnerabilities are excluded).
|
||||
|
||||
To view a project's security dashboard:
|
||||
|
||||
|
|
|
|||
|
|
@ -281,13 +281,10 @@ To link the SAML groups:
|
|||
1. Select **Save**.
|
||||
1. Repeat to add additional group links if required.
|
||||
|
||||

|
||||
|
||||
## Manage GitLab Duo seat assignment
|
||||
|
||||
{{< details >}}
|
||||
|
||||
- Tier: Premium, Ultimate
|
||||
- Offering: GitLab.com
|
||||
|
||||
{{< /details >}}
|
||||
|
|
@ -344,9 +341,7 @@ To avoid this issue, you can use the Azure AD integration, which:
|
|||
- Supports only Group Links configured with group unique identifiers (like `12345678-9abc-def0-1234-56789abcde`)
|
||||
when it processes Group Sync.
|
||||
|
||||
Alternatively, you can change the [group claims](https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-fed-group-claims#configure-the-microsoft-entra-application-registration-for-group-attributes) to use the **Groups assigned to the application** option.
|
||||
|
||||

|
||||
Alternatively, you can change the [group claims](https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-fed-group-claims) to use the **Groups assigned to the application** option.
|
||||
|
||||
### Configure Azure AD
|
||||
|
||||
|
|
@ -426,7 +421,6 @@ To configure for GitLab Self-Managed:
|
|||
|
||||
{{< details >}}
|
||||
|
||||
- Tier: Premium, Ultimate
|
||||
- Offering: GitLab Self-Managed, GitLab Dedicated
|
||||
|
||||
{{< /details >}}
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 35 KiB |
|
|
@ -512,7 +512,7 @@ Troubleshooting sections.
|
|||
|
||||
#### 422 error with non-allowed email
|
||||
|
||||
You might get an 422 error that states "Email is not allowed for sign-up. Please use your regular email address."
|
||||
You might get a 422 error that states "Email is not allowed for sign-up. Please use your regular email address."
|
||||
|
||||
This message might indicate that you must add or remove a domain from your domain allowlist or denylist settings.
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ To access your user settings:
|
|||
|
||||
## Generate or change your Support PIN
|
||||
|
||||
GitLab Support may ask for an personal identification number (PIN) to validate your identity.
|
||||
GitLab Support may ask for a personal identification number (PIN) to validate your identity.
|
||||
The PIN expires seven days after creation.
|
||||
|
||||
To generate a new Support PIN:
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ To clear the cache:
|
|||
|
||||
## Base domain
|
||||
|
||||
Specifying a base domain automatically sets `KUBE_INGRESS_BASE_DOMAIN` as an deployment variable.
|
||||
Specifying a base domain automatically sets `KUBE_INGRESS_BASE_DOMAIN` as a deployment variable.
|
||||
If you are using [Auto DevOps](../../../topics/autodevops/_index.md), this domain is used for the different
|
||||
stages. For example, Auto Review Apps and Auto Deploy.
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ To determine the external Ingress IP address, or external Ingress hostname:
|
|||
cluster. This information can then be used to set up DNS entries and forwarding
|
||||
rules that allow external access to your deployed applications.
|
||||
|
||||
Depending an your Ingress, the external IP address can be retrieved in various ways.
|
||||
Depending on your Ingress, the external IP address can be retrieved in various ways.
|
||||
This list provides a generic solution, and some GitLab-specific approaches:
|
||||
|
||||
- In general, you can list the IP addresses of all load balancers by running:
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ open and watch updates in real time or you can return to it later.
|
|||
To cancel imports that are pending or in progress, next to the imported project, select **Cancel**.
|
||||
If the import has already started, the imported files are kept.
|
||||
|
||||
To open an repository in GitLab URL after it has been imported, select its GitLab path.
|
||||
To open a repository in GitLab URL after it has been imported, select its GitLab path.
|
||||
|
||||
Completed imports can be re-imported by selecting **Re-import** and specifying new name. This creates a new copy of the source project.
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ that can provide useful signals.
|
|||
### CPU and Memory
|
||||
|
||||
There are two main RPCs that handle clones/fetches. The following log entry
|
||||
fields an be used to inspect how much system resources are consumed by
|
||||
fields can be used to inspect how much system resources are consumed by
|
||||
clones/fetches for a given repository.
|
||||
|
||||
The following are log entry fields in the Gitaly logs that can be filtered on:
|
||||
|
|
|
|||
|
|
@ -1254,7 +1254,7 @@ or completely separately.
|
|||
|
||||
{{< /tabs >}}
|
||||
|
||||
1. [Deprecated in GitLab 16.0 and planned for removal in 19.0)](../../../update/deprecations.md#sidekiq-delivery-method-for-incoming_email-and-service_desk_email-is-deprecated):
|
||||
1. [Deprecated in GitLab 16.0 and planned for removal in 19.0](../../../update/deprecations.md#sidekiq-delivery-method-for-incoming_email-and-service_desk_email-is-deprecated):
|
||||
If you experience issues with the `webhook` setup, use `sidekiq` to deliver the email payload directly to GitLab Sidekiq using Redis.
|
||||
|
||||
{{< tabs >}}
|
||||
|
|
|
|||
|
|
@ -99,12 +99,12 @@ module API
|
|||
source = find_source(source_type, params[:id])
|
||||
|
||||
access_requester = source.requesters.find_by!(user_id: params[:user_id])
|
||||
member = ::Members::ApproveAccessRequestService
|
||||
result = ::Members::ApproveAccessRequestService
|
||||
.new(current_user, declared_params)
|
||||
.execute(access_requester)
|
||||
|
||||
status :created
|
||||
present member, with: Entities::Member
|
||||
present result[:member], with: Entities::Member
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
|
|
|
|||
|
|
@ -603,7 +603,7 @@ module API
|
|||
get ':id/merge_requests/:merge_request_iid/diffs', feature_category: :code_review_workflow, urgency: :low do
|
||||
merge_request = find_merge_request_with_access(params[:merge_request_iid])
|
||||
|
||||
present paginate(merge_request.merge_request_diff.paginated_diffs(params[:page], params[:per_page])).diffs, with: Entities::Diff, enable_unidiff: declared_params[:unidiff]
|
||||
present merge_request.merge_request_diff.paginated_diffs(params[:page], params[:per_page]).diffs, with: Entities::Diff, enable_unidiff: declared_params[:unidiff]
|
||||
end
|
||||
|
||||
desc 'Get the merge request raw diffs' do
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ module Authn
|
|||
module Tokens
|
||||
class DeployToken
|
||||
def self.prefix?(plaintext)
|
||||
plaintext.start_with?(::DeployToken::DEPLOY_TOKEN_PREFIX)
|
||||
deploy_token_prefixes = [::DeployToken.prefix_for_deploy_token,
|
||||
Authn::TokenField::PrefixHelper.default_instance_prefix(::DeployToken.prefix_for_deploy_token)]
|
||||
|
||||
plaintext.start_with?(*deploy_token_prefixes)
|
||||
end
|
||||
|
||||
attr_reader :revocable, :source
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ module Gitlab
|
|||
if ([version]"#{GLAB_REQUIRED_VERSION}" -le [version]$glabVersion) {
|
||||
#{GLAB_ENV_SET_WINDOWS}
|
||||
#{GLAB_LOGIN_WINDOWS}
|
||||
#{glab_create_command(GLAB_CREATE_WINDOWS)}
|
||||
#{glab_create_command('windows')}
|
||||
}
|
||||
else {
|
||||
Write-Output "#{GLAB_WARNING_MESSAGE}"
|
||||
|
|
@ -89,7 +89,7 @@ module Gitlab
|
|||
if [ "$(printf "%s\n%s" "#{GLAB_REQUIRED_VERSION}" "$(glab --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')" | sort -V | head -n1)" = "#{GLAB_REQUIRED_VERSION}" ]; then
|
||||
#{GLAB_ENV_SET_UNIX}
|
||||
#{GLAB_LOGIN_UNIX}
|
||||
#{glab_create_command(GLAB_CREATE_UNIX)}
|
||||
#{glab_create_command('unix')}
|
||||
else
|
||||
echo "#{GLAB_WARNING_MESSAGE}"
|
||||
|
||||
|
|
@ -116,9 +116,22 @@ module Gitlab
|
|||
command.freeze
|
||||
end
|
||||
|
||||
def glab_create_command(base_command)
|
||||
command = base_command.dup
|
||||
command.concat(" \"#{config[:tag_name]}\"")
|
||||
def glab_create_command(platform) # rubocop:disable Metrics/ -- It's more readable this way
|
||||
if platform == 'windows'
|
||||
command = GLAB_CREATE_WINDOWS.dup
|
||||
|
||||
# More information: https://gitlab.com/groups/gitlab-org/-/epics/15437#note_2432564707
|
||||
tag_name = config[:tag_name].presence || '$env:CI_COMMIT_TAG'
|
||||
ref = config[:ref].presence || '$env:CI_COMMIT_SHA'
|
||||
else
|
||||
command = GLAB_CREATE_UNIX.dup
|
||||
|
||||
# More information: https://gitlab.com/groups/gitlab-org/-/epics/15437#note_2432564707
|
||||
tag_name = config[:tag_name].presence || '$CI_COMMIT_TAG'
|
||||
ref = config[:ref].presence || '$CI_COMMIT_SHA'
|
||||
end
|
||||
|
||||
command.concat(" \"#{tag_name}\"")
|
||||
command.concat(" --assets-links #{stringified_json(create_asset_links)}") if create_asset_links.present?
|
||||
command.concat(" --milestone \"#{config[:milestones].join(',')}\"") if config[:milestones].present?
|
||||
command.concat(" --name \"#{config[:name]}\"") if config[:name].present?
|
||||
|
|
@ -128,7 +141,7 @@ module Gitlab
|
|||
command.concat(" --experimental-notes-text-or-file \"#{config[:description]}\"")
|
||||
end
|
||||
|
||||
command.concat(" --ref \"#{config[:ref]}\"") if config[:ref].present?
|
||||
command.concat(" --ref \"#{ref}\"") if ref.present?
|
||||
command.concat(" --tag-message \"#{config[:tag_message]}\"") if config[:tag_message].present?
|
||||
command.concat(" --released-at \"#{config[:released_at]}\"") if config[:released_at].present?
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ module Gitlab
|
|||
page ||= DEFAULT_PAGE
|
||||
per_page ||= DEFAULT_PER_PAGE
|
||||
|
||||
relation.page(page).per([per_page.to_i, DEFAULT_PER_PAGE].min)
|
||||
relation.page(page).per(per_page.to_i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24103,6 +24103,9 @@ msgstr ""
|
|||
msgid "Error tracking"
|
||||
msgstr ""
|
||||
|
||||
msgid "Error tracking service responded with an error."
|
||||
msgstr ""
|
||||
|
||||
msgid "Error updating %{issuableType}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -25172,6 +25175,9 @@ msgstr ""
|
|||
msgid "Failed to load related branches"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to load stack trace."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to load stacktrace."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -31399,9 +31405,6 @@ msgstr ""
|
|||
msgid "Includes LFS objects. It can be overridden per group, or per project. Set to 0 for no limit."
|
||||
msgstr ""
|
||||
|
||||
msgid "Includes all merge requests you are assigned to and a reviewer has approved."
|
||||
msgstr ""
|
||||
|
||||
msgid "Includes an MVC structure to help you get started"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -36856,6 +36859,9 @@ msgstr ""
|
|||
msgid "Merge requests can't be merged if the status checks did not succeed or are still running."
|
||||
msgstr ""
|
||||
|
||||
msgid "Merge requests you authored or are assigned to, without reviewers."
|
||||
msgstr ""
|
||||
|
||||
msgid "Merge train pipelines continue without the merged changes."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -55300,6 +55306,9 @@ msgstr ""
|
|||
msgid "Sentry event"
|
||||
msgstr ""
|
||||
|
||||
msgid "Sentry issue not found."
|
||||
msgstr ""
|
||||
|
||||
msgid "Sep"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -65952,6 +65961,9 @@ msgstr ""
|
|||
msgid "Vulnerability|Request/Response"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Resolve the vulnerability in these dependencies to see additional paths. GitLab shows a maximum of 20 dependency paths per vulnerability."
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Scanner:"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -66078,6 +66090,9 @@ msgstr ""
|
|||
msgid "Waiting for assignee"
|
||||
msgstr ""
|
||||
|
||||
msgid "Waiting for author or assignee"
|
||||
msgstr ""
|
||||
|
||||
msgid "Waiting for merge (open and assigned)"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -69310,9 +69325,6 @@ msgstr ""
|
|||
msgid "Your applications"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your assigned merge requests that are waiting for approvals, and reviews you have requested changes for."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your authorized applications"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -69411,9 +69423,18 @@ msgstr ""
|
|||
msgid "Your membership will expire in %{days_to_expire} days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your merge requests"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your merge requests have a new homepage!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your merge requests that are waiting for approvals."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your merge requests with approvals by all assigned reviewers."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your name"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -69486,6 +69507,9 @@ msgstr ""
|
|||
msgid "Your review"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your reviews you've requested changes for or commented on."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your search didn't match any commits. Try a different query."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -305,7 +305,7 @@
|
|||
"swagger-cli": "^4.0.4",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"timezone-mock": "^1.0.8",
|
||||
"vite": "^6.2.4",
|
||||
"vite": "^6.2.5",
|
||||
"vite-plugin-ruby": "^5.1.1",
|
||||
"vue-loader-vue3": "npm:vue-loader@17.4.2",
|
||||
"vue-test-utils-compat": "0.0.14",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ RSpec.describe 'Merge request > User sees merge request file tree sidebar', :js,
|
|||
let(:sidebar_scroller) { sidebar.find('.vue-recycle-scroller') }
|
||||
|
||||
before do
|
||||
stub_feature_flags(improved_review_experience: false)
|
||||
sign_in(user)
|
||||
visit diffs_project_merge_request_path(project, merge_request)
|
||||
wait_for_requests
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
import { GlModal } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import BeforeSubmitApproveUsersModal from '~/pages/admin/application_settings/general/components/before_submit_approve_users_modal.vue';
|
||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
|
||||
jest.mock('~/sentry/sentry_browser_wrapper');
|
||||
|
||||
describe('BeforeSubmitApproveUsersModal', () => {
|
||||
/** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */
|
||||
let wrapper;
|
||||
let beforeSubmitHook;
|
||||
let beforeSubmitHookContexts = {};
|
||||
|
||||
const modalId = 'before-submit-modal-id';
|
||||
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
const verifyApproveUsers = () => beforeSubmitHook.mock.calls[0][0]();
|
||||
const modalStub = { show: jest.fn(), hide: jest.fn() };
|
||||
const GlModalStub = stubComponent(GlModal, { methods: modalStub });
|
||||
|
||||
const createComponent = ({ provide = {} } = {}) => {
|
||||
beforeSubmitHook = jest.fn();
|
||||
|
||||
wrapper = shallowMountExtended(BeforeSubmitApproveUsersModal, {
|
||||
propsData: { id: modalId },
|
||||
provide: {
|
||||
beforeSubmitHook,
|
||||
beforeSubmitHookContexts,
|
||||
pendingUserCount: 10,
|
||||
...provide,
|
||||
},
|
||||
stubs: {
|
||||
GlModal: GlModalStub,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe('with should prevent submit', () => {
|
||||
beforeEach(() => {
|
||||
beforeSubmitHookContexts = { [modalId]: { shouldPreventSubmit: () => true } };
|
||||
createComponent();
|
||||
verifyApproveUsers();
|
||||
});
|
||||
|
||||
it('shows the modal', () => {
|
||||
expect(modalStub.show).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows a title', () => {
|
||||
expect(findModal().props('title')).toBe('Change setting and approve pending users?');
|
||||
});
|
||||
|
||||
it('shows a text', () => {
|
||||
expect(wrapper.text()).toBe(
|
||||
'By changing this setting, you can also automatically approve 10 users who are pending approval.',
|
||||
);
|
||||
});
|
||||
|
||||
it('shows a confirm button', () => {
|
||||
expect(findModal().props('actionPrimary').text).toBe('Proceed and approve 10 users');
|
||||
});
|
||||
|
||||
it('shows a secondary button', () => {
|
||||
expect(findModal().props('actionSecondary').text).toBe('Proceed without auto-approval');
|
||||
});
|
||||
|
||||
it('shows a cancel button', () => {
|
||||
expect(findModal().props('actionCancel').text).toBe('Cancel');
|
||||
});
|
||||
|
||||
it('registers the hook', () => {
|
||||
expect(beforeSubmitHook).toHaveBeenCalledWith(expect.any(Function));
|
||||
});
|
||||
|
||||
it.each(['hide', 'primary', 'secondary'])('emits %s event', (event) => {
|
||||
findModal().vm.$emit(event);
|
||||
|
||||
expect(wrapper.emitted(event)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the before submit hook has no reference ID', () => {
|
||||
it('does not show the modal', () => {
|
||||
beforeSubmitHookContexts = {};
|
||||
createComponent();
|
||||
verifyApproveUsers();
|
||||
|
||||
expect(modalStub.show).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with should prevent submit to false', () => {
|
||||
it('does not show the modal', () => {
|
||||
beforeSubmitHookContexts = { [modalId]: { shouldPreventSubmit: () => false } };
|
||||
createComponent();
|
||||
verifyApproveUsers();
|
||||
|
||||
expect(modalStub.show).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with should prevent submit not provided', () => {
|
||||
it('does not show the modal', () => {
|
||||
beforeSubmitHookContexts = { [modalId]: {} };
|
||||
createComponent();
|
||||
verifyApproveUsers();
|
||||
|
||||
expect(modalStub.show).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with should prevent raising an error', () => {
|
||||
it('captures the error with Sentry', () => {
|
||||
const error = new Error('This is an error');
|
||||
beforeSubmitHookContexts = {
|
||||
[modalId]: {
|
||||
shouldPreventSubmit: () => {
|
||||
throw error;
|
||||
},
|
||||
},
|
||||
};
|
||||
createComponent();
|
||||
verifyApproveUsers();
|
||||
|
||||
expect(Sentry.captureException).toHaveBeenCalledWith(error, {
|
||||
tags: { vue_component: 'before_submit_approve_users_modal' },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue