Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-07-13 21:10:39 +00:00
parent e62d575077
commit 75f2fd2ba8
94 changed files with 1534 additions and 674 deletions

View File

@ -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';

View File

@ -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,
},
};

View File

@ -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);
}

View File

@ -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>

View File

@ -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;
}

View File

@ -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>

View File

@ -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"

View File

@ -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>

View File

@ -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(

View File

@ -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>

View File

@ -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)"
>

View File

@ -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>

View File

@ -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>

View File

@ -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);
};

View File

@ -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);

View File

@ -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>

View File

@ -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);
});
},
},

View File

@ -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.',

View File

@ -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',

View File

@ -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');
});
}

View File

@ -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}

View File

@ -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);

View File

@ -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: [],

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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.

View File

@ -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**.

View File

@ -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.

View File

@ -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.
![Domain Denylist](../../user/admin_area/settings/img/domain_denylist_v14_1.png)
## 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. -->

View File

@ -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:
![Sign up form](../../user/admin_area/settings/img/sign_up_terms.png)
<!-- ## 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. -->

View File

@ -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. |

View File

@ -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. -->

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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)

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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.
![Domain Denylist](img/domain_denylist_v14_1.png)
## 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 -->

View File

@ -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:
![Sign up form](img/sign_up_terms.png)
<!-- ## 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 -->

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -4,8 +4,5 @@ inherit_from:
AllCops:
NewCops: enable
Gitlab/RSpec/AvoidSetup:
Enabled: false
RSpec/MultipleMemoizedHelpers:
Max: 25

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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() {

View File

@ -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

View File

@ -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];

View File

@ -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);
});
});

View File

@ -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,
});
});
});

View File

@ -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 }) => {

View File

@ -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,

View File

@ -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;

View File

@ -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);
});
});
});

View File

@ -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);
});
});

View File

@ -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', () => {

View File

@ -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 = {

View 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,
},
]);
});
});
});

View File

@ -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: [],
},
},

View File

@ -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' }],
);
},
);

View File

@ -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);
});

View File

@ -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();
});

View File

@ -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)

View File

@ -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|

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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