Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e62d575077
commit
75f2fd2ba8
|
|
@ -20,3 +20,6 @@ export const status = {
|
|||
export const SLOT_SUCCESS = 'success';
|
||||
export const SLOT_LOADING = 'loading';
|
||||
export const SLOT_ERROR = 'error';
|
||||
|
||||
export const CODE_QUALITY_SCALE_KEY = 'codeQuality';
|
||||
export const SAST_SCALE_KEY = 'sast';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
export const SEVERITY_CLASSES = {
|
||||
info: 'gl-text-blue-400',
|
||||
low: 'gl-text-orange-300',
|
||||
medium: 'gl-text-orange-400',
|
||||
high: 'gl-text-red-600',
|
||||
critical: 'gl-text-red-800',
|
||||
unknown: 'gl-text-gray-400',
|
||||
};
|
||||
|
||||
export const SEVERITY_ICONS = {
|
||||
info: 'severity-info',
|
||||
low: 'severity-low',
|
||||
medium: 'severity-medium',
|
||||
high: 'severity-high',
|
||||
critical: 'severity-critical',
|
||||
unknown: 'severity-unknown',
|
||||
};
|
||||
|
||||
export const SEVERITIES = {
|
||||
info: {
|
||||
class: SEVERITY_CLASSES.info,
|
||||
name: SEVERITY_ICONS.info,
|
||||
},
|
||||
low: {
|
||||
class: SEVERITY_CLASSES.low,
|
||||
name: SEVERITY_ICONS.low,
|
||||
},
|
||||
medium: {
|
||||
class: SEVERITY_CLASSES.medium,
|
||||
name: SEVERITY_ICONS.medium,
|
||||
},
|
||||
high: {
|
||||
class: SEVERITY_CLASSES.high,
|
||||
name: SEVERITY_ICONS.high,
|
||||
},
|
||||
critical: {
|
||||
class: SEVERITY_CLASSES.critical,
|
||||
name: SEVERITY_ICONS.critical,
|
||||
},
|
||||
unknown: {
|
||||
class: SEVERITY_CLASSES.unknown,
|
||||
name: SEVERITY_ICONS.unknown,
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { SEVERITIES as SEVERITIES_CODE_QUALITY } from '~/ci/reports/codequality_report/constants';
|
||||
import { SEVERITIES as SEVERITIES_SAST } from '~/ci/reports/sast/constants';
|
||||
import { SAST_SCALE_KEY } from './constants';
|
||||
|
||||
function mapSeverity(findings) {
|
||||
const severityInfo =
|
||||
findings.scale === SAST_SCALE_KEY ? SEVERITIES_SAST : SEVERITIES_CODE_QUALITY;
|
||||
return {
|
||||
...findings,
|
||||
class: severityInfo[findings.severity].class,
|
||||
name: severityInfo[findings.severity].name,
|
||||
};
|
||||
}
|
||||
|
||||
export function getSeverity(findings) {
|
||||
if (Array.isArray(findings)) {
|
||||
return findings.map((finding) => mapSeverity(finding));
|
||||
}
|
||||
return mapSeverity(findings);
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<script>
|
||||
import { s__ } from '~/locale';
|
||||
import ContributionEventBase from './contribution_event_base.vue';
|
||||
|
||||
export default {
|
||||
name: 'ContributionEventMerged',
|
||||
i18n: {
|
||||
message: s__(
|
||||
'ContributionEvent|Accepted merge request %{targetLink} in %{resourceParentLink}.',
|
||||
),
|
||||
},
|
||||
components: { ContributionEventBase },
|
||||
props: {
|
||||
event: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<contribution-event-base
|
||||
:event="event"
|
||||
:message="$options.i18n.message"
|
||||
icon-name="git-merge"
|
||||
icon-class="gl-text-blue-600"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -7,6 +7,7 @@ import {
|
|||
EVENT_TYPE_LEFT,
|
||||
EVENT_TYPE_PUSHED,
|
||||
EVENT_TYPE_PRIVATE,
|
||||
EVENT_TYPE_MERGED,
|
||||
} from '../constants';
|
||||
import ContributionEventApproved from './contribution_event/contribution_event_approved.vue';
|
||||
import ContributionEventExpired from './contribution_event/contribution_event_expired.vue';
|
||||
|
|
@ -14,6 +15,7 @@ import ContributionEventJoined from './contribution_event/contribution_event_joi
|
|||
import ContributionEventLeft from './contribution_event/contribution_event_left.vue';
|
||||
import ContributionEventPushed from './contribution_event/contribution_event_pushed.vue';
|
||||
import ContributionEventPrivate from './contribution_event/contribution_event_private.vue';
|
||||
import ContributionEventMerged from './contribution_event/contribution_event_merged.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
@ -126,6 +128,9 @@ export default {
|
|||
case EVENT_TYPE_PRIVATE:
|
||||
return ContributionEventPrivate;
|
||||
|
||||
case EVENT_TYPE_MERGED:
|
||||
return ContributionEventMerged;
|
||||
|
||||
default:
|
||||
return EmptyComponent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ import HiddenFilesWarning from './hidden_files_warning.vue';
|
|||
import NoChanges from './no_changes.vue';
|
||||
import TreeList from './tree_list.vue';
|
||||
import VirtualScrollerScrollSync from './virtual_scroller_scroll_sync';
|
||||
import PreRenderer from './pre_renderer.vue';
|
||||
|
||||
export default {
|
||||
name: 'DiffsApp',
|
||||
|
|
@ -65,6 +66,7 @@ export default {
|
|||
FindingsDrawer,
|
||||
DynamicScroller,
|
||||
DynamicScrollerItem,
|
||||
PreRenderer,
|
||||
VirtualScrollerScrollSync,
|
||||
CompareVersions,
|
||||
DiffFile,
|
||||
|
|
@ -93,6 +95,11 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
endpointSast: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
endpointCodequality: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
|
@ -275,6 +282,10 @@ export default {
|
|||
this.setCodequalityEndpoint(this.endpointCodequality);
|
||||
}
|
||||
|
||||
if (this.endpointSast) {
|
||||
this.setSastEndpoint(this.endpointSast);
|
||||
}
|
||||
|
||||
if (this.shouldShow) {
|
||||
this.fetchData();
|
||||
}
|
||||
|
|
@ -356,11 +367,13 @@ export default {
|
|||
'moveToNeighboringCommit',
|
||||
'setBaseConfig',
|
||||
'setCodequalityEndpoint',
|
||||
'setSastEndpoint',
|
||||
'fetchDiffFilesMeta',
|
||||
'fetchDiffFilesBatch',
|
||||
'fetchFileByFile',
|
||||
'fetchCoverageFiles',
|
||||
'fetchCodequality',
|
||||
'fetchSast',
|
||||
'rereadNoteHash',
|
||||
'startRenderDiffsQueue',
|
||||
'assignDiscussionsToDiff',
|
||||
|
|
@ -458,6 +471,10 @@ export default {
|
|||
this.fetchCodequality();
|
||||
}
|
||||
|
||||
if (this.endpointSast) {
|
||||
this.fetchSast();
|
||||
}
|
||||
|
||||
if (!this.isNotesFetched) {
|
||||
notesEventHub.$emit('fetchNotesData');
|
||||
}
|
||||
|
|
@ -663,6 +680,22 @@ export default {
|
|||
</dynamic-scroller-item>
|
||||
</template>
|
||||
<template #before>
|
||||
<pre-renderer :max-length="diffFilesLength">
|
||||
<template #default="{ item, index, active }">
|
||||
<dynamic-scroller-item :item="item" :active="active">
|
||||
<diff-file
|
||||
:file="item"
|
||||
:reviewed="fileReviews[item.id]"
|
||||
:is-first-file="index === 0"
|
||||
:is-last-file="index === diffFilesLength - 1"
|
||||
:help-page-path="helpPagePath"
|
||||
:can-current-user-fork="canCurrentUserFork"
|
||||
:view-diffs-file-by-file="viewDiffsFileByFile"
|
||||
pre-render
|
||||
/>
|
||||
</dynamic-scroller-item>
|
||||
</template>
|
||||
</pre-renderer>
|
||||
<virtual-scroller-scroll-sync v-model="virtualScrollCurrentIndex" />
|
||||
</template>
|
||||
</dynamic-scroller>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
<script>
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { NEW_CODE_QUALITY_FINDINGS } from '../i18n';
|
||||
import { NEW_CODE_QUALITY_FINDINGS, NEW_SAST_FINDINGS } from '../i18n';
|
||||
import DiffCodeQualityItem from './diff_code_quality_item.vue';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
newFindings: NEW_CODE_QUALITY_FINDINGS,
|
||||
newCodeQualityFindings: NEW_CODE_QUALITY_FINDINGS,
|
||||
newSastFindings: NEW_SAST_FINDINGS,
|
||||
},
|
||||
components: { GlButton, DiffCodeQualityItem },
|
||||
props: {
|
||||
|
|
@ -13,6 +14,10 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
sast: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -22,19 +27,35 @@ export default {
|
|||
data-testid="diff-codequality"
|
||||
class="gl-relative codequality-findings-list gl-border-top-1 gl-border-bottom-1 gl-bg-gray-10 gl-text-black-normal gl-pl-5 gl-pt-4 gl-pb-4"
|
||||
>
|
||||
<h4
|
||||
data-testid="diff-codequality-findings-heading"
|
||||
class="gl-mt-0 gl-mb-0 gl-font-base gl-font-regular"
|
||||
>
|
||||
{{ $options.i18n.newFindings }}
|
||||
</h4>
|
||||
<ul class="gl-list-style-none gl-mb-0 gl-p-0">
|
||||
<diff-code-quality-item
|
||||
v-for="finding in codeQuality"
|
||||
:key="finding.description"
|
||||
:finding="finding"
|
||||
/>
|
||||
</ul>
|
||||
<template v-if="codeQuality.length">
|
||||
<h4
|
||||
data-testid="diff-codequality-findings-heading"
|
||||
class="gl-my-0 gl-font-base gl-font-regular"
|
||||
>
|
||||
{{ $options.i18n.newCodeQualityFindings }}
|
||||
</h4>
|
||||
<ul class="gl-list-style-none gl-mb-0 gl-p-0">
|
||||
<diff-code-quality-item
|
||||
v-for="finding in codeQuality"
|
||||
:key="finding.description"
|
||||
:finding="finding"
|
||||
/>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<template v-if="sast.length">
|
||||
<h4 data-testid="diff-sast-findings-heading" class="gl-my-0 gl-font-base gl-font-regular">
|
||||
{{ $options.i18n.newSastFindings }}
|
||||
</h4>
|
||||
<ul class="gl-list-style-none gl-mb-0 gl-p-0">
|
||||
<diff-code-quality-item
|
||||
v-for="finding in sast"
|
||||
:key="finding.description"
|
||||
:finding="finding"
|
||||
/>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<gl-button
|
||||
data-testid="diff-codequality-close"
|
||||
category="tertiary"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlLink, GlIcon } from '@gitlab/ui';
|
||||
import { mapActions } from 'vuex';
|
||||
import { SEVERITY_CLASSES, SEVERITY_ICONS } from '~/ci/reports/codequality_report/constants';
|
||||
import { getSeverity } from '~/ci/reports/utils';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
||||
export default {
|
||||
|
|
@ -12,14 +12,21 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
link: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
enhancedFinding() {
|
||||
return getSeverity(this.finding);
|
||||
},
|
||||
listText() {
|
||||
return `${this.finding.severity} - ${this.finding.description}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
severityClass(severity) {
|
||||
return SEVERITY_CLASSES[severity] || SEVERITY_CLASSES.unknown;
|
||||
},
|
||||
severityIcon(severity) {
|
||||
return SEVERITY_ICONS[severity] || SEVERITY_ICONS.unknown;
|
||||
},
|
||||
toggleDrawer() {
|
||||
this.setDrawer(this.finding);
|
||||
},
|
||||
|
|
@ -33,8 +40,8 @@ export default {
|
|||
<span class="gl-mr-3">
|
||||
<gl-icon
|
||||
:size="12"
|
||||
:name="severityIcon(finding.severity)"
|
||||
:class="severityClass(finding.severity)"
|
||||
:name="enhancedFinding.name"
|
||||
:class="enhancedFinding.class"
|
||||
class="codequality-severity-icon"
|
||||
/>
|
||||
</span>
|
||||
|
|
@ -43,12 +50,13 @@ export default {
|
|||
data-testid="description-button-section"
|
||||
class="gl-display-flex"
|
||||
>
|
||||
<gl-link category="primary" variant="link" @click="toggleDrawer">
|
||||
{{ finding.severity }} - {{ finding.description }}</gl-link
|
||||
<gl-link v-if="link" category="primary" variant="link" @click="toggleDrawer">
|
||||
{{ listText }}</gl-link
|
||||
>
|
||||
<span v-else>{{ listText }}</span>
|
||||
</span>
|
||||
<span v-else data-testid="description-plain-text" class="gl-display-flex">
|
||||
{{ finding.severity }} - {{ finding.description }}
|
||||
{{ listText }}
|
||||
</span>
|
||||
</li>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { GlLoadingIcon, GlButton } from '@gitlab/ui';
|
|||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import { sprintf } from '~/locale';
|
||||
import { createAlert } from '~/alert';
|
||||
import { mapParallel } from 'ee_else_ce/diffs/components/diff_row_utils';
|
||||
import { mapParallel, mapParallelNoSast } from 'ee_else_ce/diffs/components/diff_row_utils';
|
||||
import DiffFileDrafts from '~/batch_comments/components/diff_file_drafts.vue';
|
||||
import draftCommentsMixin from '~/diffs/mixins/draft_comments';
|
||||
import { diffViewerModes } from '~/ide/constants';
|
||||
|
|
@ -14,6 +14,7 @@ import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_d
|
|||
import NoteForm from '~/notes/components/note_form.vue';
|
||||
import eventHub from '~/notes/event_hub';
|
||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { IMAGE_DIFF_POSITION_TYPE } from '../constants';
|
||||
import { SAVING_THE_COMMENT_FAILED, SOMETHING_WENT_WRONG } from '../i18n';
|
||||
import { getDiffMode } from '../store/utils';
|
||||
|
|
@ -35,7 +36,7 @@ export default {
|
|||
UserAvatarLink,
|
||||
DiffFileDrafts,
|
||||
},
|
||||
mixins: [diffLineNoteFormMixin, draftCommentsMixin],
|
||||
mixins: [diffLineNoteFormMixin, draftCommentsMixin, glFeatureFlagsMixin()],
|
||||
props: {
|
||||
diffFile: {
|
||||
type: Object,
|
||||
|
|
@ -54,6 +55,7 @@ export default {
|
|||
'getCommentFormForDiffFile',
|
||||
'diffLines',
|
||||
'fileLineCodequality',
|
||||
'fileLineSast',
|
||||
]),
|
||||
...mapGetters(['getNoteableData', 'noteableType', 'getUserData']),
|
||||
diffMode() {
|
||||
|
|
@ -90,8 +92,11 @@ export default {
|
|||
return this.getUserData;
|
||||
},
|
||||
mappedLines() {
|
||||
// TODO: Do this data generation when we receive a response to save a computed property being created
|
||||
return this.diffLines(this.diffFile).map(mapParallel(this)) || [];
|
||||
if (this.glFeatures.sastReportsInInlineDiff) {
|
||||
return this.diffLines(this.diffFile).map(mapParallel(this)) || [];
|
||||
}
|
||||
|
||||
return this.diffLines(this.diffFile).map(mapParallelNoSast(this)) || [];
|
||||
},
|
||||
imageDiscussions() {
|
||||
return this.diffFile.discussions.filter(
|
||||
|
|
|
|||
|
|
@ -88,6 +88,11 @@ export default {
|
|||
required: false,
|
||||
default: true,
|
||||
},
|
||||
preRender: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
idState() {
|
||||
return {
|
||||
|
|
@ -117,7 +122,7 @@ export default {
|
|||
return getShortShaFromFile(this.file);
|
||||
},
|
||||
showLoadingIcon() {
|
||||
return this.idState.isLoadingCollapsedDiff;
|
||||
return this.idState.isLoadingCollapsedDiff || (!this.file.renderIt && !this.isCollapsed);
|
||||
},
|
||||
hasDiff() {
|
||||
return hasDiff(this.file);
|
||||
|
|
@ -172,9 +177,6 @@ export default {
|
|||
showLocalFileReviews() {
|
||||
return Boolean(gon.current_user_id);
|
||||
},
|
||||
codequalityDiffForFile() {
|
||||
return this.codequalityDiff?.files?.[this.file.file_path] || [];
|
||||
},
|
||||
isCollapsed() {
|
||||
if (collapsedType(this.file) !== DIFF_FILE_MANUAL_COLLAPSE) {
|
||||
return this.viewDiffsFileByFile ? false : this.file.viewer?.automaticallyCollapsed;
|
||||
|
|
@ -200,6 +202,8 @@ export default {
|
|||
watch: {
|
||||
'file.id': {
|
||||
handler: function fileIdHandler() {
|
||||
if (this.preRender) return;
|
||||
|
||||
this.manageViewedEffects();
|
||||
},
|
||||
},
|
||||
|
|
@ -212,6 +216,7 @@ export default {
|
|||
newHash &&
|
||||
oldHash &&
|
||||
!this.hasDiff &&
|
||||
!this.preRender &&
|
||||
!this.idState.hasLoadedCollapsedDiff
|
||||
) {
|
||||
this.requestDiff();
|
||||
|
|
@ -220,10 +225,14 @@ export default {
|
|||
},
|
||||
},
|
||||
created() {
|
||||
if (this.preRender) return;
|
||||
|
||||
notesEventHub.$on(`loadCollapsedDiff/${this.file.file_hash}`, this.requestDiff);
|
||||
eventHub.$on(EVT_EXPAND_ALL_FILES, this.expandAllListener);
|
||||
},
|
||||
mounted() {
|
||||
if (this.preRender) return;
|
||||
|
||||
if (this.hasDiff) {
|
||||
this.postRender();
|
||||
}
|
||||
|
|
@ -231,12 +240,15 @@ export default {
|
|||
this.manageViewedEffects();
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.preRender) return;
|
||||
|
||||
eventHub.$off(EVT_EXPAND_ALL_FILES, this.expandAllListener);
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', [
|
||||
'loadCollapsedDiff',
|
||||
'assignDiscussionsToDiff',
|
||||
'setRenderIt',
|
||||
'setFileCollapsedByUser',
|
||||
'saveDiffDiscussion',
|
||||
'toggleFileCommentForm',
|
||||
|
|
@ -300,6 +312,10 @@ export default {
|
|||
.then(() => {
|
||||
idState.isLoadingCollapsedDiff = false;
|
||||
idState.hasLoadedCollapsedDiff = true;
|
||||
|
||||
if (this.file.file_hash === file.file_hash) {
|
||||
this.setRenderIt(this.file);
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
if (this.file.file_hash !== file.file_hash) return;
|
||||
|
|
@ -359,14 +375,15 @@ export default {
|
|||
|
||||
<template>
|
||||
<div
|
||||
:id="file.file_hash"
|
||||
:id="!preRender && active && file.file_hash"
|
||||
:class="{
|
||||
'is-active': currentDiffFileId === file.file_hash,
|
||||
'comments-disabled': Boolean(file.brokenSymlink),
|
||||
'has-body': showBody,
|
||||
'is-virtual-scrolling': isVirtualScrollingEnabled,
|
||||
}"
|
||||
:data-path="file.new_path"
|
||||
class="diff-file file-holder gl-border-none gl-mb-0! gl-pb-5"
|
||||
class="diff-file file-holder gl-border-none"
|
||||
>
|
||||
<diff-file-header
|
||||
:can-current-user-fork="canCurrentUserFork"
|
||||
|
|
@ -377,7 +394,6 @@ export default {
|
|||
:add-merge-request-buttons="true"
|
||||
:view-diffs-file-by-file="viewDiffsFileByFile"
|
||||
:show-local-file-reviews="showLocalFileReviews"
|
||||
:codequality-diff="codequalityDiffForFile"
|
||||
class="js-file-title file-title gl-border-1 gl-border-solid gl-border-gray-100"
|
||||
:class="hasBodyClasses.header"
|
||||
@toggleFile="handleToggle({ viaUserInteraction: true })"
|
||||
|
|
@ -406,7 +422,7 @@ export default {
|
|||
</div>
|
||||
<template v-else>
|
||||
<div
|
||||
:id="`diff-content-${file.file_hash}`"
|
||||
:id="!preRender && active && `diff-content-${file.file_hash}`"
|
||||
:class="hasBodyClasses.contentByHash"
|
||||
data-testid="content-area"
|
||||
>
|
||||
|
|
@ -530,3 +546,20 @@ export default {
|
|||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@keyframes shadow-fade {
|
||||
from {
|
||||
box-shadow: 0 0 4px #919191;
|
||||
}
|
||||
|
||||
to {
|
||||
box-shadow: 0 0 0 #dfdfdf;
|
||||
}
|
||||
}
|
||||
|
||||
.diff-file.is-active {
|
||||
box-shadow: 0 0 0 #dfdfdf;
|
||||
animation: shadow-fade 1.2s 0.1s 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -101,11 +101,6 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
codequalityDiff: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
idState() {
|
||||
return {
|
||||
|
|
@ -428,7 +423,6 @@ export default {
|
|||
toggle-class="btn-icon js-diff-more-actions"
|
||||
class="gl-pt-0!"
|
||||
data-qa-selector="dropdown_button"
|
||||
lazy
|
||||
@show="setMoreActionsShown(true)"
|
||||
@hidden="setMoreActionsShown(false)"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -15,13 +15,22 @@ export default {
|
|||
parsedCodeQuality() {
|
||||
return (this.line.left ?? this.line.right)?.codequality;
|
||||
},
|
||||
parsedSast() {
|
||||
return (this.line.left ?? this.line.right)?.sast;
|
||||
},
|
||||
codeQualityLineNumber() {
|
||||
return this.parsedCodeQuality[0].line;
|
||||
return this.parsedCodeQuality[0]?.line;
|
||||
},
|
||||
sastLineNumber() {
|
||||
return this.parsedSast[0]?.line;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hideCodeQualityFindings() {
|
||||
this.$emit('hideCodeQualityFindings', this.codeQualityLineNumber);
|
||||
this.$emit(
|
||||
'hideCodeQualityFindings',
|
||||
this.codeQualityLineNumber ? this.codeQualityLineNumber : this.sastLineNumber,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -30,6 +39,7 @@ export default {
|
|||
<template>
|
||||
<diff-code-quality
|
||||
:code-quality="parsedCodeQuality"
|
||||
:sast="parsedSast"
|
||||
@hideCodeQualityFindings="hideCodeQualityFindings"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -141,6 +141,18 @@ export default {
|
|||
},
|
||||
(props) => [props.inline, props.line.right?.codequality?.length].join(':'),
|
||||
),
|
||||
showSecurityLeft: memoize(
|
||||
(props) => {
|
||||
return props.inline && props.line.left?.sast?.length > 0;
|
||||
},
|
||||
(props) => [props.inline, props.line.left?.sast?.length].join(':'),
|
||||
),
|
||||
showSecurityRight: memoize(
|
||||
(props) => {
|
||||
return !props.inline && props.line.right?.sast?.length > 0;
|
||||
},
|
||||
(props) => [props.inline, props.line.right?.sast?.length].join(':'),
|
||||
),
|
||||
classNameMapCellLeft: memoize(
|
||||
(props) => {
|
||||
return utils.classNameMapCell({
|
||||
|
|
@ -325,12 +337,17 @@ export default {
|
|||
>
|
||||
<component
|
||||
:is="$options.CodeQualityGutterIcon"
|
||||
v-if="$options.showCodequalityLeft(props)"
|
||||
v-if="$options.showCodequalityLeft(props) || $options.showSecurityLeft(props)"
|
||||
:code-quality-expanded="props.codeQualityExpanded"
|
||||
:codequality="props.line.left.codequality"
|
||||
:sast="props.line.left.sast"
|
||||
:file-path="props.filePath"
|
||||
@showCodeQualityFindings="
|
||||
listeners.toggleCodeQualityFindings(props.line.left.codequality[0].line)
|
||||
listeners.toggleCodeQualityFindings(
|
||||
props.line.left.codequality[0]
|
||||
? props.line.left.codequality[0].line
|
||||
: props.line.left.sast[0].line,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -461,12 +478,17 @@ export default {
|
|||
>
|
||||
<component
|
||||
:is="$options.CodeQualityGutterIcon"
|
||||
v-if="$options.showCodequalityRight(props)"
|
||||
v-if="$options.showCodequalityRight(props) || $options.showSecurityRight(props)"
|
||||
:codequality="props.line.right.codequality"
|
||||
:sast="props.line.right.sast"
|
||||
:file-path="props.filePath"
|
||||
data-testid="codeQualityIcon"
|
||||
@showCodeQualityFindings="
|
||||
listeners.toggleCodeQualityFindings(props.line.right.codequality[0].line)
|
||||
listeners.toggleCodeQualityFindings(
|
||||
props.line.right.codequality[0]
|
||||
? props.line.right.codequality[0].line
|
||||
: props.line.right.sast[0].line,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -189,3 +189,7 @@ export const mapParallel = (content) => (line) => {
|
|||
commentRowClasses: hasDiscussions(left) || hasDiscussions(right) ? '' : 'js-temp-notes-holder',
|
||||
};
|
||||
};
|
||||
|
||||
export const mapParallelNoSast = (content) => {
|
||||
return mapParallel(content);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ export default {
|
|||
...mapGetters('diffs', ['commitId', 'fileLineCoverage']),
|
||||
...mapState('diffs', [
|
||||
'codequalityDiff',
|
||||
'sastDiff',
|
||||
'highlightedRow',
|
||||
'coverageLoaded',
|
||||
'selectedCommentPosition',
|
||||
|
|
@ -75,7 +76,10 @@ export default {
|
|||
);
|
||||
},
|
||||
hasCodequalityChanges() {
|
||||
return this.codequalityDiff?.files?.[this.diffFile.file_path]?.length > 0;
|
||||
return (
|
||||
this.codequalityDiff?.files?.[this.diffFile.file_path]?.length > 0 ||
|
||||
this.sastDiff?.added?.length > 0
|
||||
);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
|
|
@ -183,7 +187,10 @@ export default {
|
|||
);
|
||||
},
|
||||
getCodeQualityLine(line) {
|
||||
return (line.left ?? line.right)?.codequality?.[0]?.line;
|
||||
return (
|
||||
(line.left ?? line.right)?.codequality?.[0]?.line ||
|
||||
(line.left ?? line.right)?.sast?.[0]?.line
|
||||
);
|
||||
},
|
||||
lineDrafts(line, side) {
|
||||
return (line[side]?.lineDrafts || []).filter((entry) => entry.isDraft);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
<script>
|
||||
export default {
|
||||
inject: ['vscrollParent'],
|
||||
props: {
|
||||
maxLength: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
nextIndex: -1,
|
||||
nextItem: null,
|
||||
startedRender: false,
|
||||
width: 0,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.width = this.$el.parentNode.offsetWidth;
|
||||
|
||||
this.$_itemsWithSizeWatcher = this.$watch('vscrollParent.itemsWithSize', async () => {
|
||||
await this.$nextTick();
|
||||
|
||||
const nextItem = this.findNextToRender();
|
||||
|
||||
if (nextItem) {
|
||||
this.startedRender = true;
|
||||
requestIdleCallback(() => {
|
||||
this.nextItem = nextItem;
|
||||
|
||||
if (this.nextIndex === this.maxLength - 1) {
|
||||
this.$nextTick(() => {
|
||||
if (this.vscrollParent.itemsWithSize[this.maxLength - 1].size !== 0) {
|
||||
this.clearRendering();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (this.startedRender) {
|
||||
this.clearRendering();
|
||||
}
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$_itemsWithSizeWatcher();
|
||||
},
|
||||
methods: {
|
||||
clearRendering() {
|
||||
this.nextItem = null;
|
||||
|
||||
if (this.maxLength === this.vscrollParent.itemsWithSize.length) {
|
||||
this.$_itemsWithSizeWatcher();
|
||||
}
|
||||
},
|
||||
findNextToRender() {
|
||||
return this.vscrollParent.itemsWithSize.find(({ size }, index) => {
|
||||
const isNext = size === 0;
|
||||
|
||||
if (isNext) {
|
||||
this.nextIndex = index;
|
||||
}
|
||||
|
||||
return isNext;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="nextItem" :style="{ width: `${width}px` }" class="gl-absolute diff-file-offscreen">
|
||||
<slot
|
||||
v-bind="{ item: nextItem.item, index: nextIndex, active: true, itemWithSize: nextItem }"
|
||||
></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.diff-file-offscreen {
|
||||
top: -200%;
|
||||
left: -200%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -18,18 +18,17 @@ export default {
|
|||
|
||||
if (index < 0) return;
|
||||
|
||||
this.scrollToIndex(index);
|
||||
|
||||
if (!this.vscrollParent.itemsWithSize[index].size) {
|
||||
if (this.vscrollParent.itemsWithSize[index].size) {
|
||||
this.scrollToIndex(index);
|
||||
} else {
|
||||
this.$_itemsWithSizeWatcher = this.$watch('vscrollParent.itemsWithSize', async () => {
|
||||
await this.$nextTick();
|
||||
|
||||
if (this.vscrollParent.itemsWithSize[index].size) {
|
||||
this.$_itemsWithSizeWatcher();
|
||||
this.scrollToIndex(index);
|
||||
|
||||
await this.$nextTick();
|
||||
|
||||
this.scrollToIndex(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -41,15 +40,12 @@ export default {
|
|||
if (this.$_itemsWithSizeWatcher) this.$_itemsWithSizeWatcher();
|
||||
},
|
||||
methods: {
|
||||
async scrollToIndex(index) {
|
||||
scrollToIndex(index) {
|
||||
this.vscrollParent.scrollToItem(index);
|
||||
this.$emit('update', -1);
|
||||
|
||||
await this.$nextTick();
|
||||
|
||||
setTimeout(() => {
|
||||
handleLocationHash();
|
||||
window.scrollBy(0, 150);
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ export const CONFLICT_TEXT = {
|
|||
export const HIDE_COMMENTS = __('Hide comments');
|
||||
|
||||
export const NEW_CODE_QUALITY_FINDINGS = __('New code quality findings');
|
||||
export const NEW_SAST_FINDINGS = __('New Security findings');
|
||||
|
||||
export const BUILDING_YOUR_MR = __(
|
||||
'Building your merge request… This page will update when the build is complete.',
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ export default function initDiffsApp(store = notesStore) {
|
|||
return {
|
||||
endpointCoverage: dataset.endpointCoverage || '',
|
||||
endpointCodequality: dataset.endpointCodequality || '',
|
||||
endpointSast: dataset.endpointSast || '',
|
||||
helpPagePath: dataset.helpPagePath,
|
||||
currentUser: JSON.parse(dataset.currentUserData) || {},
|
||||
changesEmptyStateIllustration: dataset.changesEmptyStateIllustration,
|
||||
|
|
@ -79,6 +80,7 @@ export default function initDiffsApp(store = notesStore) {
|
|||
props: {
|
||||
endpointCoverage: this.endpointCoverage,
|
||||
endpointCodequality: this.endpointCodequality,
|
||||
endpointSast: this.endpointSast,
|
||||
currentUser: this.currentUser,
|
||||
helpPagePath: this.helpPagePath,
|
||||
shouldShow: this.activeTab === 'diffs',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
scrollToElement,
|
||||
} from '~/lib/utils/common_utils';
|
||||
import { createAlert, VARIANT_WARNING } from '~/alert';
|
||||
import { diffViewerModes } from '~/ide/constants';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
||||
import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
||||
|
|
@ -59,6 +60,7 @@ import {
|
|||
ERROR_DISMISSING_SUGESTION_POPOVER,
|
||||
} from '../i18n';
|
||||
import eventHub from '../event_hub';
|
||||
import { isCollapsed } from '../utils/diff_file';
|
||||
import { markFileReview, setReviewsForMergeRequest } from '../utils/file_reviews';
|
||||
import { getDerivedMergeRequestInformation } from '../utils/merge_request';
|
||||
import { queueRedisHllEvents } from '../utils/queue_events';
|
||||
|
|
@ -177,7 +179,7 @@ export const fetchDiffFilesBatch = ({ commit, state, dispatch }) => {
|
|||
};
|
||||
const hash = window.location.hash.replace('#', '').split('diff-content-').pop();
|
||||
let totalLoaded = 0;
|
||||
let scrolledVirtualScroller = hash === '';
|
||||
let scrolledVirtualScroller = false;
|
||||
|
||||
commit(types.SET_BATCH_LOADING_STATE, 'loading');
|
||||
commit(types.SET_RETRIEVING_BATCHES, true);
|
||||
|
|
@ -249,6 +251,8 @@ export const fetchDiffFilesBatch = ({ commit, state, dispatch }) => {
|
|||
return nextPage;
|
||||
})
|
||||
.then((nextPage) => {
|
||||
dispatch('startRenderDiffsQueue');
|
||||
|
||||
if (nextPage) {
|
||||
return getBatch(nextPage);
|
||||
}
|
||||
|
|
@ -381,6 +385,10 @@ export const renderFileForDiscussionId = ({ commit, rootState, state }, discussi
|
|||
const file = state.diffFiles.find((f) => f.file_hash === discussion.diff_file.file_hash);
|
||||
|
||||
if (file) {
|
||||
if (!file.renderIt) {
|
||||
commit(types.RENDER_FILE, file);
|
||||
}
|
||||
|
||||
if (file.viewer.automaticallyCollapsed) {
|
||||
notesEventHub.$emit(`loadCollapsedDiff/${file.file_hash}`);
|
||||
scrollToElement(document.getElementById(file.file_hash));
|
||||
|
|
@ -398,6 +406,46 @@ export const renderFileForDiscussionId = ({ commit, rootState, state }, discussi
|
|||
}
|
||||
};
|
||||
|
||||
export const startRenderDiffsQueue = ({ state, commit }) => {
|
||||
const diffFilesToRender = state.diffFiles.filter(
|
||||
(file) =>
|
||||
!file.renderIt &&
|
||||
file.viewer &&
|
||||
(!isCollapsed(file) || file.viewer.name !== diffViewerModes.text),
|
||||
);
|
||||
let currentDiffFileIndex = 0;
|
||||
|
||||
const checkItem = () => {
|
||||
const nextFile = diffFilesToRender[currentDiffFileIndex];
|
||||
|
||||
if (nextFile) {
|
||||
let retryCount = 0;
|
||||
currentDiffFileIndex += 1;
|
||||
commit(types.RENDER_FILE, nextFile);
|
||||
|
||||
const requestIdle = () =>
|
||||
requestIdleCallback((idleDeadline) => {
|
||||
// Wait for at least 5ms before trying to render
|
||||
// or for 5 tries and then force render the file
|
||||
if (idleDeadline.timeRemaining() >= 5 || retryCount > 4) {
|
||||
checkItem();
|
||||
} else {
|
||||
requestIdle();
|
||||
retryCount += 1;
|
||||
}
|
||||
});
|
||||
|
||||
requestIdle();
|
||||
}
|
||||
};
|
||||
|
||||
if (diffFilesToRender.length) {
|
||||
checkItem();
|
||||
}
|
||||
};
|
||||
|
||||
export const setRenderIt = ({ commit }, file) => commit(types.RENDER_FILE, file);
|
||||
|
||||
export const setInlineDiffViewType = ({ commit }) => {
|
||||
commit(types.SET_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE);
|
||||
|
||||
|
|
@ -798,7 +846,7 @@ export const toggleFullDiff = ({ dispatch, commit, getters, state }, filePath) =
|
|||
}
|
||||
};
|
||||
|
||||
export function switchToFullDiffFromRenamedFile({ commit }, { diffFile }) {
|
||||
export function switchToFullDiffFromRenamedFile({ commit, dispatch }, { diffFile }) {
|
||||
return axios
|
||||
.get(diffFile.context_lines_path, {
|
||||
params: {
|
||||
|
|
@ -825,6 +873,8 @@ export function switchToFullDiffFromRenamedFile({ commit }, { diffFile }) {
|
|||
},
|
||||
});
|
||||
commit(types.SET_CURRENT_VIEW_DIFF_FILE_LINES, { filePath: diffFile.file_path, lines });
|
||||
|
||||
dispatch('startRenderDiffsQueue');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -153,6 +153,11 @@ export const fileLineCodequality = () => () => {
|
|||
return null;
|
||||
};
|
||||
|
||||
// This function is overwritten for the inline SAST feature in EE
|
||||
export const fileLineSast = () => () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns index of a currently selected diff in diffFiles
|
||||
* @returns {number}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@ function updateDiffFilesInState(state, files) {
|
|||
return Object.assign(state, { diffFiles: files });
|
||||
}
|
||||
|
||||
function renderFile(file) {
|
||||
Object.assign(file, {
|
||||
renderIt: true,
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
[types.SET_BASE_CONFIG](state, options) {
|
||||
const {
|
||||
|
|
@ -96,6 +102,10 @@ export default {
|
|||
Object.assign(state, { coverageFiles, coverageLoaded: true });
|
||||
},
|
||||
|
||||
[types.RENDER_FILE](state, file) {
|
||||
renderFile(file);
|
||||
},
|
||||
|
||||
[types.SET_MERGE_REQUEST_DIFFS](state, mergeRequestDiffs) {
|
||||
Object.assign(state, {
|
||||
mergeRequestDiffs,
|
||||
|
|
@ -345,6 +355,10 @@ export default {
|
|||
file.viewer.manuallyCollapsed = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (file && !collapsed) {
|
||||
renderFile(file);
|
||||
}
|
||||
},
|
||||
[types.SET_CURRENT_VIEW_DIFF_FILE_LINES](state, { filePath, lines }) {
|
||||
const file = state.diffFiles.find((f) => f.file_path === filePath);
|
||||
|
|
|
|||
|
|
@ -307,6 +307,7 @@ function mergeTwoFiles(target, source) {
|
|||
...target,
|
||||
[INLINE_DIFF_LINES_KEY]: missingInline ? source[INLINE_DIFF_LINES_KEY] : originalInline,
|
||||
parallel_diff_lines: null,
|
||||
renderIt: source.renderIt,
|
||||
collapsed: source.collapsed,
|
||||
};
|
||||
}
|
||||
|
|
@ -387,6 +388,7 @@ function prepareDiffFileLines(file) {
|
|||
|
||||
function finalizeDiffFile(file) {
|
||||
Object.assign(file, {
|
||||
renderIt: true,
|
||||
isShowingFullFile: false,
|
||||
isLoadingFullFile: false,
|
||||
discussions: [],
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
push_frontend_feature_flag(:core_security_mr_widget_counts, project)
|
||||
push_frontend_feature_flag(:issue_assignees_widget, @project)
|
||||
push_frontend_feature_flag(:moved_mr_sidebar, project)
|
||||
push_frontend_feature_flag(:sast_reports_in_inline_diff, project)
|
||||
push_frontend_feature_flag(:mr_experience_survey, project)
|
||||
push_frontend_feature_flag(:saved_replies, current_user)
|
||||
push_frontend_feature_flag(:code_quality_inline_drawer, project)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ class Packages::Npm::Metadatum < ApplicationRecord
|
|||
def ensure_package_json_size
|
||||
return if package_json.to_s.size < MAX_PACKAGE_JSON_SIZE
|
||||
|
||||
errors.add(:package_json, _('structure is too large'))
|
||||
errors.add(:package_json, :too_large,
|
||||
message: format(
|
||||
_('structure is too large. Maximum size is %{max_size} characters'),
|
||||
max_size: MAX_PACKAGE_JSON_SIZE
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class Packages::Package < ApplicationRecord
|
|||
validates :name, format: { with: Gitlab::Regex.conan_recipe_component_regex }, if: :conan?
|
||||
validates :name, format: { with: Gitlab::Regex.generic_package_name_regex }, if: :generic?
|
||||
validates :name, format: { with: Gitlab::Regex.helm_package_regex }, if: :helm?
|
||||
validates :name, format: { with: Gitlab::Regex.npm_package_name_regex }, if: :npm?
|
||||
validates :name, format: { with: Gitlab::Regex.npm_package_name_regex, message: Gitlab::Regex.npm_package_name_regex_message }, if: :npm?
|
||||
validates :name, format: { with: Gitlab::Regex.nuget_package_name_regex }, if: :nuget?
|
||||
validates :name, format: { with: Gitlab::Regex.terraform_module_package_name_regex }, if: :terraform_module?
|
||||
validates :name, format: { with: Gitlab::Regex.debian_package_name_regex }, if: :debian_package?
|
||||
|
|
@ -94,7 +94,8 @@ class Packages::Package < ApplicationRecord
|
|||
validates :version, format: { with: Gitlab::Regex.pypi_version_regex }, if: :pypi?
|
||||
validates :version, format: { with: Gitlab::Regex.prefixed_semver_regex }, if: :golang?
|
||||
validates :version, format: { with: Gitlab::Regex.helm_version_regex }, if: :helm?
|
||||
validates :version, format: { with: Gitlab::Regex.semver_regex }, if: -> { composer_tag_version? || npm? || terraform_module? }
|
||||
validates :version, format: { with: Gitlab::Regex.semver_regex, message: Gitlab::Regex.semver_regex_message },
|
||||
if: -> { composer_tag_version? || npm? || terraform_module? }
|
||||
|
||||
validates :version,
|
||||
presence: true,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ module Packages
|
|||
ApplicationRecord.transaction { create_npm_package! }
|
||||
end
|
||||
|
||||
return error('Could not obtain package lease.', 400) unless package
|
||||
return error('Could not obtain package lease. Please try again.', 400) unless package
|
||||
|
||||
package
|
||||
end
|
||||
|
|
@ -40,7 +40,7 @@ module Packages
|
|||
def create_npm_metadatum!(package)
|
||||
package.create_npm_metadatum!(package_json: package_json)
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
if package.npm_metadatum && package.npm_metadatum.errors.added?(:package_json, 'structure is too large')
|
||||
if package.npm_metadatum && package.npm_metadatum.errors.where(:package_json, :too_large).any? # rubocop: disable CodeReuse/ActiveRecord
|
||||
Gitlab::ErrorTracking.track_exception(e, field_sizes: field_sizes_for_error_tracking)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ While this isn't an exhaustive list, following these steps gives you a solid sta
|
|||
- Use a long root password, stored in a vault.
|
||||
- Install trusted SSL certificate and establish a process for renewal and revocation.
|
||||
- [Configure SSH key restrictions](../security/ssh_keys_restrictions.md#restrict-allowed-ssh-key-technologies-and-minimum-length) per your organization's guidelines.
|
||||
- [Disable new sign-ups](../user/admin_area/settings/sign_up_restrictions.md#disable-new-sign-ups).
|
||||
- [Disable new sign-ups](settings/sign_up_restrictions.md#disable-new-sign-ups).
|
||||
- Require email confirmation.
|
||||
- Set password length limit, configure SSO or SAML user management.
|
||||
- Limit email domains if allowing sign-up.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
stage: Anti-Abuse
|
||||
group: Anti-Abuse
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# IP address restrictions **(FREE SELF)**
|
||||
|
||||
IP address restrictions help prevent malicious users hiding their activities behind multiple IP addresses.
|
||||
|
||||
GitLab maintains a list of the unique IP addresses used by a user to make requests over a specified period. When the
|
||||
specified limit is reached, any requests made by the user from a new IP address are rejected with a `403 Forbidden` error.
|
||||
|
||||
IP addresses are cleared from the list when no further requests have been made by the user from the IP address in the specified time period.
|
||||
|
||||
NOTE:
|
||||
When a runner runs a CI/CD job as a particular user, the runner IP address is also stored against the user's list of
|
||||
unique IP addresses. Therefore, the IP addresses per user limit should take into account the number of configured active runners.
|
||||
|
||||
## Configure IP address restrictions
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > Reporting**.
|
||||
1. Expand **Spam and Anti-bot Protection**.
|
||||
1. Update the IP address restrictions settings:
|
||||
1. Select the **Limit sign in from multiple IP addresses** checkbox to enable IP address restrictions.
|
||||
1. Enter a number in the **IP addresses per user** field, greater than or equal to `1`. This number specifies the
|
||||
maximum number of unique IP addresses a user can access GitLab from in the specified time period before requests
|
||||
from a new IP address are rejected.
|
||||
1. Enter a number in the **IP address expiration time** field, greater than or equal to `0`. This number specifies the
|
||||
time in seconds an IP address counts towards the limit for a user, taken from the time the last request was made.
|
||||
1. Select **Save changes**.
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
---
|
||||
stage: Systems
|
||||
group: Distribution
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Spamcheck anti-spam service **(FREE SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6259) in GitLab 14.8.
|
||||
|
||||
WARNING:
|
||||
Spamcheck is available to all tiers, but only on instances using GitLab Enterprise Edition (EE). For [licensing reasons](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6259#note_726605397), it is not included in the GitLab Community Edition (CE) package. You can [migrate from CE to EE](../../update/package/convert_to_ee.md).
|
||||
|
||||
[Spamcheck](https://gitlab.com/gitlab-org/spamcheck) is an anti-spam engine
|
||||
developed by GitLab originally to combat rising amount of spam in GitLab.com,
|
||||
and later made public to be used in self-managed GitLab instances.
|
||||
|
||||
## Enable Spamcheck
|
||||
|
||||
Spamcheck is only available for package-based installations:
|
||||
|
||||
1. Edit `/etc/gitlab/gitlab.rb` and enable Spamcheck:
|
||||
|
||||
```ruby
|
||||
spamcheck['enable'] = true
|
||||
```
|
||||
|
||||
1. Reconfigure GitLab:
|
||||
|
||||
```shell
|
||||
sudo gitlab-ctl reconfigure
|
||||
```
|
||||
|
||||
1. Verify that the new services `spamcheck` and `spam-classifier` are
|
||||
up and running:
|
||||
|
||||
```shell
|
||||
sudo gitlab-ctl status
|
||||
```
|
||||
|
||||
## Configure GitLab to use Spamcheck
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > Reporting**.
|
||||
1. Expand **Spam and Anti-bot Protection**.
|
||||
1. Update the Spam Check settings:
|
||||
1. Check the "Enable Spam Check via external API endpoint" checkbox.
|
||||
1. For **URL of the external Spam Check endpoint** use `grpc://localhost:8001`.
|
||||
1. Leave **Spam Check API key** blank.
|
||||
1. Select **Save changes**.
|
||||
|
||||
NOTE:
|
||||
In single-node instances, Spamcheck runs over `localhost`, and hence is running
|
||||
in an unauthenticated mode. If on multi-node instances where GitLab runs on one
|
||||
server and Spamcheck runs on another server listening over a public endpoint, it
|
||||
is recommended to enforce some sort of authentication using a reverse proxy in
|
||||
front of the Spamcheck service that can be used along with an API key. One
|
||||
example would be to use `JWT` authentication for this and specifying a bearer
|
||||
token as the API key.
|
||||
[Native authentication for Spamcheck is in the works](https://gitlab.com/gitlab-com/gl-security/engineering-and-research/automation-team/spam/spamcheck/-/issues/171).
|
||||
|
||||
## Running Spamcheck over TLS
|
||||
|
||||
Spamcheck service on its own cannot communicate directly over TLS with GitLab.
|
||||
However, Spamcheck can be deployed behind a reverse proxy which performs TLS
|
||||
termination. In such a scenario, GitLab can be made to communicate with
|
||||
Spamcheck over TLS by specifying `tls://` scheme for the external Spamcheck URL
|
||||
instead of `grpc://` in the Admin Area settings.
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
type: reference
|
||||
---
|
||||
|
||||
# Sign-up restrictions **(FREE SELF)**
|
||||
|
||||
You can enforce the following restrictions on sign ups:
|
||||
|
||||
- Disable new sign ups.
|
||||
- Require administrator approval for new sign ups.
|
||||
- Require user email confirmation.
|
||||
- Allow or deny sign ups using specific email domains.
|
||||
|
||||
## Disable new sign ups
|
||||
|
||||
By default, any user visiting your GitLab domain can sign up for an account. For customers running
|
||||
public-facing GitLab instances, we **highly** recommend that you consider disabling new sign ups if
|
||||
you do not expect public users to sign up for an account.
|
||||
|
||||
To disable sign ups:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Sign-up restrictions**.
|
||||
1. Clear the **Sign-up enabled** checkbox, then select **Save changes**.
|
||||
|
||||
## Require administrator approval for new sign ups
|
||||
|
||||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4491) in GitLab 13.5.
|
||||
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/267568) in GitLab 13.6.
|
||||
|
||||
When this setting is enabled, any user visiting your GitLab domain and signing up for a new account using the registration form
|
||||
must be explicitly [approved](../../user/admin_area/moderate_users.md#approve-or-reject-a-user-sign-up) by an
|
||||
administrator before they can start using their account. In GitLab 13.6 and later, this setting is
|
||||
enabled by default for new GitLab instances. It is only applicable if sign ups are enabled.
|
||||
|
||||
To require administrator approval for new sign ups:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Sign-up restrictions**.
|
||||
1. Select the **Require admin approval for new sign-ups** checkbox, then select **Save changes**.
|
||||
|
||||
In [GitLab 13.7 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/273258), if an administrator disables this setting, the users in pending approval state are
|
||||
automatically approved in a background job.
|
||||
|
||||
NOTE:
|
||||
This setting doesn't apply to LDAP or OmniAuth users. To enforce approvals for new users
|
||||
signing up using OmniAuth or LDAP, set `block_auto_created_users` to `true` in the
|
||||
[OmniAuth configuration](../../integration/omniauth.md#configure-common-settings) or
|
||||
[LDAP configuration](../auth/ldap/index.md#basic-configuration-settings).
|
||||
A [user cap](#user-cap) can also be used to enforce approvals for new users.
|
||||
|
||||
## Confirm user email
|
||||
|
||||
> - Soft email confirmation [introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/47003) in GitLab 12.2 [with a flag](../../operations/feature_flags.md) named `soft_email_confirmation`.
|
||||
> - Soft email confirmation [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107302/diffs) from a feature flag to an application setting in GitLab 15.9.
|
||||
|
||||
You can send confirmation emails during sign up and require that users confirm
|
||||
their email address before they are allowed to sign in.
|
||||
|
||||
For example, to enforce confirmation of the email address used for new sign ups:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Sign-up restrictions**.
|
||||
1. Under **Email confirmation settings**, select **Hard**.
|
||||
|
||||
The following settings are available:
|
||||
|
||||
- **Hard** - Send a confirmation email during sign up. New users must confirm their email address before they can log in.
|
||||
- **Soft** - Send a confirmation email during sign up. New users can log in immediately, but must confirm their email in three days. Unconfirmed accounts are deleted.
|
||||
- **Off** - New users can sign up without confirming their email address.
|
||||
|
||||
## User cap
|
||||
|
||||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4315) in GitLab 13.7.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/292600) in GitLab 13.9.
|
||||
|
||||
When the number of billable users reaches the user cap, any user who is added or requests access must be
|
||||
[approved](../../user/admin_area/moderate_users.md#approve-or-reject-a-user-sign-up) by an administrator before they can start using
|
||||
their account.
|
||||
|
||||
If an administrator [increases](#set-the-user-cap-number) or [removes](#remove-the-user-cap) the
|
||||
user cap, the users in pending approval state are automatically approved in a background job.
|
||||
|
||||
NOTE:
|
||||
The amount of billable users [is updated once a day](../../subscriptions/self_managed/index.md#billable-users).
|
||||
This means the user cap might apply only retrospectively after the cap has already been exceeded.
|
||||
To ensure the cap is enabled immediately, set it to a low value below the current number of
|
||||
billable users, for example: `1`.
|
||||
|
||||
On instances that use LDAP or OmniAuth, enabling and disabling
|
||||
[administrator approval for new sign ups](#require-administrator-approval-for-new-sign-ups)
|
||||
involves changing the Rails configuration, and may require downtime.
|
||||
User cap can be used instead. As noted above, set the cap to value that ensures it is enforced immediately.
|
||||
|
||||
### Set the user cap number
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Sign-up restrictions**.
|
||||
1. Enter a number in **User cap**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
New user sign ups are subject to the user cap restriction.
|
||||
|
||||
## Remove the user cap
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Sign-up restrictions**.
|
||||
1. Remove the number from **User cap**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
New users sign ups are not subject to the user cap restriction. Users in pending approval state are
|
||||
automatically approved in a background job.
|
||||
|
||||
## Minimum password length limit
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20661) in GitLab 12.6
|
||||
|
||||
You can [change](../../security/password_length_limits.md#modify-minimum-password-length)
|
||||
the minimum number of characters a user must have in their password using the GitLab UI.
|
||||
|
||||
### Password complexity requirements **(PREMIUM SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/354965) in GitLab 15.2.
|
||||
|
||||
By default, the only requirement for user passwords is [minimum password length](#minimum-password-length-limit).
|
||||
You can add additional complexity requirements. Changes to password complexity requirements apply to new passwords:
|
||||
|
||||
- For new users that sign up.
|
||||
- For existing users that reset their password.
|
||||
|
||||
Existing passwords are unaffected. To change password complexity requirements:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Sign-up restrictions**.
|
||||
1. Under **Minimum password length (number of characters)**, select additional password complexity requirements. You can require numbers, uppercase letters, lowercase letters,
|
||||
and symbols.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Allow or deny sign ups using specific email domains
|
||||
|
||||
You can specify an inclusive or exclusive list of email domains which can be used for user sign up.
|
||||
|
||||
These restrictions are only applied during sign up from an external user. An administrator can add a
|
||||
user through the administrator panel with a disallowed domain. The users can also change their
|
||||
email addresses to disallowed domains after sign up.
|
||||
|
||||
### Allowlist email domains
|
||||
|
||||
You can restrict users only to sign up using email addresses matching the given
|
||||
domains list.
|
||||
|
||||
### Denylist email domains
|
||||
|
||||
You can block users from signing up when using an email addresses of specific domains. This can
|
||||
reduce the risk of malicious users creating spam accounts with disposable email addresses.
|
||||
|
||||
### Create email domain allowlist or denylist
|
||||
|
||||
To create an email domain allowlist or denylist:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Sign-up restrictions**.
|
||||
1. For the allowlist, you must enter the list manually. For the denylist, you can enter the list
|
||||
manually or upload a `.txt` file that contains list entries.
|
||||
|
||||
Both the allowlist and denylist accept wildcards. For example, you can use
|
||||
`*.company.com` to accept every `company.com` subdomain, or `*.io` to block all
|
||||
domains ending in `.io`. Domains must be separated by a whitespace,
|
||||
semicolon, comma, or a new line.
|
||||
|
||||

|
||||
|
||||
## Set up LDAP user filter
|
||||
|
||||
You can limit GitLab access to a subset of the LDAP users on your LDAP server.
|
||||
|
||||
See the [documentation on setting up an LDAP user filter](../auth/ldap/index.md#set-up-ldap-user-filter) for more information.
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
|
||||
one might have when setting this up, or when something is changed, or on upgrading, it's
|
||||
important to describe those, too. Think of things that may go wrong and include them here.
|
||||
This is important to minimize requests for support, and to avoid doc comments with
|
||||
questions that you know someone might ask.
|
||||
|
||||
Each scenario can be a third-level heading, for example `### Getting error message X`.
|
||||
If you have none to add when creating a doc, leave this section in place
|
||||
but commented out to help encourage others to add to it in the future. -->
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
type: reference
|
||||
---
|
||||
|
||||
# Terms of Service and Privacy Policy **(FREE SELF)**
|
||||
|
||||
An administrator can enforce acceptance of a terms of service and privacy policy.
|
||||
When this option is enabled, new and existing users must accept the terms.
|
||||
|
||||
When enabled, you can view the Terms of Service at the `-/users/terms` page on the instance,
|
||||
for example `https://gitlab.example.com/-/users/terms`.
|
||||
|
||||
## Enforce a Terms of Service and Privacy Policy
|
||||
|
||||
To enforce acceptance of a Terms of Service and Privacy Policy:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand the **Terms of Service and Privacy Policy** section.
|
||||
1. Check the **All users must accept the Terms of Service and Privacy Policy to access GitLab** checkbox.
|
||||
1. Input the text of the **Terms of Service and Privacy Policy**. You can use [Markdown](../../user/markdown.md)
|
||||
in this text box.
|
||||
1. Select **Save changes**.
|
||||
|
||||
For each update to the terms, a new version is stored. When a user accepts or declines the terms,
|
||||
GitLab records which version they accepted or declined.
|
||||
|
||||
Existing users must accept the terms on their next GitLab interaction.
|
||||
If an authenticated user declines the terms, they are signed out.
|
||||
|
||||
When enabled, it adds a mandatory checkbox to the sign up page for new users:
|
||||
|
||||

|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
|
||||
one might have when setting this up, or when something is changed, or on upgrading, it's
|
||||
important to describe those, too. Think of things that may go wrong and include them here.
|
||||
This is important to minimize requests for support, and to avoid doc comments with
|
||||
questions that you know someone might ask.
|
||||
|
||||
Each scenario can be a third-level heading, for example `### Getting error message X`.
|
||||
If you have none to add when creating a doc, leave this section in place
|
||||
but commented out to help encourage others to add to it in the future. -->
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
stage: Deploy
|
||||
group: Environments
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
type: reference
|
||||
---
|
||||
|
||||
# Terraform limits **(FREE SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/352951) in GitLab 15.7.
|
||||
|
||||
You can limit the total storage of [Terraform state files](../terraform_state.md).
|
||||
The limit applies to each individual
|
||||
state file version, and is checked whenever a new version is created.
|
||||
|
||||
To add a storage limit:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > Preferences**.
|
||||
1. Expand **Terraform limits**.
|
||||
1. Adjust the size limit.
|
||||
|
||||
## Available settings
|
||||
|
||||
| Setting | Default | Description |
|
||||
|------------------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Terraform state size limit (bytes) | 0 | Terraform state files that exceed this size are not saved, and associated Terraform operations are rejected. Set to 0 to allow files of unlimited size. |
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
stage: Data Stores
|
||||
group: Tenant Scale
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
type: reference
|
||||
---
|
||||
|
||||
# Customer experience improvement and third-party offers **(FREE SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/20379) in GitLab 11.1.
|
||||
|
||||
Within GitLab, we inform users of available third-party offers they might find valuable in order
|
||||
to enhance the development of their projects. An example is the Google Cloud Platform free credit
|
||||
for using [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/).
|
||||
|
||||
Furthermore, we use content to improve customer experience. An example are the personalization
|
||||
questions when creating a group.
|
||||
|
||||
To toggle the display of customer experience improvement content and third-party offers:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Customer experience improvement and third-party offers**.
|
||||
1. Select **Do not display content for customer experience improvement and offers from third parties**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
|
||||
one might have when setting this up, or when something is changed, or on upgrading, it's
|
||||
important to describe those, too. Think of things that may go wrong and include them here.
|
||||
This is important to minimize requests for support, and to avoid doc comments with
|
||||
questions that you know someone might ask.
|
||||
|
||||
Each scenario can be a third-level heading, for example `### Getting error message X`.
|
||||
If you have none to add when creating a doc, leave this section in place
|
||||
but commented out to help encourage others to add to it in the future. -->
|
||||
|
|
@ -114,11 +114,11 @@ Checks if CI/CD YAML configuration is valid. This endpoint validates basic CI/CD
|
|||
configuration syntax. It doesn't have any namespace-specific context.
|
||||
|
||||
Access to this endpoint does not require authentication when the instance
|
||||
[allows new sign ups](../user/admin_area/settings/sign_up_restrictions.md#disable-new-sign-ups)
|
||||
[allows new sign ups](../administration/settings/sign_up_restrictions.md#disable-new-sign-ups)
|
||||
and:
|
||||
|
||||
- Does not have an [allowlist or denylist](../user/admin_area/settings/sign_up_restrictions.md#allow-or-deny-sign-ups-using-specific-email-domains).
|
||||
- Does not [require administrator approval for new sign ups](../user/admin_area/settings/sign_up_restrictions.md#require-administrator-approval-for-new-sign-ups).
|
||||
- Does not have an [allowlist or denylist](../administration/settings/sign_up_restrictions.md#allow-or-deny-sign-ups-using-specific-email-domains).
|
||||
- Does not [require administrator approval for new sign ups](../administration/settings/sign_up_restrictions.md#require-administrator-approval-for-new-sign-ups).
|
||||
- Does not have additional [sign up restrictions](../user/admin_area/settings/sign_up_restrictions.md).
|
||||
|
||||
Otherwise, authentication is required.
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ GitLab may conduct unscheduled maintenance to address high-severity issues affec
|
|||
|
||||
### Application
|
||||
|
||||
GitLab Dedicated comes with the self-managed [Ultimate feature set](https://about.gitlab.com/pricing/feature-comparison/) with the exception of the unsupported features [listed below](#features-that-are-not-available).
|
||||
GitLab Dedicated comes with the self-managed [Ultimate feature set](https://about.gitlab.com/pricing/feature-comparison/) with the exception of the [unsupported features](#features-that-are-not-available) listed below.
|
||||
|
||||
#### GitLab Runners
|
||||
|
||||
|
|
|
|||
|
|
@ -114,10 +114,10 @@ Managing the number of users against the number of subscription seats can be a c
|
|||
|
||||
GitLab has several features which can help you manage the number of users:
|
||||
|
||||
- Enable the [**Require administrator approval for new sign ups**](../../user/admin_area/settings/sign_up_restrictions.md#require-administrator-approval-for-new-sign-ups)
|
||||
- Enable the [**Require administrator approval for new sign ups**](../../administration/settings/sign_up_restrictions.md#require-administrator-approval-for-new-sign-ups)
|
||||
option.
|
||||
- Enable `block_auto_created_users` for new sign-ups via [LDAP](../../administration/auth/ldap/index.md#basic-configuration-settings) or [OmniAuth](../../integration/omniauth.md#configure-common-settings).
|
||||
- Enable the [User cap](../../user/admin_area/settings/sign_up_restrictions.md#user-cap)
|
||||
- Enable the [User cap](../../administration/settings/sign_up_restrictions.md#user-cap)
|
||||
option. **Available in GitLab 13.7 and later**.
|
||||
- [Disable new sign-ups](../../user/admin_area/settings/sign_up_restrictions.md), and instead manage new
|
||||
users manually.
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ users.
|
|||
A user in _pending approval_ state requires action by an administrator. A user sign up can be in a
|
||||
pending approval state because an administrator has enabled any of the following options:
|
||||
|
||||
- [Require administrator approval for new sign-ups](settings/sign_up_restrictions.md#require-administrator-approval-for-new-sign-ups) setting.
|
||||
- [User cap](settings/sign_up_restrictions.md#user-cap).
|
||||
- [Require administrator approval for new sign-ups](../../administration/settings/sign_up_restrictions.md#require-administrator-approval-for-new-sign-ups) setting.
|
||||
- [User cap](../../administration/settings/sign_up_restrictions.md#user-cap).
|
||||
- [Block auto-created users (OmniAuth)](../../integration/omniauth.md#configure-common-settings)
|
||||
- [Block auto-created users (LDAP)](../../administration/auth/ldap/index.md#basic-configuration-settings)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +1,11 @@
|
|||
---
|
||||
stage: Anti-Abuse
|
||||
group: Anti-Abuse
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
redirect_to: '../../../administration/reporting/ip_addr_restrictions.md'
|
||||
remove_date: '2023-10-13'
|
||||
---
|
||||
|
||||
# IP address restrictions **(FREE SELF)**
|
||||
This document was moved to [another location](../../../administration/reporting/ip_addr_restrictions.md).
|
||||
|
||||
IP address restrictions help prevent malicious users hiding their activities behind multiple IP addresses.
|
||||
|
||||
GitLab maintains a list of the unique IP addresses used by a user to make requests over a specified period. When the
|
||||
specified limit is reached, any requests made by the user from a new IP address are rejected with a `403 Forbidden` error.
|
||||
|
||||
IP addresses are cleared from the list when no further requests have been made by the user from the IP address in the specified time period.
|
||||
|
||||
NOTE:
|
||||
When a runner runs a CI/CD job as a particular user, the runner IP address is also stored against the user's list of
|
||||
unique IP addresses. Therefore, the IP addresses per user limit should take into account the number of configured active runners.
|
||||
|
||||
## Configure IP address restrictions
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > Reporting**.
|
||||
1. Expand **Spam and Anti-bot Protection**.
|
||||
1. Update the IP address restrictions settings:
|
||||
1. Select the **Limit sign in from multiple IP addresses** checkbox to enable IP address restrictions.
|
||||
1. Enter a number in the **IP addresses per user** field, greater than or equal to `1`. This number specifies the
|
||||
maximum number of unique IP addresses a user can access GitLab from in the specified time period before requests
|
||||
from a new IP address are rejected.
|
||||
1. Enter a number in the **IP address expiration time** field, greater than or equal to `0`. This number specifies the
|
||||
time in seconds an IP address counts towards the limit for a user, taken from the time the last request was made.
|
||||
1. Select **Save changes**.
|
||||
<!-- This redirect file can be deleted after <2023-10-13>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
|||
|
|
@ -1,69 +1,11 @@
|
|||
---
|
||||
stage: Systems
|
||||
group: Distribution
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
redirect_to: '../../../administration/reporting/spamcheck.md'
|
||||
remove_date: '2023-10-13'
|
||||
---
|
||||
|
||||
# Spamcheck anti-spam service **(FREE SELF)**
|
||||
This document was moved to [another location](../../../administration/reporting/spamcheck.md).
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6259) in GitLab 14.8.
|
||||
|
||||
WARNING:
|
||||
Spamcheck is available to all tiers, but only on instances using GitLab Enterprise Edition (EE). For [licensing reasons](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6259#note_726605397), it is not included in the GitLab Community Edition (CE) package. You can [migrate from CE to EE](../../../update/package/convert_to_ee.md).
|
||||
|
||||
[Spamcheck](https://gitlab.com/gitlab-org/spamcheck) is an anti-spam engine
|
||||
developed by GitLab originally to combat rising amount of spam in GitLab.com,
|
||||
and later made public to be used in self-managed GitLab instances.
|
||||
|
||||
## Enable Spamcheck
|
||||
|
||||
Spamcheck is only available for package-based installations:
|
||||
|
||||
1. Edit `/etc/gitlab/gitlab.rb` and enable Spamcheck:
|
||||
|
||||
```ruby
|
||||
spamcheck['enable'] = true
|
||||
```
|
||||
|
||||
1. Reconfigure GitLab:
|
||||
|
||||
```shell
|
||||
sudo gitlab-ctl reconfigure
|
||||
```
|
||||
|
||||
1. Verify that the new services `spamcheck` and `spam-classifier` are
|
||||
up and running:
|
||||
|
||||
```shell
|
||||
sudo gitlab-ctl status
|
||||
```
|
||||
|
||||
## Configure GitLab to use Spamcheck
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > Reporting**.
|
||||
1. Expand **Spam and Anti-bot Protection**.
|
||||
1. Update the Spam Check settings:
|
||||
1. Check the "Enable Spam Check via external API endpoint" checkbox.
|
||||
1. For **URL of the external Spam Check endpoint** use `grpc://localhost:8001`.
|
||||
1. Leave **Spam Check API key** blank.
|
||||
1. Select **Save changes**.
|
||||
|
||||
NOTE:
|
||||
In single-node instances, Spamcheck runs over `localhost`, and hence is running
|
||||
in an unauthenticated mode. If on multi-node instances where GitLab runs on one
|
||||
server and Spamcheck runs on another server listening over a public endpoint, it
|
||||
is recommended to enforce some sort of authentication using a reverse proxy in
|
||||
front of the Spamcheck service that can be used along with an API key. One
|
||||
example would be to use `JWT` authentication for this and specifying a bearer
|
||||
token as the API key.
|
||||
[Native authentication for Spamcheck is in the works](https://gitlab.com/gitlab-com/gl-security/engineering-and-research/automation-team/spam/spamcheck/-/issues/171).
|
||||
|
||||
## Running Spamcheck over TLS
|
||||
|
||||
Spamcheck service on its own cannot communicate directly over TLS with GitLab.
|
||||
However, Spamcheck can be deployed behind a reverse proxy which performs TLS
|
||||
termination. In such a scenario, GitLab can be made to communicate with
|
||||
Spamcheck over TLS by specifying `tls://` scheme for the external Spamcheck URL
|
||||
instead of `grpc://` in the Admin Area settings.
|
||||
<!-- This redirect file can be deleted after <2023-10-13>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
|||
|
|
@ -1,206 +1,11 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
type: reference
|
||||
redirect_to: '../../../administration/settings/sign_up_restrictions.md'
|
||||
remove_date: '2023-10-13'
|
||||
---
|
||||
|
||||
# Sign-up restrictions **(FREE SELF)**
|
||||
This document was moved to [another location](../../../administration/settings/sign_up_restrictions.md).
|
||||
|
||||
You can enforce the following restrictions on sign ups:
|
||||
|
||||
- Disable new sign ups.
|
||||
- Require administrator approval for new sign ups.
|
||||
- Require user email confirmation.
|
||||
- Allow or deny sign ups using specific email domains.
|
||||
|
||||
## Disable new sign ups
|
||||
|
||||
By default, any user visiting your GitLab domain can sign up for an account. For customers running
|
||||
public-facing GitLab instances, we **highly** recommend that you consider disabling new sign ups if
|
||||
you do not expect public users to sign up for an account.
|
||||
|
||||
To disable sign ups:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Sign-up restrictions**.
|
||||
1. Clear the **Sign-up enabled** checkbox, then select **Save changes**.
|
||||
|
||||
## Require administrator approval for new sign ups
|
||||
|
||||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4491) in GitLab 13.5.
|
||||
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/267568) in GitLab 13.6.
|
||||
|
||||
When this setting is enabled, any user visiting your GitLab domain and signing up for a new account using the registration form
|
||||
must be explicitly [approved](../moderate_users.md#approve-or-reject-a-user-sign-up) by an
|
||||
administrator before they can start using their account. In GitLab 13.6 and later, this setting is
|
||||
enabled by default for new GitLab instances. It is only applicable if sign ups are enabled.
|
||||
|
||||
To require administrator approval for new sign ups:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Sign-up restrictions**.
|
||||
1. Select the **Require admin approval for new sign-ups** checkbox, then select **Save changes**.
|
||||
|
||||
In [GitLab 13.7 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/273258), if an administrator disables this setting, the users in pending approval state are
|
||||
automatically approved in a background job.
|
||||
|
||||
NOTE:
|
||||
This setting doesn't apply to LDAP or OmniAuth users. To enforce approvals for new users
|
||||
signing up using OmniAuth or LDAP, set `block_auto_created_users` to `true` in the
|
||||
[OmniAuth configuration](../../../integration/omniauth.md#configure-common-settings) or
|
||||
[LDAP configuration](../../../administration/auth/ldap/index.md#basic-configuration-settings).
|
||||
A [user cap](#user-cap) can also be used to enforce approvals for new users.
|
||||
|
||||
## Confirm user email
|
||||
|
||||
> - Soft email confirmation [introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/47003) in GitLab 12.2 [with a flag](../../../operations/feature_flags.md) named `soft_email_confirmation`.
|
||||
> - Soft email confirmation [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107302/diffs) from a feature flag to an application setting in GitLab 15.9.
|
||||
|
||||
You can send confirmation emails during sign up and require that users confirm
|
||||
their email address before they are allowed to sign in.
|
||||
|
||||
For example, to enforce confirmation of the email address used for new sign ups:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Sign-up restrictions**.
|
||||
1. Under **Email confirmation settings**, select **Hard**.
|
||||
|
||||
The following settings are available:
|
||||
|
||||
- **Hard** - Send a confirmation email during sign up. New users must confirm their email address before they can log in.
|
||||
- **Soft** - Send a confirmation email during sign up. New users can log in immediately, but must confirm their email in three days. Unconfirmed accounts are deleted.
|
||||
- **Off** - New users can sign up without confirming their email address.
|
||||
|
||||
## User cap
|
||||
|
||||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4315) in GitLab 13.7.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/292600) in GitLab 13.9.
|
||||
|
||||
When the number of billable users reaches the user cap, any user who is added or requests access must be
|
||||
[approved](../moderate_users.md#approve-or-reject-a-user-sign-up) by an administrator before they can start using
|
||||
their account.
|
||||
|
||||
If an administrator [increases](#set-the-user-cap-number) or [removes](#remove-the-user-cap) the
|
||||
user cap, the users in pending approval state are automatically approved in a background job.
|
||||
|
||||
NOTE:
|
||||
The amount of billable users [is updated once a day](../../../subscriptions/self_managed/index.md#billable-users).
|
||||
This means the user cap might apply only retrospectively after the cap has already been exceeded.
|
||||
To ensure the cap is enabled immediately, set it to a low value below the current number of
|
||||
billable users, for example: `1`.
|
||||
|
||||
On instances that use LDAP or OmniAuth, enabling and disabling
|
||||
[administrator approval for new sign ups](#require-administrator-approval-for-new-sign-ups)
|
||||
involves changing the Rails configuration, and may require downtime.
|
||||
User cap can be used instead. As noted above, set the cap to value that ensures it is enforced immediately.
|
||||
|
||||
### Set the user cap number
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Sign-up restrictions**.
|
||||
1. Enter a number in **User cap**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
New user sign ups are subject to the user cap restriction.
|
||||
|
||||
## Remove the user cap
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Sign-up restrictions**.
|
||||
1. Remove the number from **User cap**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
New users sign ups are not subject to the user cap restriction. Users in pending approval state are
|
||||
automatically approved in a background job.
|
||||
|
||||
## Minimum password length limit
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20661) in GitLab 12.6
|
||||
|
||||
You can [change](../../../security/password_length_limits.md#modify-minimum-password-length)
|
||||
the minimum number of characters a user must have in their password using the GitLab UI.
|
||||
|
||||
### Password complexity requirements **(PREMIUM SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/354965) in GitLab 15.2.
|
||||
|
||||
By default, the only requirement for user passwords is [minimum password length](#minimum-password-length-limit).
|
||||
You can add additional complexity requirements. Changes to password complexity requirements apply to new passwords:
|
||||
|
||||
- For new users that sign up.
|
||||
- For existing users that reset their password.
|
||||
|
||||
Existing passwords are unaffected. To change password complexity requirements:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Sign-up restrictions**.
|
||||
1. Under **Minimum password length (number of characters)**, select additional password complexity requirements. You can require numbers, uppercase letters, lowercase letters,
|
||||
and symbols.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Allow or deny sign ups using specific email domains
|
||||
|
||||
You can specify an inclusive or exclusive list of email domains which can be used for user sign up.
|
||||
|
||||
These restrictions are only applied during sign up from an external user. An administrator can add a
|
||||
user through the administrator panel with a disallowed domain. The users can also change their
|
||||
email addresses to disallowed domains after sign up.
|
||||
|
||||
### Allowlist email domains
|
||||
|
||||
You can restrict users only to sign up using email addresses matching the given
|
||||
domains list.
|
||||
|
||||
### Denylist email domains
|
||||
|
||||
You can block users from signing up when using an email addresses of specific domains. This can
|
||||
reduce the risk of malicious users creating spam accounts with disposable email addresses.
|
||||
|
||||
### Create email domain allowlist or denylist
|
||||
|
||||
To create an email domain allowlist or denylist:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Sign-up restrictions**.
|
||||
1. For the allowlist, you must enter the list manually. For the denylist, you can enter the list
|
||||
manually or upload a `.txt` file that contains list entries.
|
||||
|
||||
Both the allowlist and denylist accept wildcards. For example, you can use
|
||||
`*.company.com` to accept every `company.com` subdomain, or `*.io` to block all
|
||||
domains ending in `.io`. Domains must be separated by a whitespace,
|
||||
semicolon, comma, or a new line.
|
||||
|
||||

|
||||
|
||||
## Set up LDAP user filter
|
||||
|
||||
You can limit GitLab access to a subset of the LDAP users on your LDAP server.
|
||||
|
||||
See the [documentation on setting up an LDAP user filter](../../../administration/auth/ldap/index.md#set-up-ldap-user-filter) for more information.
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
|
||||
one might have when setting this up, or when something is changed, or on upgrading, it's
|
||||
important to describe those, too. Think of things that may go wrong and include them here.
|
||||
This is important to minimize requests for support, and to avoid doc comments with
|
||||
questions that you know someone might ask.
|
||||
|
||||
Each scenario can be a third-level heading, for example `### Getting error message X`.
|
||||
If you have none to add when creating a doc, leave this section in place
|
||||
but commented out to help encourage others to add to it in the future. -->
|
||||
<!-- This redirect file can be deleted after <2023-10-13>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
|||
|
|
@ -1,49 +1,11 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
type: reference
|
||||
redirect_to: '../../../administration/settings/terms.md'
|
||||
remove_date: '2023-10-13'
|
||||
---
|
||||
|
||||
# Terms of Service and Privacy Policy **(FREE SELF)**
|
||||
This document was moved to [another location](../../../administration/settings/terms.md).
|
||||
|
||||
An administrator can enforce acceptance of a terms of service and privacy policy.
|
||||
When this option is enabled, new and existing users must accept the terms.
|
||||
|
||||
When enabled, you can view the Terms of Service at the `-/users/terms` page on the instance,
|
||||
for example `https://gitlab.example.com/-/users/terms`.
|
||||
|
||||
## Enforce a Terms of Service and Privacy Policy
|
||||
|
||||
To enforce acceptance of a Terms of Service and Privacy Policy:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand the **Terms of Service and Privacy Policy** section.
|
||||
1. Check the **All users must accept the Terms of Service and Privacy Policy to access GitLab** checkbox.
|
||||
1. Input the text of the **Terms of Service and Privacy Policy**. You can use [Markdown](../../markdown.md)
|
||||
in this text box.
|
||||
1. Select **Save changes**.
|
||||
|
||||
For each update to the terms, a new version is stored. When a user accepts or declines the terms,
|
||||
GitLab records which version they accepted or declined.
|
||||
|
||||
Existing users must accept the terms on their next GitLab interaction.
|
||||
If an authenticated user declines the terms, they are signed out.
|
||||
|
||||
When enabled, it adds a mandatory checkbox to the sign up page for new users:
|
||||
|
||||

|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
|
||||
one might have when setting this up, or when something is changed, or on upgrading, it's
|
||||
important to describe those, too. Think of things that may go wrong and include them here.
|
||||
This is important to minimize requests for support, and to avoid doc comments with
|
||||
questions that you know someone might ask.
|
||||
|
||||
Each scenario can be a third-level heading, for example `### Getting error message X`.
|
||||
If you have none to add when creating a doc, leave this section in place
|
||||
but commented out to help encourage others to add to it in the future. -->
|
||||
<!-- This redirect file can be deleted after <2023-10-13>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
|||
|
|
@ -1,28 +1,11 @@
|
|||
---
|
||||
stage: Deploy
|
||||
group: Environments
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
type: reference
|
||||
redirect_to: '../../../administration/settings/terraform_limits.md'
|
||||
remove_date: '2023-10-13'
|
||||
---
|
||||
|
||||
# Terraform limits **(FREE SELF)**
|
||||
This document was moved to [another location](../../../administration/settings/terraform_limits.md).
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/352951) in GitLab 15.7.
|
||||
|
||||
You can limit the total storage of [Terraform state files](../../../administration/terraform_state.md).
|
||||
The limit applies to each individual
|
||||
state file version, and is checked whenever a new version is created.
|
||||
|
||||
To add a storage limit:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > Preferences**.
|
||||
1. Expand **Terraform limits**.
|
||||
1. Adjust the size limit.
|
||||
|
||||
## Available settings
|
||||
|
||||
| Setting | Default | Description |
|
||||
|------------------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Terraform state size limit (bytes) | 0 | Terraform state files that exceed this size are not saved, and associated Terraform operations are rejected. Set to 0 to allow files of unlimited size. |
|
||||
<!-- This redirect file can be deleted after <2023-10-13>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
|||
|
|
@ -1,38 +1,11 @@
|
|||
---
|
||||
stage: Data Stores
|
||||
group: Tenant Scale
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
type: reference
|
||||
redirect_to: '../../../administration/settings/third_party_offers.md'
|
||||
remove_date: '2023-10-13'
|
||||
---
|
||||
|
||||
# Customer experience improvement and third-party offers **(FREE SELF)**
|
||||
This document was moved to [another location](../../../administration/settings/third_party_offers.md).
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/20379) in GitLab 11.1.
|
||||
|
||||
Within GitLab, we inform users of available third-party offers they might find valuable in order
|
||||
to enhance the development of their projects. An example is the Google Cloud Platform free credit
|
||||
for using [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/).
|
||||
|
||||
Furthermore, we use content to improve customer experience. An example are the personalization
|
||||
questions when creating a group.
|
||||
|
||||
To toggle the display of customer experience improvement content and third-party offers:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Customer experience improvement and third-party offers**.
|
||||
1. Select **Do not display content for customer experience improvement and offers from third parties**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
|
||||
one might have when setting this up, or when something is changed, or on upgrading, it's
|
||||
important to describe those, too. Think of things that may go wrong and include them here.
|
||||
This is important to minimize requests for support, and to avoid doc comments with
|
||||
questions that you know someone might ask.
|
||||
|
||||
Each scenario can be a third-level heading, for example `### Getting error message X`.
|
||||
If you have none to add when creating a doc, leave this section in place
|
||||
but commented out to help encourage others to add to it in the future. -->
|
||||
<!-- This redirect file can be deleted after <2023-10-13>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ tier. Users can continue to access the features in a paid tier without sharing u
|
|||
- [Maintenance mode](../../../administration/maintenance_mode/index.md).
|
||||
- [Configurable issue boards](../../project/issue_board.md#configurable-issue-boards).
|
||||
- [Coverage-guided fuzz testing](../../application_security/coverage_fuzzing/index.md).
|
||||
- [Password complexity requirements](../../../user/admin_area/settings/sign_up_restrictions.md#password-complexity-requirements).
|
||||
- [Password complexity requirements](../../../administration/settings/sign_up_restrictions.md#password-complexity-requirements).
|
||||
|
||||
NOTE:
|
||||
Registration is not yet required for participation, but may be added in a future milestone.
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ You can use group access tokens:
|
|||
- On GitLab SaaS: If you have the Premium or Ultimate license tier. Group access tokens are not available with a [trial license](https://about.gitlab.com/free-trial/).
|
||||
- On self-managed instances: With any license tier. If you have the Free tier:
|
||||
- Review your security and compliance policies around
|
||||
[user self-enrollment](../../admin_area/settings/sign_up_restrictions.md#disable-new-sign-ups).
|
||||
[user self-enrollment](../../../administration/settings/sign_up_restrictions.md#disable-new-sign-ups).
|
||||
- Consider [disabling group access tokens](#enable-or-disable-group-access-token-creation) to
|
||||
lower potential abuse.
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ By default GitLab enforces the following password requirements:
|
|||
Self-managed installations can configure the following additional password requirements:
|
||||
|
||||
- [Password minimum and maximum length limits](../../security/password_length_limits.md).
|
||||
- [Password complexity requirements](../admin_area/settings/sign_up_restrictions.md#password-complexity-requirements).
|
||||
- [Password complexity requirements](../../administration/settings/sign_up_restrictions.md#password-complexity-requirements).
|
||||
|
||||
## Block weak passwords
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,2 @@
|
|||
inherit_from:
|
||||
- ../../.rubocop.yml
|
||||
|
||||
CodeReuse/ActiveRecord:
|
||||
Enabled: false
|
||||
|
||||
Rails/ApplicationRecord:
|
||||
Enabled: false
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 3.0
|
||||
|
||||
Naming/FileName:
|
||||
Exclude:
|
||||
- spec/**/*.rb
|
||||
- lib/gitlab/rspec.rb
|
||||
- lib/gitlab/rspec/all.rb
|
||||
- ../config/rubocop.yml
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ AllCops:
|
|||
# Target the current Ruby version. For example, "3.0" or "3.1".
|
||||
TargetRubyVersion: <%= RUBY_VERSION[/^\d+\.\d+/, 0] %>
|
||||
SuggestExtensions: false
|
||||
NewCops: disable
|
||||
|
||||
# This cop doesn't make sense in the context of gems
|
||||
CodeReuse/ActiveRecord:
|
||||
|
|
@ -60,6 +61,10 @@ Naming/FileName:
|
|||
Exclude:
|
||||
- spec/**/*.rb
|
||||
|
||||
# This cop doesn't make sense in the context of gems
|
||||
RSpec/AvoidConditionalStatements:
|
||||
Enabled: false
|
||||
|
||||
RSpec/ContextWording:
|
||||
Prefixes:
|
||||
- 'when'
|
||||
|
|
@ -80,6 +85,9 @@ RSpec/MissingFeatureCategory:
|
|||
Style/HashSyntax:
|
||||
Enabled: false
|
||||
|
||||
Style/Lambda:
|
||||
EnforcedStyle: literal
|
||||
|
||||
Style/RegexpLiteralMixedPreserve:
|
||||
Enabled: true
|
||||
SupportedStyles:
|
||||
|
|
|
|||
|
|
@ -4,8 +4,5 @@ inherit_from:
|
|||
AllCops:
|
||||
NewCops: enable
|
||||
|
||||
Gitlab/RSpec/AvoidSetup:
|
||||
Enabled: false
|
||||
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 25
|
||||
|
|
|
|||
|
|
@ -353,7 +353,7 @@ RSpec.describe Gitlab::Utils::StrongMemoize, feature_category: :shared do
|
|||
|
||||
it { is_expected.to eq(output) }
|
||||
|
||||
if params[:valid] # rubocop:disable RSpec/AvoidConditionalStatements
|
||||
if params[:valid]
|
||||
it 'is a valid ivar name' do
|
||||
expect { instance_variable_defined?(ivar) }.not_to raise_error
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@ CodeReuse/ActiveRecord:
|
|||
Gitlab/Json:
|
||||
Enabled: false
|
||||
|
||||
# FIXME
|
||||
Gitlab/RSpec/AvoidSetup:
|
||||
Enabled: false
|
||||
|
||||
Naming/FileName:
|
||||
Exclude:
|
||||
- spec/**/*.rb
|
||||
|
|
@ -20,9 +16,6 @@ Naming/FileName:
|
|||
Rails/Pluck:
|
||||
Enabled: false
|
||||
|
||||
RSpec/AvoidConditionalStatements:
|
||||
Enabled: false
|
||||
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 6
|
||||
AllowSubject: true
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ RSpec.describe RspecFlaky::FlakyExample, :aggregate_failures do
|
|||
end
|
||||
|
||||
it 'updates the flaky_reports' do
|
||||
expected_flaky_reports = flaky_example.to_h[:first_flaky_at] ? flaky_example.to_h[:flaky_reports] + 1 : 1 # rubocop:disable RSpec/AvoidConditionalStatements
|
||||
expected_flaky_reports = flaky_example.to_h[:first_flaky_at] ? flaky_example.to_h[:flaky_reports] + 1 : 1
|
||||
|
||||
expect { flaky_example.update!(example_attrs) }.to change { flaky_example.to_h[:flaky_reports] }.by(1)
|
||||
expect(flaky_example.to_h[:flaky_reports]).to eq(expected_flaky_reports)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ module API
|
|||
included do
|
||||
helpers ::API::Helpers::Packages::DependencyProxyHelpers
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid do |e|
|
||||
render_structured_api_error!({ message: e.message, error: e.message }, 400)
|
||||
end
|
||||
|
||||
before do
|
||||
require_packages_enabled!
|
||||
authenticate_non_get!
|
||||
|
|
@ -88,7 +92,7 @@ module API
|
|||
packages = ::Packages::Npm::PackageFinder.new(package_name, project: project)
|
||||
.execute
|
||||
|
||||
not_found! if packages.empty?
|
||||
not_found!('Package') if packages.empty?
|
||||
|
||||
track_package_event(:list_tags, :npm, project: project, namespace: project.namespace)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ module API
|
|||
module Npm
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
include ::API::Helpers::PackagesHelpers
|
||||
extend ::Gitlab::Utils::Override
|
||||
|
||||
NPM_ENDPOINT_REQUIREMENTS = {
|
||||
package_name: API::NO_SLASH_URL_PART_REGEX
|
||||
|
|
@ -105,6 +106,20 @@ module API
|
|||
group
|
||||
end
|
||||
strong_memoize_attr :group
|
||||
|
||||
override :not_found!
|
||||
def not_found!(resource = nil)
|
||||
reason = "#{resource} not found"
|
||||
message = "404 #{reason}".titleize
|
||||
render_structured_api_error!({ message: message, error: reason }, 404)
|
||||
end
|
||||
|
||||
override :bad_request_missing_attribute!
|
||||
def bad_request_missing_attribute!(attribute)
|
||||
reason = "\"#{attribute}\" not given"
|
||||
message = "400 Bad request - #{reason}"
|
||||
render_structured_api_error!({ message: message, error: reason }, 400)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -29,21 +29,13 @@ module API
|
|||
end
|
||||
|
||||
def gitaly_info(project)
|
||||
shard = repo_type.repository_for(project).shard
|
||||
{
|
||||
address: Gitlab::GitalyClient.address(shard),
|
||||
token: Gitlab::GitalyClient.token(shard),
|
||||
features: Feature::Gitaly.server_feature_flags
|
||||
}
|
||||
gitaly_features = Feature::Gitaly.server_feature_flags
|
||||
|
||||
Gitlab::GitalyClient.connection_data(project.repository_storage).merge(features: gitaly_features)
|
||||
end
|
||||
|
||||
def gitaly_repository(project)
|
||||
{
|
||||
storage_name: project.repository_storage,
|
||||
relative_path: project.disk_path + '.git',
|
||||
gl_repository: repo_type.identifier_for_container(project),
|
||||
gl_project_path: repo_type.repository_for(project).full_path
|
||||
}
|
||||
project.repository.gitaly_repository.to_h
|
||||
end
|
||||
|
||||
def check_feature_enabled
|
||||
|
|
|
|||
|
|
@ -6,10 +6,6 @@ module API
|
|||
feature_category :package_registry
|
||||
urgency :low
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid do |e|
|
||||
render_api_error!(e.message, 400)
|
||||
end
|
||||
|
||||
helpers do
|
||||
def endpoint_scope
|
||||
:instance
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ module API
|
|||
urgency :low
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid do |e|
|
||||
render_api_error!(e.message, 400)
|
||||
render_structured_api_error!({ message: e.message, error: e.message }, 400)
|
||||
end
|
||||
|
||||
helpers do
|
||||
|
|
@ -78,7 +78,7 @@ module API
|
|||
.new(project, current_user, params.merge(build: current_authenticated_job)).execute
|
||||
|
||||
if created_package[:status] == :error
|
||||
render_api_error!(created_package[:message], created_package[:http_status])
|
||||
render_structured_api_error!({ message: created_package[:message], error: created_package[:message] }, created_package[:http_status])
|
||||
else
|
||||
enqueue_sync_metadata_cache_worker(project, created_package.name)
|
||||
track_package_event('push_package', :npm, category: 'API::NpmPackages', project: project, namespace: project.namespace)
|
||||
|
|
|
|||
|
|
@ -68,7 +68,9 @@ module Gitlab
|
|||
end
|
||||
|
||||
def gitaly_info(project)
|
||||
gitaly_features = Feature::Gitaly.server_feature_flags
|
||||
connection_data = Gitlab::GitalyClient.connection_data(project.repository_storage)
|
||||
.merge(features: gitaly_features)
|
||||
|
||||
Gitlab::Agent::Entity::GitalyInfo.new(connection_data)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@ module Gitlab
|
|||
@npm_package_name_regex ||= %r{\A(?:@(#{Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX})/)?[-+\.\_a-zA-Z0-9]+\z}o
|
||||
end
|
||||
|
||||
def npm_package_name_regex_message
|
||||
'should be a valid NPM package name: https://github.com/npm/validate-npm-package-name#naming-rules.'
|
||||
end
|
||||
|
||||
def nuget_package_name_regex
|
||||
@nuget_package_name_regex ||= %r{\A[-+\.\_a-zA-Z0-9]+\z}.freeze
|
||||
end
|
||||
|
|
@ -177,6 +181,10 @@ module Gitlab
|
|||
@semver_regex ||= Regexp.new("\\A#{::Gitlab::Regex.unbounded_semver_regex.source}\\z", ::Gitlab::Regex.unbounded_semver_regex.options).freeze
|
||||
end
|
||||
|
||||
def semver_regex_message
|
||||
'should follow SemVer: https://semver.org'
|
||||
end
|
||||
|
||||
# These partial semver regexes are intended for use in composing other
|
||||
# regexes rather than being used alone.
|
||||
def _semver_major_minor_patch_regex
|
||||
|
|
|
|||
|
|
@ -1536,8 +1536,8 @@ msgstr ""
|
|||
msgid "0 B"
|
||||
msgstr ""
|
||||
|
||||
msgid "1 Code quality finding"
|
||||
msgid_plural "%d Code quality findings"
|
||||
msgid "1 Code Quality finding"
|
||||
msgid_plural "%d Code Quality findings"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
|
|
@ -1551,6 +1551,11 @@ msgid_plural "%d Issues"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "1 Security finding"
|
||||
msgid_plural "%d Security findings"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "1 closed issue"
|
||||
msgid_plural "%{issues} closed issues"
|
||||
msgstr[0] ""
|
||||
|
|
@ -5189,6 +5194,9 @@ msgstr ""
|
|||
msgid "An unexpected error occurred while communicating with the Web Terminal."
|
||||
msgstr ""
|
||||
|
||||
msgid "An unexpected error occurred while loading the Sast diff."
|
||||
msgstr ""
|
||||
|
||||
msgid "An unexpected error occurred while loading the code quality diff."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -12639,6 +12647,9 @@ msgstr ""
|
|||
msgid "ContributionAnalytics|Total Contributions"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributionEvent|Accepted merge request %{targetLink} in %{resourceParentLink}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributionEvent|Approved merge request %{targetLink} in %{resourceParentLink}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -30228,6 +30239,9 @@ msgstr ""
|
|||
msgid "New Requirement"
|
||||
msgstr ""
|
||||
|
||||
msgid "New Security findings"
|
||||
msgstr ""
|
||||
|
||||
msgid "New Snippet"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -55513,7 +55527,7 @@ msgstr ""
|
|||
msgid "starts on %{timebox_start_date}"
|
||||
msgstr ""
|
||||
|
||||
msgid "structure is too large"
|
||||
msgid "structure is too large. Maximum size is %{max_size} characters"
|
||||
msgstr ""
|
||||
|
||||
msgid "stuck"
|
||||
|
|
|
|||
|
|
@ -36,9 +36,9 @@ module QA
|
|||
|
||||
if has_css?('button[data-testid="listbox-reset-button"]')
|
||||
find('button[data-testid="listbox-reset-button"]').click
|
||||
elsif dropdown_open?
|
||||
expand_select_list
|
||||
end
|
||||
|
||||
expand_select_list if dropdown_open?
|
||||
end
|
||||
|
||||
def search_item(item_text)
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ module QA
|
|||
RSpec.describe 'Package', :requires_admin, product_group: :package_registry do
|
||||
describe 'Terraform Module Registry',
|
||||
quarantine: {
|
||||
only: { pipeline: :nightly },
|
||||
type: :investigating,
|
||||
issue: 'https://gitlab.com/gitlab-org/quality/quality-engineering/team-tasks/-/issues/1883'
|
||||
only: { job: 'airgapped' },
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/417407',
|
||||
type: :investigating
|
||||
} do
|
||||
include Runtime::Fixtures
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
module QA
|
||||
RSpec.describe 'Package', :object_storage, product_group: :package_registry, quarantine: {
|
||||
issue: 'https://gitlab.com/gitlab-org/quality/quality-engineering/team-tasks/-/issues/1883',
|
||||
type: :investigating
|
||||
only: { job: %w[object_storage relative_url airgapped], condition: -> { QA::Support::FIPS.enabled? } },
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/417584',
|
||||
type: :bug
|
||||
} do
|
||||
describe 'Conan Repository' do
|
||||
include Runtime::Fixtures
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ module QA
|
|||
RSpec.describe 'Package', :object_storage, product_group: :package_registry do
|
||||
describe 'Helm Registry',
|
||||
quarantine: {
|
||||
only: { pipeline: :nightly },
|
||||
type: :investigating,
|
||||
issue: 'https://gitlab.com/gitlab-org/quality/quality-engineering/team-tasks/-/issues/1883'
|
||||
only: { job: %w[relative_url airgapped] },
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/417590',
|
||||
type: :investigating
|
||||
} do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
include Runtime::Fixtures
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ module QA
|
|||
RSpec.describe 'Package', :object_storage, product_group: :package_registry do
|
||||
describe 'Maven group level endpoint',
|
||||
quarantine: {
|
||||
only: { pipeline: :nightly },
|
||||
type: :investigating,
|
||||
issue: 'https://gitlab.com/gitlab-org/quality/quality-engineering/team-tasks/-/issues/1883'
|
||||
only: { job: %w[relative_url airgapped] },
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/417600',
|
||||
type: :investigating
|
||||
} do
|
||||
include Runtime::Fixtures
|
||||
include Support::Helpers::MaskToken
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
module QA
|
||||
RSpec.describe 'Package', :object_storage,
|
||||
quarantine: {
|
||||
only: { pipeline: :nightly },
|
||||
type: :investigating,
|
||||
issue: 'https://gitlab.com/gitlab-org/quality/quality-engineering/team-tasks/-/issues/1883'
|
||||
only: { job: %w[relative_url airgapped] },
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/417600',
|
||||
type: :investigating
|
||||
} do
|
||||
describe 'Maven project level endpoint', product_group: :package_registry do
|
||||
include Runtime::Fixtures
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Package', :object_storage, except: { job: 'relative-url' }, product_group: :package_registry do
|
||||
RSpec.describe 'Package', :object_storage,
|
||||
quarantine: {
|
||||
only: { job: 'relative_url', condition: -> { QA::Support::FIPS.enabled? } },
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/417600',
|
||||
type: :investigating
|
||||
}, product_group: :package_registry do
|
||||
describe 'Maven Repository with Gradle' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
include Runtime::Fixtures
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ module QA
|
|||
RSpec.describe 'Package', :object_storage, product_group: :package_registry do
|
||||
describe 'PyPI Repository',
|
||||
quarantine: {
|
||||
only: { pipeline: :nightly },
|
||||
type: :investigating,
|
||||
issue: 'https://gitlab.com/gitlab-org/quality/quality-engineering/team-tasks/-/issues/1883'
|
||||
only: { job: %w[relative_url airgapped] },
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/417592',
|
||||
type: :investigating
|
||||
} do
|
||||
include Runtime::Fixtures
|
||||
include Support::Helpers::MaskToken
|
||||
|
|
|
|||
|
|
@ -20,19 +20,19 @@ function retrieve_tests_metadata() {
|
|||
}
|
||||
|
||||
function update_tests_metadata() {
|
||||
local rspec_flaky_folder_path="$(dirname "${FLAKY_RSPEC_SUITE_REPORT_PATH}")/"
|
||||
local knapsack_folder_path="$(dirname "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}")/"
|
||||
local rspec_flaky_folder_path="$(dirname "${FLAKY_RSPEC_SUITE_REPORT_PATH:-unknown_folder}")/"
|
||||
local knapsack_folder_path="$(dirname "${KNAPSACK_RSPEC_SUITE_REPORT_PATH:-unknown_folder}")/"
|
||||
|
||||
echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}"
|
||||
echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH:-unknown_file}"
|
||||
|
||||
scripts/merge-reports "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" ${knapsack_folder_path}rspec*.json
|
||||
scripts/merge-reports "${KNAPSACK_RSPEC_SUITE_REPORT_PATH:-unknown_file}" ${knapsack_folder_path:-unknown_folder}rspec*.json
|
||||
|
||||
export FLAKY_RSPEC_GENERATE_REPORT="true"
|
||||
scripts/merge-reports "${FLAKY_RSPEC_SUITE_REPORT_PATH}" ${rspec_flaky_folder_path}all_*.json
|
||||
scripts/merge-reports "${FLAKY_RSPEC_SUITE_REPORT_PATH:-unknown_file}" ${rspec_flaky_folder_path:-unknown_folder}all_*.json
|
||||
|
||||
# Prune flaky tests that weren't flaky in the last 7 days, *after* updating the flaky tests detected
|
||||
# in this pipeline, so that first_flaky_at for tests that are still flaky is maintained.
|
||||
scripts/flaky_examples/prune-old-flaky-examples "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
|
||||
scripts/flaky_examples/prune-old-flaky-examples "${FLAKY_RSPEC_SUITE_REPORT_PATH:-unknown_file}"
|
||||
|
||||
if [[ "$CI_PIPELINE_SOURCE" == "schedule" ]]; then
|
||||
if [[ -n "$RSPEC_PROFILING_PGSSLKEY" ]]; then
|
||||
|
|
@ -70,10 +70,10 @@ function update_tests_mapping() {
|
|||
return 0
|
||||
fi
|
||||
|
||||
scripts/generate-test-mapping "${RSPEC_TESTS_MAPPING_PATH}" crystalball/rspec*.yml
|
||||
scripts/pack-test-mapping "${RSPEC_TESTS_MAPPING_PATH}" "${RSPEC_PACKED_TESTS_MAPPING_PATH}"
|
||||
gzip "${RSPEC_PACKED_TESTS_MAPPING_PATH}"
|
||||
rm -f crystalball/rspec*.yml "${RSPEC_PACKED_TESTS_MAPPING_PATH}"
|
||||
scripts/generate-test-mapping "${RSPEC_TESTS_MAPPING_PATH:-unknown_file}" crystalball/rspec*.yml
|
||||
scripts/pack-test-mapping "${RSPEC_TESTS_MAPPING_PATH:-unknown_file}" "${RSPEC_PACKED_TESTS_MAPPING_PATH:-unknown_file}"
|
||||
gzip "${RSPEC_PACKED_TESTS_MAPPING_PATH:-unknown_file}"
|
||||
rm -f crystalball/rspec*.yml "${RSPEC_PACKED_TESTS_MAPPING_PATH:-unknown_file}"
|
||||
}
|
||||
|
||||
function crystalball_rspec_data_exists() {
|
||||
|
|
@ -451,14 +451,14 @@ function cleanup_individual_job_reports() {
|
|||
local rspec_flaky_folder_path="$(dirname "${FLAKY_RSPEC_SUITE_REPORT_PATH}")/"
|
||||
local knapsack_folder_path="$(dirname "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}")/"
|
||||
|
||||
rm -rf ${knapsack_folder_path}rspec*.json \
|
||||
${rspec_flaky_folder_path}all_*.json \
|
||||
${rspec_flaky_folder_path}new_*.json \
|
||||
${rspec_flaky_folder_path}skipped_flaky_tests_*_report.txt \
|
||||
${rspec_flaky_folder_path}retried_tests_*_report.txt \
|
||||
${RSPEC_LAST_RUN_RESULTS_FILE} \
|
||||
${RSPEC_PROFILING_FOLDER_PATH}/**/*
|
||||
rmdir ${RSPEC_PROFILING_FOLDER_PATH} || true
|
||||
rm -rf ${knapsack_folder_path:-unknown_folder}rspec*.json \
|
||||
${rspec_flaky_folder_path:-unknown_folder}all_*.json \
|
||||
${rspec_flaky_folder_path:-unknown_folder}new_*.json \
|
||||
${rspec_flaky_folder_path:-unknown_folder}skipped_flaky_tests_*_report.txt \
|
||||
${rspec_flaky_folder_path:-unknown_folder}retried_tests_*_report.txt \
|
||||
${RSPEC_LAST_RUN_RESULTS_FILE:-unknown_folder} \
|
||||
${RSPEC_PROFILING_FOLDER_PATH:-unknown_folder}/**/*
|
||||
rmdir ${RSPEC_PROFILING_FOLDER_PAT:-unknown_folder} || true
|
||||
}
|
||||
|
||||
function generate_flaky_tests_reports() {
|
||||
|
|
|
|||
|
|
@ -77,9 +77,8 @@ RSpec.describe 'Merge request > User sees diff', :js, feature_category: :code_re
|
|||
sign_in(author_user)
|
||||
visit diffs_project_merge_request_path(project, merge_request)
|
||||
|
||||
first(".js-diff-more-actions").click
|
||||
|
||||
expect(page).to have_selector(".js-edit-blob")
|
||||
# Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax
|
||||
expect(page).to have_selector(".js-edit-blob", visible: false)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import { SEVERITIES as SEVERITIES_CODE_QUALITY } from '~/ci/reports/codequality_report/constants';
|
||||
import { SEVERITIES as SEVERITIES_SAST } from '~/ci/reports/sast/constants';
|
||||
|
||||
export const failedIssue = {
|
||||
result: 'failure',
|
||||
name: 'Test#sum when a is 1 and b is 2 returns summary',
|
||||
|
|
@ -36,3 +39,54 @@ export const failedReport = {
|
|||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const findingSastInfo = {
|
||||
scale: 'sast',
|
||||
severity: 'info',
|
||||
};
|
||||
|
||||
export const findingSastInfoEnhanced = {
|
||||
scale: 'sast',
|
||||
severity: 'info',
|
||||
class: SEVERITIES_SAST.info.class,
|
||||
name: SEVERITIES_SAST.info.name,
|
||||
};
|
||||
|
||||
export const findingsCodeQualityBlocker = {
|
||||
scale: 'codeQuality',
|
||||
severity: 'blocker',
|
||||
};
|
||||
|
||||
export const findingCodeQualityBlockerEnhanced = {
|
||||
scale: 'codeQuality',
|
||||
severity: 'blocker',
|
||||
class: SEVERITIES_CODE_QUALITY.blocker.class,
|
||||
name: SEVERITIES_CODE_QUALITY.blocker.name,
|
||||
};
|
||||
|
||||
export const findingCodeQualityInfo = {
|
||||
scale: 'codeQuality',
|
||||
severity: 'info',
|
||||
};
|
||||
|
||||
export const findingCodeQualityInfoEnhanced = {
|
||||
scale: 'codeQuality',
|
||||
severity: 'info',
|
||||
class: SEVERITIES_CODE_QUALITY.info.class,
|
||||
name: SEVERITIES_CODE_QUALITY.info.name,
|
||||
};
|
||||
|
||||
export const findingUnknownInfo = {
|
||||
scale: 'codeQuality',
|
||||
severity: 'info',
|
||||
};
|
||||
|
||||
export const findingUnknownInfoEnhanced = {
|
||||
scale: 'codeQuality',
|
||||
severity: 'info',
|
||||
class: SEVERITIES_CODE_QUALITY.info.class,
|
||||
name: SEVERITIES_CODE_QUALITY.info.name,
|
||||
};
|
||||
|
||||
export const findingsArray = [findingSastInfo, findingsCodeQualityBlocker];
|
||||
export const findingsArrayEnhanced = [findingSastInfoEnhanced, findingCodeQualityBlockerEnhanced];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
import { getSeverity } from '~/ci/reports/utils';
|
||||
|
||||
import {
|
||||
findingSastInfo,
|
||||
findingSastInfoEnhanced,
|
||||
findingCodeQualityInfo,
|
||||
findingCodeQualityInfoEnhanced,
|
||||
findingUnknownInfo,
|
||||
findingUnknownInfoEnhanced,
|
||||
findingsArray,
|
||||
findingsArrayEnhanced,
|
||||
} from './mock_data/mock_data';
|
||||
|
||||
describe('getSeverity utility function', () => {
|
||||
it('should enhance finding with sast scale', () => {
|
||||
expect(getSeverity(findingSastInfo)).toEqual(findingSastInfoEnhanced);
|
||||
});
|
||||
|
||||
it('should enhance finding with codequality scale', () => {
|
||||
expect(getSeverity(findingCodeQualityInfo)).toEqual(findingCodeQualityInfoEnhanced);
|
||||
});
|
||||
|
||||
it('should use codeQuality scale when scale is unknown', () => {
|
||||
expect(getSeverity(findingUnknownInfo)).toEqual(findingUnknownInfoEnhanced);
|
||||
});
|
||||
|
||||
it('should correctly enhance an array of findings', () => {
|
||||
expect(getSeverity(findingsArray)).toEqual(findingsArrayEnhanced);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import ContributionEventMerged from '~/contribution_events/components/contribution_event/contribution_event_merged.vue';
|
||||
import ContributionEventBase from '~/contribution_events/components/contribution_event/contribution_event_base.vue';
|
||||
import { eventMerged } from '../../utils';
|
||||
|
||||
const defaultPropsData = {
|
||||
event: eventMerged(),
|
||||
};
|
||||
|
||||
describe('ContributionEventMerged', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMountExtended(ContributionEventMerged, {
|
||||
propsData: defaultPropsData,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('renders `ContributionEventBase`', () => {
|
||||
expect(wrapper.findComponent(ContributionEventBase).props()).toEqual({
|
||||
event: defaultPropsData.event,
|
||||
iconName: 'git-merge',
|
||||
iconClass: 'gl-text-blue-600',
|
||||
message: ContributionEventMerged.i18n.message,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -6,6 +6,7 @@ import ContributionEventJoined from '~/contribution_events/components/contributi
|
|||
import ContributionEventLeft from '~/contribution_events/components/contribution_event/contribution_event_left.vue';
|
||||
import ContributionEventPushed from '~/contribution_events/components/contribution_event/contribution_event_pushed.vue';
|
||||
import ContributionEventPrivate from '~/contribution_events/components/contribution_event/contribution_event_private.vue';
|
||||
import ContributionEventMerged from '~/contribution_events/components/contribution_event/contribution_event_merged.vue';
|
||||
import {
|
||||
eventApproved,
|
||||
eventExpired,
|
||||
|
|
@ -13,6 +14,7 @@ import {
|
|||
eventLeft,
|
||||
eventPushedBranch,
|
||||
eventPrivate,
|
||||
eventMerged,
|
||||
} from '../utils';
|
||||
|
||||
describe('ContributionEvents', () => {
|
||||
|
|
@ -28,6 +30,7 @@ describe('ContributionEvents', () => {
|
|||
eventLeft(),
|
||||
eventPushedBranch(),
|
||||
eventPrivate(),
|
||||
eventMerged(),
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
@ -41,6 +44,7 @@ describe('ContributionEvents', () => {
|
|||
${ContributionEventLeft} | ${eventLeft()}
|
||||
${ContributionEventPushed} | ${eventPushedBranch()}
|
||||
${ContributionEventPrivate} | ${eventPrivate()}
|
||||
${ContributionEventMerged} | ${eventMerged()}
|
||||
`(
|
||||
'renders `$expectedComponent.name` component and passes expected event',
|
||||
({ expectedComponent, expectedEvent }) => {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
EVENT_TYPE_LEFT,
|
||||
EVENT_TYPE_PUSHED,
|
||||
EVENT_TYPE_PRIVATE,
|
||||
EVENT_TYPE_MERGED,
|
||||
PUSH_EVENT_REF_TYPE_BRANCH,
|
||||
PUSH_EVENT_REF_TYPE_TAG,
|
||||
} from '~/contribution_events/constants';
|
||||
|
|
@ -20,6 +21,8 @@ export const eventJoined = () => findEventByAction(EVENT_TYPE_JOINED);
|
|||
|
||||
export const eventLeft = () => findEventByAction(EVENT_TYPE_LEFT);
|
||||
|
||||
export const eventMerged = () => findEventByAction(EVENT_TYPE_MERGED);
|
||||
|
||||
const findPushEvent = ({
|
||||
isNew = false,
|
||||
isRemoved = false,
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ describe('diffs/components/app', () => {
|
|||
propsData: {
|
||||
endpointCoverage: `${TEST_HOST}/diff/endpointCoverage`,
|
||||
endpointCodequality: '',
|
||||
endpointSast: '',
|
||||
projectPath: 'namespace/project',
|
||||
currentUser: {},
|
||||
changesEmptyStateIllustration: '',
|
||||
...props,
|
||||
|
|
@ -184,6 +186,16 @@ describe('diffs/components/app', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('SAST diff', () => {
|
||||
it('does not fetch Sast data on FOSS', () => {
|
||||
createComponent();
|
||||
jest.spyOn(wrapper.vm, 'fetchSast');
|
||||
wrapper.vm.fetchData(false);
|
||||
|
||||
expect(wrapper.vm.fetchSast).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('displays loading icon on loading', () => {
|
||||
createComponent({}, ({ state }) => {
|
||||
state.diffs.isLoading = true;
|
||||
|
|
|
|||
|
|
@ -2,20 +2,22 @@ import { GlIcon, GlLink } from '@gitlab/ui';
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import DiffCodeQualityItem from '~/diffs/components/diff_code_quality_item.vue';
|
||||
import { SEVERITY_CLASSES, SEVERITY_ICONS } from '~/ci/reports/codequality_report/constants';
|
||||
import { multipleFindingsArr } from '../mock_data/diff_code_quality';
|
||||
import { multipleFindingsArrCodeQualityScale } from '../mock_data/diff_code_quality';
|
||||
|
||||
let wrapper;
|
||||
|
||||
const [codeQualityFinding] = multipleFindingsArrCodeQualityScale;
|
||||
const findIcon = () => wrapper.findComponent(GlIcon);
|
||||
const findButton = () => wrapper.findComponent(GlLink);
|
||||
const findDescriptionPlainText = () => wrapper.findByTestId('description-plain-text');
|
||||
const findDescriptionLinkSection = () => wrapper.findByTestId('description-button-section');
|
||||
|
||||
describe('DiffCodeQuality', () => {
|
||||
const createWrapper = ({ glFeatures = {} } = {}) => {
|
||||
const createWrapper = ({ glFeatures = {}, link = true } = {}) => {
|
||||
return shallowMountExtended(DiffCodeQualityItem, {
|
||||
propsData: {
|
||||
finding: multipleFindingsArr[0],
|
||||
finding: codeQualityFinding,
|
||||
link,
|
||||
},
|
||||
provide: {
|
||||
glFeatures,
|
||||
|
|
@ -28,8 +30,8 @@ describe('DiffCodeQuality', () => {
|
|||
expect(findIcon().exists()).toBe(true);
|
||||
|
||||
expect(findIcon().attributes()).toMatchObject({
|
||||
class: `codequality-severity-icon ${SEVERITY_CLASSES[multipleFindingsArr[0].severity]}`,
|
||||
name: SEVERITY_ICONS[multipleFindingsArr[0].severity],
|
||||
class: `codequality-severity-icon ${SEVERITY_CLASSES[codeQualityFinding.severity]}`,
|
||||
name: SEVERITY_ICONS[codeQualityFinding.severity],
|
||||
size: '12',
|
||||
});
|
||||
});
|
||||
|
|
@ -41,26 +43,35 @@ describe('DiffCodeQuality', () => {
|
|||
codeQualityInlineDrawer: false,
|
||||
},
|
||||
});
|
||||
expect(findDescriptionPlainText().text()).toContain(multipleFindingsArr[0].severity);
|
||||
expect(findDescriptionPlainText().text()).toContain(multipleFindingsArr[0].description);
|
||||
expect(findDescriptionPlainText().text()).toContain(codeQualityFinding.severity);
|
||||
expect(findDescriptionPlainText().text()).toContain(codeQualityFinding.description);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with codeQualityInlineDrawer flag true', () => {
|
||||
beforeEach(() => {
|
||||
const [{ description, severity }] = multipleFindingsArrCodeQualityScale;
|
||||
const renderedText = `${severity} - ${description}`;
|
||||
it('when link prop is true, should render gl-link', () => {
|
||||
wrapper = createWrapper({
|
||||
glFeatures: {
|
||||
codeQualityInlineDrawer: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findButton().exists()).toBe(true);
|
||||
expect(findButton().text()).toBe(renderedText);
|
||||
});
|
||||
|
||||
it('should render severity as plain text', () => {
|
||||
expect(findDescriptionLinkSection().text()).toContain(multipleFindingsArr[0].severity);
|
||||
});
|
||||
it('when link prop is false, should not render gl-link', () => {
|
||||
wrapper = createWrapper({
|
||||
glFeatures: {
|
||||
codeQualityInlineDrawer: true,
|
||||
},
|
||||
link: false,
|
||||
});
|
||||
|
||||
it('should render button with description text', () => {
|
||||
expect(findButton().text()).toContain(multipleFindingsArr[0].description);
|
||||
expect(findButton().exists()).toBe(false);
|
||||
expect(findDescriptionLinkSection().text()).toBe(renderedText);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,26 +1,32 @@
|
|||
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import DiffCodeQuality from '~/diffs/components/diff_code_quality.vue';
|
||||
import DiffCodeQualityItem from '~/diffs/components/diff_code_quality_item.vue';
|
||||
import { NEW_CODE_QUALITY_FINDINGS } from '~/diffs/i18n';
|
||||
import { multipleFindingsArr } from '../mock_data/diff_code_quality';
|
||||
import { NEW_CODE_QUALITY_FINDINGS, NEW_SAST_FINDINGS } from '~/diffs/i18n';
|
||||
import {
|
||||
multipleCodeQualityNoSast,
|
||||
multipleSastNoCodeQuality,
|
||||
multipleFindingsArrSastScale,
|
||||
} from '../mock_data/diff_code_quality';
|
||||
|
||||
let wrapper;
|
||||
|
||||
const diffItems = () => wrapper.findAllComponents(DiffCodeQualityItem);
|
||||
const findHeading = () => wrapper.findByTestId(`diff-codequality-findings-heading`);
|
||||
const findCodeQualityHeading = () => wrapper.findByTestId(`diff-codequality-findings-heading`);
|
||||
const findSastHeading = () => wrapper.findByTestId(`diff-sast-findings-heading`);
|
||||
|
||||
describe('DiffCodeQuality', () => {
|
||||
const createWrapper = (codeQuality, mountFunction = mountExtended) => {
|
||||
const createWrapper = (findings, mountFunction = mountExtended) => {
|
||||
return mountFunction(DiffCodeQuality, {
|
||||
propsData: {
|
||||
expandedLines: [],
|
||||
codeQuality,
|
||||
codeQuality: findings.codeQuality,
|
||||
sast: findings.sast,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
it('hides details and throws hideCodeQualityFindings event on close click', async () => {
|
||||
wrapper = createWrapper(multipleFindingsArr);
|
||||
wrapper = createWrapper(multipleCodeQualityNoSast);
|
||||
expect(wrapper.findByTestId('diff-codequality').exists()).toBe(true);
|
||||
|
||||
await wrapper.findByTestId('diff-codequality-close').trigger('click');
|
||||
|
|
@ -28,11 +34,29 @@ describe('DiffCodeQuality', () => {
|
|||
});
|
||||
|
||||
it('renders heading and correct amount of list items for codequality array and their description', () => {
|
||||
wrapper = createWrapper(multipleFindingsArr, shallowMountExtended);
|
||||
wrapper = createWrapper(multipleCodeQualityNoSast, shallowMountExtended);
|
||||
|
||||
expect(findHeading().text()).toEqual(NEW_CODE_QUALITY_FINDINGS);
|
||||
expect(findCodeQualityHeading().text()).toEqual(NEW_CODE_QUALITY_FINDINGS);
|
||||
|
||||
expect(diffItems()).toHaveLength(multipleFindingsArr.length);
|
||||
expect(diffItems().at(0).props().finding).toEqual(multipleFindingsArr[0]);
|
||||
expect(diffItems()).toHaveLength(multipleCodeQualityNoSast.codeQuality.length);
|
||||
expect(diffItems().at(0).props().finding).toEqual(multipleCodeQualityNoSast.codeQuality[0]);
|
||||
});
|
||||
|
||||
it('does not render codeQuality section when codeQuality array is empty', () => {
|
||||
wrapper = createWrapper(multipleSastNoCodeQuality, shallowMountExtended);
|
||||
expect(findCodeQualityHeading().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders heading and correct amount of list items for sast array and their description', () => {
|
||||
wrapper = createWrapper(multipleSastNoCodeQuality, shallowMountExtended);
|
||||
|
||||
expect(findSastHeading().text()).toEqual(NEW_SAST_FINDINGS);
|
||||
expect(diffItems()).toHaveLength(multipleSastNoCodeQuality.sast.length);
|
||||
expect(diffItems().at(0).props().finding).toEqual(multipleFindingsArrSastScale[0]);
|
||||
});
|
||||
|
||||
it('does not render sast section when sast array is empty', () => {
|
||||
wrapper = createWrapper(multipleCodeQualityNoSast, shallowMountExtended);
|
||||
expect(findSastHeading().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import Vuex from 'vuex';
|
|||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { sprintf } from '~/locale';
|
||||
import { createAlert } from '~/alert';
|
||||
import * as diffRowUtils from 'ee_else_ce/diffs/components/diff_row_utils';
|
||||
import DiffContentComponent from '~/diffs/components/diff_content.vue';
|
||||
import DiffDiscussions from '~/diffs/components/diff_discussions.vue';
|
||||
import DiffView from '~/diffs/components/diff_view.vue';
|
||||
|
|
@ -77,6 +78,7 @@ describe('DiffContent', () => {
|
|||
getCommentFormForDiffFile: getCommentFormForDiffFileGetterMock,
|
||||
diffLines: () => () => [...getDiffFileMock().parallel_diff_lines],
|
||||
fileLineCodequality: () => () => [],
|
||||
fileLineSast: () => () => [],
|
||||
},
|
||||
actions: {
|
||||
saveDiffDiscussion: saveDiffDiscussionMock,
|
||||
|
|
@ -118,6 +120,32 @@ describe('DiffContent', () => {
|
|||
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should include Sast findings when sastReportsInInlineDiff flag is true', () => {
|
||||
const mapParallelSpy = jest.spyOn(diffRowUtils, 'mapParallel');
|
||||
const mapParallelNoSastSpy = jest.spyOn(diffRowUtils, 'mapParallelNoSast');
|
||||
createComponent({
|
||||
provide: {
|
||||
glFeatures: {
|
||||
sastReportsInInlineDiff: true,
|
||||
},
|
||||
},
|
||||
props: { diffFile: { ...textDiffFile, renderingLines: true } },
|
||||
});
|
||||
|
||||
expect(mapParallelSpy).toHaveBeenCalled();
|
||||
expect(mapParallelNoSastSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not include Sast findings when sastReportsInInlineDiff flag is false', () => {
|
||||
const mapParallelSpy = jest.spyOn(diffRowUtils, 'mapParallel');
|
||||
const mapParallelNoSastSpy = jest.spyOn(diffRowUtils, 'mapParallelNoSast');
|
||||
|
||||
createComponent({ props: { diffFile: { ...textDiffFile, renderingLines: true } } });
|
||||
|
||||
expect(mapParallelNoSastSpy).toHaveBeenCalled();
|
||||
expect(mapParallelSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with whitespace only change', () => {
|
||||
|
|
|
|||
|
|
@ -546,6 +546,24 @@ describe('DiffFile', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('loads collapsed file on mounted when single file mode is enabled', async () => {
|
||||
const file = {
|
||||
...getReadableFile(),
|
||||
load_collapsed_diff_url: '/diff_for_path',
|
||||
highlighted_diff_lines: [],
|
||||
parallel_diff_lines: [],
|
||||
viewer: { name: 'collapsed', automaticallyCollapsed: true },
|
||||
};
|
||||
|
||||
axiosMock.onGet(file.load_collapsed_diff_url).reply(HTTP_STATUS_OK, getReadableFile());
|
||||
|
||||
({ wrapper, store } = createComponent({ file, props: { viewDiffsFileByFile: true } }));
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(findLoader(wrapper).exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('merge conflicts', () => {
|
||||
it('does not render conflict alert', () => {
|
||||
const file = {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,13 @@ const left = {
|
|||
severity: EXAMPLE_SEVERITY,
|
||||
},
|
||||
],
|
||||
sast: [
|
||||
{
|
||||
line: EXAMPLE_LINE_NUMBER,
|
||||
description: EXAMPLE_DESCRIPTION,
|
||||
severity: EXAMPLE_SEVERITY,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -30,6 +37,13 @@ const right = {
|
|||
severity: EXAMPLE_SEVERITY,
|
||||
},
|
||||
],
|
||||
sast: [
|
||||
{
|
||||
line: EXAMPLE_LINE_NUMBER,
|
||||
description: EXAMPLE_DESCRIPTION,
|
||||
severity: EXAMPLE_SEVERITY,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -60,6 +74,13 @@ describe('DiffLine', () => {
|
|||
severity: EXAMPLE_SEVERITY,
|
||||
},
|
||||
]);
|
||||
expect(wrapper.findComponent(DiffCodeQuality).props('sast')).toEqual([
|
||||
{
|
||||
line: EXAMPLE_LINE_NUMBER,
|
||||
description: EXAMPLE_DESCRIPTION,
|
||||
severity: EXAMPLE_SEVERITY,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export const multipleFindingsArr = [
|
||||
export const multipleFindingsArrCodeQualityScale = [
|
||||
{
|
||||
severity: 'minor',
|
||||
description: 'mocked minor Issue',
|
||||
|
|
@ -31,19 +31,78 @@ export const multipleFindingsArr = [
|
|||
},
|
||||
];
|
||||
|
||||
export const fiveFindings = {
|
||||
filePath: 'index.js',
|
||||
codequality: multipleFindingsArr.slice(0, 5),
|
||||
export const multipleFindingsArrSastScale = [
|
||||
{
|
||||
severity: 'low',
|
||||
description: 'mocked low Issue',
|
||||
line: 2,
|
||||
},
|
||||
{
|
||||
severity: 'medium',
|
||||
description: 'mocked medium Issue',
|
||||
line: 3,
|
||||
},
|
||||
{
|
||||
severity: 'info',
|
||||
description: 'mocked info Issue',
|
||||
line: 3,
|
||||
},
|
||||
{
|
||||
severity: 'high',
|
||||
description: 'mocked high Issue',
|
||||
line: 3,
|
||||
},
|
||||
{
|
||||
severity: 'critical',
|
||||
description: 'mocked critical Issue',
|
||||
line: 3,
|
||||
},
|
||||
{
|
||||
severity: 'unknown',
|
||||
description: 'mocked unknown Issue',
|
||||
line: 3,
|
||||
},
|
||||
];
|
||||
|
||||
export const multipleCodeQualityNoSast = {
|
||||
codeQuality: multipleFindingsArrCodeQualityScale,
|
||||
sast: [],
|
||||
};
|
||||
|
||||
export const threeFindings = {
|
||||
filePath: 'index.js',
|
||||
codequality: multipleFindingsArr.slice(0, 3),
|
||||
export const multipleSastNoCodeQuality = {
|
||||
codeQuality: [],
|
||||
sast: multipleFindingsArrSastScale,
|
||||
};
|
||||
|
||||
export const singularFinding = {
|
||||
export const fiveCodeQualityFindings = {
|
||||
filePath: 'index.js',
|
||||
codequality: [multipleFindingsArr[0]],
|
||||
codequality: multipleFindingsArrCodeQualityScale.slice(0, 5),
|
||||
};
|
||||
|
||||
export const threeCodeQualityFindings = {
|
||||
filePath: 'index.js',
|
||||
codequality: multipleFindingsArrCodeQualityScale.slice(0, 3),
|
||||
};
|
||||
|
||||
export const singularCodeQualityFinding = {
|
||||
filePath: 'index.js',
|
||||
codequality: [multipleFindingsArrCodeQualityScale[0]],
|
||||
};
|
||||
|
||||
export const singularFindingSast = {
|
||||
filePath: 'index.js',
|
||||
sast: [multipleFindingsArrSastScale[0]],
|
||||
};
|
||||
|
||||
export const threeSastFindings = {
|
||||
filePath: 'index.js',
|
||||
sast: multipleFindingsArrSastScale.slice(0, 3),
|
||||
};
|
||||
|
||||
export const oneCodeQualityTwoSastFindings = {
|
||||
filePath: 'index.js',
|
||||
sast: multipleFindingsArrSastScale.slice(0, 2),
|
||||
codequality: [multipleFindingsArrCodeQualityScale[0]],
|
||||
};
|
||||
|
||||
export const diffCodeQuality = {
|
||||
|
|
@ -73,7 +132,7 @@ export const diffCodeQuality = {
|
|||
old_line: null,
|
||||
new_line: 2,
|
||||
|
||||
codequality: [multipleFindingsArr[0]],
|
||||
codequality: [multipleFindingsArrCodeQualityScale[0]],
|
||||
lineDrafts: [],
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -365,7 +365,7 @@ describe('DiffsStoreActions', () => {
|
|||
{ type: types.SET_RETRIEVING_BATCHES, payload: false },
|
||||
{ type: types.SET_BATCH_LOADING_STATE, payload: 'error' },
|
||||
],
|
||||
[],
|
||||
[{ type: 'startRenderDiffsQueue' }, { type: 'startRenderDiffsQueue' }],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -664,6 +664,41 @@ describe('DiffsStoreActions', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('startRenderDiffsQueue', () => {
|
||||
it('should set all files to RENDER_FILE', () => {
|
||||
const state = {
|
||||
diffFiles: [
|
||||
{
|
||||
id: 1,
|
||||
renderIt: false,
|
||||
viewer: {
|
||||
automaticallyCollapsed: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
renderIt: false,
|
||||
viewer: {
|
||||
automaticallyCollapsed: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const pseudoCommit = (commitType, file) => {
|
||||
expect(commitType).toBe(types.RENDER_FILE);
|
||||
Object.assign(file, {
|
||||
renderIt: true,
|
||||
});
|
||||
};
|
||||
|
||||
diffActions.startRenderDiffsQueue({ state, commit: pseudoCommit });
|
||||
|
||||
expect(state.diffFiles[0].renderIt).toBe(true);
|
||||
expect(state.diffFiles[1].renderIt).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setInlineDiffViewType', () => {
|
||||
it('should set diff view type to inline and also set the cookie properly', async () => {
|
||||
await testAction(
|
||||
|
|
@ -1251,11 +1286,12 @@ describe('DiffsStoreActions', () => {
|
|||
$emit = jest.spyOn(eventHub, '$emit');
|
||||
});
|
||||
|
||||
it('expands the file for the given discussion id', () => {
|
||||
it('renders and expands file for the given discussion id', () => {
|
||||
const localState = state({ collapsed: true, renderIt: false });
|
||||
|
||||
diffActions.renderFileForDiscussionId({ rootState, state: localState, commit }, '123');
|
||||
|
||||
expect(commit).toHaveBeenCalledWith('RENDER_FILE', localState.diffFiles[0]);
|
||||
expect($emit).toHaveBeenCalledTimes(1);
|
||||
expect(commonUtils.scrollToElement).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
|
@ -1342,6 +1378,18 @@ describe('DiffsStoreActions', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('setRenderIt', () => {
|
||||
it('commits RENDER_FILE', () => {
|
||||
return testAction(
|
||||
diffActions.setRenderIt,
|
||||
'file',
|
||||
{},
|
||||
[{ type: types.RENDER_FILE, payload: 'file' }],
|
||||
[],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('receiveFullDiffError', () => {
|
||||
it('updates state with the file that did not load', () => {
|
||||
return testAction(
|
||||
|
|
@ -1466,7 +1514,7 @@ describe('DiffsStoreActions', () => {
|
|||
payload: { filePath: testFilePath, lines: [preparedLine, preparedLine] },
|
||||
},
|
||||
],
|
||||
[],
|
||||
[{ type: 'startRenderDiffsQueue' }],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ describe('DiffsStoreMutations', () => {
|
|||
|
||||
mutations[types.SET_DIFF_DATA_BATCH](state, diffMock);
|
||||
|
||||
expect(state.diffFiles[0].renderIt).toEqual(true);
|
||||
expect(state.diffFiles[0].collapsed).toEqual(false);
|
||||
expect(state.treeEntries[mockFile.file_path].diffLoaded).toBe(true);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -437,7 +437,7 @@ describe('DiffsStoreUtils', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('sets the collapsed attribute on files', () => {
|
||||
it('sets the renderIt and collapsed attribute on files', () => {
|
||||
const checkLine = preparedDiff.diff_files[0][INLINE_DIFF_LINES_KEY][0];
|
||||
|
||||
expect(checkLine.discussions.length).toBe(0);
|
||||
|
|
@ -448,6 +448,7 @@ describe('DiffsStoreUtils', () => {
|
|||
expect(firstChar).not.toBe('+');
|
||||
expect(firstChar).not.toBe('-');
|
||||
|
||||
expect(preparedDiff.diff_files[0].renderIt).toBe(true);
|
||||
expect(preparedDiff.diff_files[0].collapsed).toBe(false);
|
||||
});
|
||||
|
||||
|
|
@ -528,7 +529,8 @@ describe('DiffsStoreUtils', () => {
|
|||
preparedDiffFiles = utils.prepareDiffData({ diff: mock, meta: true });
|
||||
});
|
||||
|
||||
it('sets the collapsed attribute on files', () => {
|
||||
it('sets the renderIt and collapsed attribute on files', () => {
|
||||
expect(preparedDiffFiles[0].renderIt).toBe(true);
|
||||
expect(preparedDiffFiles[0].collapsed).toBeUndefined();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ RSpec.describe Gitlab::Kas::Client do
|
|||
|
||||
let(:repository) { instance_double(Gitlab::Agent::Entity::GitalyRepository) }
|
||||
let(:gitaly_info) { instance_double(Gitlab::Agent::Entity::GitalyInfo) }
|
||||
let(:gitaly_features) { Feature::Gitaly.server_feature_flags }
|
||||
|
||||
let(:agent_configurations) { [double] }
|
||||
|
||||
|
|
@ -94,7 +95,7 @@ RSpec.describe Gitlab::Kas::Client do
|
|||
.and_return(repository)
|
||||
|
||||
expect(Gitlab::Agent::Entity::GitalyInfo).to receive(:new)
|
||||
.with(Gitlab::GitalyClient.connection_data(project.repository_storage))
|
||||
.with(Gitlab::GitalyClient.connection_data(project.repository_storage).merge(features: gitaly_features))
|
||||
.and_return(gitaly_info)
|
||||
|
||||
expect(Gitlab::Agent::ConfigurationProject::Rpc::ListAgentConfigFilesRequest).to receive(:new)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@ RSpec.describe Packages::Npm::Metadatum, type: :model, feature_category: :packag
|
|||
|
||||
it { is_expected.not_to allow_value({}).for(:package_json) }
|
||||
|
||||
it { is_expected.not_to allow_value(test: 'test' * 10000).for(:package_json) }
|
||||
it {
|
||||
is_expected.not_to allow_value(test: 'test' * 10000).for(:package_json).with_message(/structure is too large/)
|
||||
}
|
||||
|
||||
def with_dist
|
||||
valid_json.tap do |h|
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ RSpec.describe API::Internal::Kubernetes, feature_category: :deployment_manageme
|
|||
'gitaly_info' => a_hash_including(
|
||||
'address' => match(/\.socket$/),
|
||||
'token' => 'secret',
|
||||
'features' => {}
|
||||
'features' => Feature::Gitaly.server_feature_flags
|
||||
),
|
||||
'gitaly_repository' => a_hash_including(
|
||||
'storage_name' => project.repository_storage,
|
||||
|
|
@ -290,7 +290,7 @@ RSpec.describe API::Internal::Kubernetes, feature_category: :deployment_manageme
|
|||
'gitaly_info' => a_hash_including(
|
||||
'address' => match(/\.socket$/),
|
||||
'token' => 'secret',
|
||||
'features' => {}
|
||||
'features' => Feature::Gitaly.server_feature_flags
|
||||
),
|
||||
'gitaly_repository' => a_hash_including(
|
||||
'storage_name' => project.repository_storage,
|
||||
|
|
|
|||
|
|
@ -240,12 +240,13 @@ RSpec.describe API::NpmProjectPackages, feature_category: :package_registry do
|
|||
|
||||
subject(:upload_package_with_token) { upload_with_token(package_name, params) }
|
||||
|
||||
shared_examples 'handling invalid record with 400 error' do
|
||||
shared_examples 'handling invalid record with 400 error' do |error_message|
|
||||
it 'handles an ActiveRecord::RecordInvalid exception with 400 error' do
|
||||
expect { upload_package_with_token }
|
||||
.not_to change { project.packages.count }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['error']).to eq(error_message)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -255,7 +256,7 @@ RSpec.describe API::NpmProjectPackages, feature_category: :package_registry do
|
|||
let(:package_name) { "@#{group.path}/my_inv@@lid_package_name" }
|
||||
let(:params) { upload_params(package_name: package_name) }
|
||||
|
||||
it_behaves_like 'handling invalid record with 400 error'
|
||||
it_behaves_like 'handling invalid record with 400 error', "Validation failed: Name is invalid, Name #{Gitlab::Regex.npm_package_name_regex_message}"
|
||||
it_behaves_like 'not a package tracking event'
|
||||
end
|
||||
|
||||
|
|
@ -277,7 +278,7 @@ RSpec.describe API::NpmProjectPackages, feature_category: :package_registry do
|
|||
with_them do
|
||||
let(:params) { upload_params(package_name: package_name, package_version: version) }
|
||||
|
||||
it_behaves_like 'handling invalid record with 400 error'
|
||||
it_behaves_like 'handling invalid record with 400 error', "Validation failed: Version #{Gitlab::Regex.semver_regex_message}"
|
||||
it_behaves_like 'not a package tracking event'
|
||||
end
|
||||
end
|
||||
|
|
@ -286,7 +287,7 @@ RSpec.describe API::NpmProjectPackages, feature_category: :package_registry do
|
|||
let(:package_name) { "@#{group.path}/my_package_name" }
|
||||
let(:params) { upload_params(package_name: package_name, file: 'npm/payload_with_empty_attachment.json') }
|
||||
|
||||
it_behaves_like 'handling invalid record with 400 error'
|
||||
it_behaves_like 'handling invalid record with 400 error', 'Attachment data is empty.'
|
||||
it_behaves_like 'not a package tracking event'
|
||||
end
|
||||
end
|
||||
|
|
@ -373,7 +374,7 @@ RSpec.describe API::NpmProjectPackages, feature_category: :package_registry do
|
|||
|
||||
let(:package_name) { "@#{group.path}/test" }
|
||||
|
||||
it_behaves_like 'handling invalid record with 400 error'
|
||||
it_behaves_like 'handling invalid record with 400 error', 'Validation failed: Package already exists'
|
||||
it_behaves_like 'not a package tracking event'
|
||||
|
||||
context 'with a new version' do
|
||||
|
|
@ -408,6 +409,7 @@ RSpec.describe API::NpmProjectPackages, feature_category: :package_registry do
|
|||
.not_to change { project.packages.count }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(json_response['error']).to eq('Package already exists.')
|
||||
end
|
||||
|
||||
it_behaves_like 'does not enqueue a worker to sync a metadata cache' do
|
||||
|
|
@ -461,7 +463,8 @@ RSpec.describe API::NpmProjectPackages, feature_category: :package_registry do
|
|||
.not_to change { project.packages.count }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(response.body).to include('Could not obtain package lease.')
|
||||
expect(response.body).to include('Could not obtain package lease. Please try again.')
|
||||
expect(json_response['error']).to eq('Could not obtain package lease. Please try again.')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -487,15 +490,8 @@ RSpec.describe API::NpmProjectPackages, feature_category: :package_registry do
|
|||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'handling invalid record with 400 error', 'Validation failed: Package json structure is too large. Maximum size is 20000 characters'
|
||||
it_behaves_like 'not a package tracking event'
|
||||
|
||||
it 'returns an error' do
|
||||
expect { upload_package_with_token }
|
||||
.not_to change { project.packages.count }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(response.body).to include('Validation failed: Package json structure is too large')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
|
|||
field_sizes: expected_field_sizes
|
||||
)
|
||||
|
||||
expect { subject }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Package json structure is too large')
|
||||
expect { subject }.to raise_error(ActiveRecord::RecordInvalid, /structure is too large/)
|
||||
.and not_change { Packages::Package.count }
|
||||
.and not_change { Packages::Package.npm.count }
|
||||
.and not_change { Packages::Tag.count }
|
||||
|
|
@ -296,7 +296,7 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
|
|||
end
|
||||
|
||||
with_them do
|
||||
it { expect { subject }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Version is invalid') }
|
||||
it { expect { subject }.to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Version #{Gitlab::Regex.semver_regex_message}") }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -319,7 +319,7 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
|
|||
end
|
||||
|
||||
it { expect(subject[:http_status]).to eq 400 }
|
||||
it { expect(subject[:message]).to eq 'Could not obtain package lease.' }
|
||||
it { expect(subject[:message]).to eq 'Could not obtain package lease. Please try again.' }
|
||||
end
|
||||
|
||||
context 'when many of the same packages are created at the same time', :delete do
|
||||
|
|
|
|||
|
|
@ -44,11 +44,7 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
|
|||
end
|
||||
|
||||
shared_examples 'reject metadata request' do |status:|
|
||||
it 'rejects the metadata request' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(status)
|
||||
end
|
||||
it_behaves_like 'returning response status', status
|
||||
end
|
||||
|
||||
shared_examples 'redirect metadata request' do |status:|
|
||||
|
|
@ -280,13 +276,15 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
|
|||
example_name = 'redirect metadata request'
|
||||
status = :redirected
|
||||
else
|
||||
example_name = 'reject metadata request'
|
||||
status = :not_found
|
||||
end
|
||||
end
|
||||
|
||||
status = :not_found if scope == :group && params[:package_name_type] == :non_existing && !params[:request_forward]
|
||||
|
||||
# Check the error message for :not_found
|
||||
example_name = 'returning response status with error' if status == :not_found
|
||||
|
||||
it_behaves_like example_name, status: status
|
||||
end
|
||||
end
|
||||
|
|
@ -361,11 +359,11 @@ RSpec.shared_examples 'handling audit request' do |path:, scope: :project|
|
|||
end
|
||||
|
||||
shared_examples 'reject audit request' do |status:|
|
||||
it 'rejects the audit request' do
|
||||
subject
|
||||
it_behaves_like 'returning response status', status
|
||||
end
|
||||
|
||||
expect(response).to have_gitlab_http_status(status)
|
||||
end
|
||||
shared_examples 'reject audit request with error' do |status:|
|
||||
it_behaves_like 'returning response status with error', status: status, error: 'Project not found'
|
||||
end
|
||||
|
||||
shared_examples 'redirect audit request' do |status:|
|
||||
|
|
@ -464,7 +462,7 @@ RSpec.shared_examples 'handling audit request' do |path:, scope: :project|
|
|||
example_name = 'redirect audit request'
|
||||
status = :temporary_redirect
|
||||
else
|
||||
example_name = 'reject audit request'
|
||||
example_name = 'reject audit request with error'
|
||||
status = :not_found
|
||||
end
|
||||
end
|
||||
|
|
@ -633,12 +631,12 @@ RSpec.shared_examples 'handling get dist tags requests' do |scope: :project|
|
|||
example_name = "#{params[:expected_result]} package tags request"
|
||||
status = params[:expected_status]
|
||||
|
||||
if scope == :instance && params[:package_name_type] != :scoped_naming_convention
|
||||
example_name = 'reject package tags request'
|
||||
if (scope == :instance && params[:package_name_type] != :scoped_naming_convention) || (scope == :group && params[:package_name_type] == :non_existing)
|
||||
status = :not_found
|
||||
end
|
||||
|
||||
status = :not_found if scope == :group && params[:package_name_type] == :non_existing
|
||||
# Check the error message for :not_found
|
||||
example_name = 'returning response status with error' if status == :not_found
|
||||
|
||||
it_behaves_like example_name, status: status
|
||||
end
|
||||
|
|
@ -858,6 +856,9 @@ RSpec.shared_examples 'handling different package names, visibilities and user r
|
|||
|
||||
status = :not_found if scope == :group && params[:package_name_type] == :non_existing && params[:auth].present?
|
||||
|
||||
# Check the error message for :not_found
|
||||
example_name = 'returning response status with error' if status == :not_found
|
||||
|
||||
it_behaves_like example_name, status: status
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -36,18 +36,18 @@ RSpec.shared_examples 'accept package tags request' do |status:|
|
|||
end
|
||||
|
||||
context 'with invalid package name' do
|
||||
where(:package_name, :status) do
|
||||
'%20' | :bad_request
|
||||
nil | :not_found
|
||||
where(:package_name, :status, :error) do
|
||||
'%20' | :bad_request | '"Package Name" not given'
|
||||
nil | :not_found | %r{\A(Packages|Project) not found\z}
|
||||
end
|
||||
|
||||
with_them do
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
it_behaves_like 'returning response status with error', status: params[:status], error: params[:error]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'accept create package tag request' do |user_type|
|
||||
RSpec.shared_examples 'accept create package tag request' do |status:|
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
context 'with valid package name' do
|
||||
|
|
@ -92,45 +92,55 @@ RSpec.shared_examples 'accept create package tag request' do |user_type|
|
|||
expect(response.body).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'with ActiveRecord::RecordInvalid error' do
|
||||
before do
|
||||
allow_next_instance_of(Packages::Tag) do |tag|
|
||||
allow(tag).to receive(:save!).and_raise(ActiveRecord::RecordInvalid)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'returning response status with error', status: :bad_request, error: 'Record invalid'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid package name' do
|
||||
where(:package_name, :status) do
|
||||
'unknown' | :not_found
|
||||
'' | :not_found
|
||||
'%20' | :bad_request
|
||||
where(:package_name, :status, :error) do
|
||||
'unknown' | :not_found | %r{\A(Package|Project) not found\z}
|
||||
'' | :not_found | '404 Not Found'
|
||||
'%20' | :bad_request | '"Package Name" not given'
|
||||
end
|
||||
|
||||
with_them do
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
it_behaves_like 'returning response status with error', status: params[:status], error: params[:error]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid tag name' do
|
||||
where(:tag_name, :status) do
|
||||
'' | :not_found
|
||||
'%20' | :bad_request
|
||||
where(:tag_name, :status, :error) do
|
||||
'' | :not_found | '404 Not Found'
|
||||
'%20' | :bad_request | '"Tag" not given'
|
||||
end
|
||||
|
||||
with_them do
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
it_behaves_like 'returning response status with error', status: params[:status], error: params[:error]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid version' do
|
||||
where(:version, :status) do
|
||||
' ' | :bad_request
|
||||
'' | :bad_request
|
||||
nil | :bad_request
|
||||
where(:version, :status, :error) do
|
||||
' ' | :bad_request | '"Version" not given'
|
||||
'' | :bad_request | '"Version" not given'
|
||||
nil | :bad_request | '"Version" not given'
|
||||
end
|
||||
|
||||
with_them do
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
it_behaves_like 'returning response status with error', status: params[:status], error: params[:error]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'accept delete package tag request' do |user_type|
|
||||
RSpec.shared_examples 'accept delete package tag request' do |status:|
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
context 'with valid package name' do
|
||||
|
|
@ -159,29 +169,39 @@ RSpec.shared_examples 'accept delete package tag request' do |user_type|
|
|||
|
||||
it_behaves_like 'returning response status', :not_found
|
||||
end
|
||||
|
||||
context 'with ActiveRecord::RecordInvalid error' do
|
||||
before do
|
||||
allow_next_instance_of(::Packages::RemoveTagService) do |service|
|
||||
allow(service).to receive(:execute).and_raise(ActiveRecord::RecordInvalid)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'returning response status with error', status: :bad_request, error: 'Record invalid'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid package name' do
|
||||
where(:package_name, :status) do
|
||||
'unknown' | :not_found
|
||||
'' | :not_found
|
||||
'%20' | :bad_request
|
||||
where(:package_name, :status, :error) do
|
||||
'unknown' | :not_found | %r{\A(Package tag|Project) not found\z}
|
||||
'' | :not_found | '404 Not Found'
|
||||
'%20' | :bad_request | '"Package Name" not given'
|
||||
end
|
||||
|
||||
with_them do
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
it_behaves_like 'returning response status with error', status: params[:status], error: params[:error]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid tag name' do
|
||||
where(:tag_name, :status) do
|
||||
'unknown' | :not_found
|
||||
'' | :not_found
|
||||
'%20' | :bad_request
|
||||
where(:tag_name, :status, :error) do
|
||||
'unknown' | :not_found | %r{\A(Package tag|Project) not found\z}
|
||||
'' | :not_found | '404 Not Found'
|
||||
'%20' | :bad_request | '"Tag" not given'
|
||||
end
|
||||
|
||||
with_them do
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
it_behaves_like 'returning response status with error', status: params[:status], error: params[:error]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'returning response status with error' do |status:, error: nil|
|
||||
it "returns #{status} and error message" do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(status)
|
||||
expect(json_response['error']).to be_present
|
||||
expect(json_response['error']).to match(error) if error
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue