Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
79cbe31b18
commit
26384c9a61
|
|
@ -1,4 +1,4 @@
|
|||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33"
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.34"
|
||||
|
||||
stages:
|
||||
- sync
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@
|
|||
- .default-retry
|
||||
- .default-before_script
|
||||
- .assets-compile-cache
|
||||
image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-graphicsmagick-1.3.33-docker-19.03.1
|
||||
image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-graphicsmagick-1.3.34-docker-19.03.1
|
||||
stage: prepare
|
||||
services:
|
||||
- docker:19.03.0-dind
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@
|
|||
- name: redis:alpine
|
||||
|
||||
.use-pg10:
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-postgresql-10-graphicsmagick-1.3.33"
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-postgresql-10-graphicsmagick-1.3.34"
|
||||
services:
|
||||
- name: postgres:10.9
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
|
|
@ -217,7 +217,7 @@
|
|||
- name: elasticsearch:6.4.2
|
||||
|
||||
.use-pg10-ee:
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-postgresql-10-graphicsmagick-1.3.33"
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-postgresql-10-graphicsmagick-1.3.34"
|
||||
services:
|
||||
- name: postgres:10.9
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
|
|
|
|||
|
|
@ -374,7 +374,7 @@ export default {
|
|||
|
||||
<div
|
||||
:data-can-create-note="getNoteableData.current_user.can_create_note"
|
||||
class="files d-flex prepend-top-default"
|
||||
class="files d-flex"
|
||||
>
|
||||
<div
|
||||
v-show="showTreeList"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<script>
|
||||
/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
|
|
@ -63,9 +62,6 @@ export default {
|
|||
showDropdowns() {
|
||||
return !this.commit && this.mergeRequestDiffs.length;
|
||||
},
|
||||
fileTreeIcon() {
|
||||
return this.showTreeList ? 'collapse-left' : 'expand-left';
|
||||
},
|
||||
toggleFileBrowserTitle() {
|
||||
return this.showTreeList ? __('Hide file browser') : __('Show file browser');
|
||||
},
|
||||
|
|
@ -91,7 +87,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mr-version-controls border-top border-bottom">
|
||||
<div class="mr-version-controls border-top">
|
||||
<div
|
||||
class="mr-version-menus-container content-block"
|
||||
:class="{
|
||||
|
|
@ -108,17 +104,17 @@ export default {
|
|||
:title="toggleFileBrowserTitle"
|
||||
@click="toggleShowTreeList"
|
||||
>
|
||||
<icon :name="fileTreeIcon" />
|
||||
<icon name="file-tree" />
|
||||
</button>
|
||||
<div v-if="showDropdowns" class="d-flex align-items-center compare-versions-container">
|
||||
Changes between
|
||||
{{ __('Compare') }}
|
||||
<compare-versions-dropdown
|
||||
:other-versions="mergeRequestDiffs"
|
||||
:merge-request-version="mergeRequestDiff"
|
||||
:show-commit-count="true"
|
||||
class="mr-version-dropdown"
|
||||
/>
|
||||
and
|
||||
{{ __('and') }}
|
||||
<compare-versions-dropdown
|
||||
:other-versions="comparableDiffs"
|
||||
:base-version-path="baseVersionPath"
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ export default {
|
|||
|
||||
<div
|
||||
v-if="!diffFile.submodule && addMergeRequestButtons"
|
||||
class="file-actions d-none d-sm-block"
|
||||
class="file-actions d-none d-sm-flex align-items-center flex-wrap"
|
||||
>
|
||||
<diff-stats :added-lines="diffFile.added_lines" :removed-lines="diffFile.removed_lines" />
|
||||
<div class="btn-group" role="group">
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
filesText() {
|
||||
return n__('File', 'Files', this.diffFilesLength);
|
||||
return n__('file', 'files', this.diffFilesLength);
|
||||
},
|
||||
isCompareVersionsHeader() {
|
||||
return Boolean(this.diffFilesLength);
|
||||
|
|
@ -44,13 +44,21 @@ export default {
|
|||
>
|
||||
<div v-if="hasDiffFiles" class="diff-stats-group">
|
||||
<icon name="doc-code" class="diff-stats-icon text-secondary" />
|
||||
<strong>{{ diffFilesLength }} {{ filesText }}</strong>
|
||||
<span class="text-secondary bold">{{ diffFilesLength }} {{ filesText }}</span>
|
||||
</div>
|
||||
<div class="diff-stats-group cgreen">
|
||||
<icon name="file-addition" class="diff-stats-icon" /> <strong>{{ addedLines }}</strong>
|
||||
<div
|
||||
class="diff-stats-group cgreen d-flex align-items-center"
|
||||
:class="{ bold: isCompareVersionsHeader }"
|
||||
>
|
||||
<span>+</span>
|
||||
<span class="js-file-addition-line">{{ addedLines }}</span>
|
||||
</div>
|
||||
<div class="diff-stats-group cred">
|
||||
<icon name="file-deletion" class="diff-stats-icon" /> <strong>{{ removedLines }}</strong>
|
||||
<div
|
||||
class="diff-stats-group cred d-flex align-items-center"
|
||||
:class="{ bold: isCompareVersionsHeader }"
|
||||
>
|
||||
<span>-</span>
|
||||
<span class="js-file-deletion-line">{{ removedLines }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ export default {
|
|||
this.search = '';
|
||||
},
|
||||
},
|
||||
searchPlaceholder: sprintf(s__('MergeRequest|Filter files or search with %{modifier_key}+p'), {
|
||||
modifier_key: /Mac/i.test(navigator.userAgent) ? 'cmd' : 'ctrl',
|
||||
searchPlaceholder: sprintf(s__('MergeRequest|Search files (%{modifier_key}P)'), {
|
||||
modifier_key: /Mac/i.test(navigator.userAgent) ? '⌘' : 'Ctrl+',
|
||||
}),
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -54,10 +54,6 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
issueDetailsPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
issueStackTracePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
@ -72,7 +68,7 @@ export default {
|
|||
},
|
||||
},
|
||||
apollo: {
|
||||
GQLerror: {
|
||||
error: {
|
||||
query,
|
||||
variables() {
|
||||
return {
|
||||
|
|
@ -81,19 +77,19 @@ export default {
|
|||
};
|
||||
},
|
||||
pollInterval: 2000,
|
||||
update: data => data.project.sentryDetailedError,
|
||||
update: data => data.project.sentryErrors.detailedError,
|
||||
error: () => createFlash(__('Failed to load error details from Sentry.')),
|
||||
result(res) {
|
||||
if (res.data.project?.sentryDetailedError) {
|
||||
this.$apollo.queries.GQLerror.stopPolling();
|
||||
this.setStatus(this.GQLerror.status);
|
||||
if (res.data.project?.sentryErrors?.detailedError) {
|
||||
this.$apollo.queries.error.stopPolling();
|
||||
this.setStatus(this.error.status);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
GQLerror: null,
|
||||
error: null,
|
||||
issueCreationInProgress: false,
|
||||
isAlertVisible: false,
|
||||
closedIssueId: null,
|
||||
|
|
@ -101,8 +97,6 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapState('details', [
|
||||
'error',
|
||||
'loading',
|
||||
'loadingStacktrace',
|
||||
'stacktraceData',
|
||||
'updatingResolveStatus',
|
||||
|
|
@ -114,28 +108,23 @@ export default {
|
|||
return sprintf(
|
||||
__('Reported %{timeAgo} by %{reportedBy}'),
|
||||
{
|
||||
reportedBy: `<strong>${this.GQLerror.culprit}</strong>`,
|
||||
reportedBy: `<strong>${this.error.culprit}</strong>`,
|
||||
timeAgo: this.timeFormatted(this.stacktraceData.date_received),
|
||||
},
|
||||
false,
|
||||
);
|
||||
},
|
||||
firstReleaseLink() {
|
||||
return `${this.error.external_base_url}/releases/${this.GQLerror.firstReleaseShortVersion}`;
|
||||
return `${this.error.externalBaseUrl}/releases/${this.error.firstReleaseShortVersion}`;
|
||||
},
|
||||
lastReleaseLink() {
|
||||
return `${this.error.external_base_url}releases/${this.GQLerror.lastReleaseShortVersion}`;
|
||||
},
|
||||
showDetails() {
|
||||
return Boolean(
|
||||
!this.loading && !this.$apollo.queries.GQLerror.loading && this.error && this.GQLerror,
|
||||
);
|
||||
return `${this.error.externalBaseUrl}/releases/${this.error.lastReleaseShortVersion}`;
|
||||
},
|
||||
showStacktrace() {
|
||||
return Boolean(!this.loadingStacktrace && this.stacktrace && this.stacktrace.length);
|
||||
return Boolean(this.stacktrace?.length);
|
||||
},
|
||||
issueTitle() {
|
||||
return this.GQLerror.title;
|
||||
return this.error.title;
|
||||
},
|
||||
issueDescription() {
|
||||
return sprintf(
|
||||
|
|
@ -144,13 +133,13 @@ export default {
|
|||
),
|
||||
{
|
||||
description: '# Error Details:\n',
|
||||
errorUrl: `${this.GQLerror.externalUrl}\n`,
|
||||
firstSeen: `\n${this.GQLerror.firstSeen}\n`,
|
||||
lastSeen: `${this.GQLerror.lastSeen}\n`,
|
||||
countLabel: n__('- Event', '- Events', this.GQLerror.count),
|
||||
count: `${this.GQLerror.count}\n`,
|
||||
userCountLabel: n__('- User', '- Users', this.GQLerror.userCount),
|
||||
userCount: `${this.GQLerror.userCount}\n`,
|
||||
errorUrl: `${this.error.externalUrl}\n`,
|
||||
firstSeen: `\n${this.error.firstSeen}\n`,
|
||||
lastSeen: `${this.error.lastSeen}\n`,
|
||||
countLabel: n__('- Event', '- Events', this.error.count),
|
||||
count: `${this.error.count}\n`,
|
||||
userCountLabel: n__('- User', '- Users', this.error.userCount),
|
||||
userCount: `${this.error.userCount}\n`,
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
|
@ -171,12 +160,10 @@ export default {
|
|||
},
|
||||
},
|
||||
mounted() {
|
||||
this.startPollingDetails(this.issueDetailsPath);
|
||||
this.startPollingStacktrace(this.issueStackTracePath);
|
||||
},
|
||||
methods: {
|
||||
...mapActions('details', [
|
||||
'startPollingDetails',
|
||||
'startPollingStacktrace',
|
||||
'updateStatus',
|
||||
'setStatus',
|
||||
|
|
@ -214,10 +201,10 @@ export default {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="$apollo.queries.GQLerror.loading || loading" class="py-3">
|
||||
<div v-if="$apollo.queries.error.loading" class="py-3">
|
||||
<gl-loading-icon :size="3" />
|
||||
</div>
|
||||
<div v-else-if="showDetails" class="error-details">
|
||||
<div v-else-if="error" class="error-details">
|
||||
<gl-alert v-if="isAlertVisible" @dismiss="isAlertVisible = false">
|
||||
<gl-sprintf
|
||||
:message="
|
||||
|
|
@ -232,7 +219,7 @@ export default {
|
|||
|
||||
<div class="top-area align-items-center justify-content-between py-3">
|
||||
<span v-if="!loadingStacktrace && stacktrace" v-html="reported"></span>
|
||||
<div class="d-inline-flex">
|
||||
<div class="d-inline-flex ml-lg-auto">
|
||||
<loading-button
|
||||
:label="ignoreBtnLabel"
|
||||
:loading="updatingIgnoreStatus"
|
||||
|
|
@ -247,10 +234,10 @@ export default {
|
|||
@click="onResolveStatusUpdate"
|
||||
/>
|
||||
<gl-button
|
||||
v-if="error.gitlab_issue"
|
||||
v-if="error.gitlabIssuePath"
|
||||
class="ml-2"
|
||||
data-qa-selector="view_issue_button"
|
||||
:href="error.gitlab_issue"
|
||||
:href="error.gitlabIssuePath"
|
||||
variant="success"
|
||||
>
|
||||
{{ __('View issue') }}
|
||||
|
|
@ -264,13 +251,13 @@ export default {
|
|||
<gl-form-input class="hidden" name="issue[title]" :value="issueTitle" />
|
||||
<input name="issue[description]" :value="issueDescription" type="hidden" />
|
||||
<gl-form-input
|
||||
:value="GQLerror.sentryId"
|
||||
:value="error.sentryId"
|
||||
class="hidden"
|
||||
name="issue[sentry_issue_attributes][sentry_issue_identifier]"
|
||||
/>
|
||||
<gl-form-input :value="csrfToken" class="hidden" name="authenticity_token" />
|
||||
<loading-button
|
||||
v-if="!error.gitlab_issue"
|
||||
v-if="!error.gitlabIssuePath"
|
||||
class="btn-success"
|
||||
:label="__('Create issue')"
|
||||
:loading="issueCreationInProgress"
|
||||
|
|
@ -281,8 +268,8 @@ export default {
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<tooltip-on-truncate :title="GQLerror.title" truncate-target="child" placement="top">
|
||||
<h2 class="text-truncate">{{ GQLerror.title }}</h2>
|
||||
<tooltip-on-truncate :title="error.title" truncate-target="child" placement="top">
|
||||
<h2 class="text-truncate">{{ error.title }}</h2>
|
||||
</tooltip-on-truncate>
|
||||
<template v-if="error.tags">
|
||||
<gl-badge
|
||||
|
|
@ -297,53 +284,51 @@ export default {
|
|||
</gl-badge>
|
||||
</template>
|
||||
<ul>
|
||||
<li v-if="GQLerror.gitlabCommit">
|
||||
<li v-if="error.gitlabCommit">
|
||||
<strong class="bold">{{ __('GitLab commit') }}:</strong>
|
||||
<gl-link :href="GQLerror.gitlabCommitPath">
|
||||
<span>{{ GQLerror.gitlabCommit.substr(0, 10) }}</span>
|
||||
<gl-link :href="error.gitlabCommitPath">
|
||||
<span>{{ error.gitlabCommit.substr(0, 10) }}</span>
|
||||
</gl-link>
|
||||
</li>
|
||||
<li v-if="error.gitlab_issue">
|
||||
<li v-if="error.gitlabIssuePath">
|
||||
<strong class="bold">{{ __('GitLab Issue') }}:</strong>
|
||||
<gl-link :href="error.gitlab_issue">
|
||||
<span>{{ error.gitlab_issue }}</span>
|
||||
<gl-link :href="error.gitlabIssuePath">
|
||||
<span>{{ error.gitlabIssuePath }}</span>
|
||||
</gl-link>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="bold">{{ __('Sentry event') }}:</strong>
|
||||
<gl-link
|
||||
v-track-event="trackClickErrorLinkToSentryOptions(GQLerror.externalUrl)"
|
||||
v-track-event="trackClickErrorLinkToSentryOptions(error.externalUrl)"
|
||||
class="d-inline-flex align-items-center"
|
||||
:href="GQLerror.externalUrl"
|
||||
:href="error.externalUrl"
|
||||
target="_blank"
|
||||
>
|
||||
<span class="text-truncate">{{ GQLerror.externalUrl }}</span>
|
||||
<span class="text-truncate">{{ error.externalUrl }}</span>
|
||||
<icon name="external-link" class="ml-1 flex-shrink-0" />
|
||||
</gl-link>
|
||||
</li>
|
||||
<li v-if="GQLerror.firstReleaseShortVersion">
|
||||
<li v-if="error.firstReleaseShortVersion">
|
||||
<strong class="bold">{{ __('First seen') }}:</strong>
|
||||
{{ formatDate(GQLerror.firstSeen) }}
|
||||
{{ formatDate(error.firstSeen) }}
|
||||
<gl-link :href="firstReleaseLink" target="_blank">
|
||||
<span>
|
||||
{{ __('Release') }}: {{ GQLerror.firstReleaseShortVersion.substr(0, 10) }}
|
||||
</span>
|
||||
<span>{{ __('Release') }}: {{ error.firstReleaseShortVersion.substr(0, 10) }}</span>
|
||||
</gl-link>
|
||||
</li>
|
||||
<li v-if="GQLerror.lastReleaseShortVersion">
|
||||
<li v-if="error.lastReleaseShortVersion">
|
||||
<strong class="bold">{{ __('Last seen') }}:</strong>
|
||||
{{ formatDate(GQLerror.lastSeen) }}
|
||||
{{ formatDate(error.lastSeen) }}
|
||||
<gl-link :href="lastReleaseLink" target="_blank">
|
||||
<span>{{ __('Release') }}: {{ GQLerror.lastReleaseShortVersion.substr(0, 10) }}</span>
|
||||
<span>{{ __('Release') }}: {{ error.lastReleaseShortVersion.substr(0, 10) }}</span>
|
||||
</gl-link>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="bold">{{ __('Events') }}:</strong>
|
||||
<span>{{ GQLerror.count }}</span>
|
||||
<span>{{ error.count }}</span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="bold">{{ __('Users') }}:</strong>
|
||||
<span>{{ GQLerror.userCount }}</span>
|
||||
<span>{{ error.userCount }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
|
@ -351,7 +336,7 @@ export default {
|
|||
<gl-loading-icon :size="3" />
|
||||
</div>
|
||||
|
||||
<template v-if="showStacktrace">
|
||||
<template v-else-if="showStacktrace">
|
||||
<h3 class="my-4">{{ __('Stack trace') }}</h3>
|
||||
<stacktrace :entries="stacktrace" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
GlDropdownDivider,
|
||||
GlTooltipDirective,
|
||||
GlPagination,
|
||||
GlButtonGroup,
|
||||
} from '@gitlab/ui';
|
||||
import AccessorUtils from '~/lib/utils/accessor';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
|
|
@ -20,12 +21,16 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
|
|||
import { __ } from '~/locale';
|
||||
import _ from 'underscore';
|
||||
|
||||
export const tableDataClass = 'table-col d-flex d-sm-table-cell';
|
||||
export const tableDataClass = 'table-col d-flex d-sm-table-cell align-items-center';
|
||||
|
||||
export default {
|
||||
FIRST_PAGE: 1,
|
||||
PREV_PAGE: 1,
|
||||
NEXT_PAGE: 2,
|
||||
statusButtons: [
|
||||
{ status: 'ignored', icon: 'eye-slash', title: __('Ignore') },
|
||||
{ status: 'resolved', icon: 'check-circle', title: __('Resolve') },
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
key: 'error',
|
||||
|
|
@ -48,20 +53,13 @@ export default {
|
|||
{
|
||||
key: 'lastSeen',
|
||||
label: __('Last seen'),
|
||||
thClass: '',
|
||||
thClass: 'w-15p',
|
||||
tdClass: `${tableDataClass}`,
|
||||
},
|
||||
{
|
||||
key: 'ignore',
|
||||
key: 'status',
|
||||
label: '',
|
||||
thClass: 'w-3rem',
|
||||
tdClass: `${tableDataClass} pl-0`,
|
||||
},
|
||||
{
|
||||
key: 'resolved',
|
||||
label: '',
|
||||
thClass: 'w-3rem',
|
||||
tdClass: `${tableDataClass} pl-0`,
|
||||
tdClass: `${tableDataClass} text-right`,
|
||||
},
|
||||
{
|
||||
key: 'details',
|
||||
|
|
@ -88,6 +86,7 @@ export default {
|
|||
Icon,
|
||||
GlPagination,
|
||||
TimeAgo,
|
||||
GlButtonGroup,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
|
|
@ -332,25 +331,19 @@ export default {
|
|||
<time-ago :time="errors.item.lastSeen" class="text-secondary" />
|
||||
</div>
|
||||
</template>
|
||||
<template #cell(ignore)="errors">
|
||||
<gl-button
|
||||
ref="ignoreError"
|
||||
v-gl-tooltip.hover
|
||||
:title="__('Ignore')"
|
||||
@click="updateIssueStatus(errors.item.id, 'ignored')"
|
||||
>
|
||||
<gl-icon name="eye-slash" :size="12" />
|
||||
</gl-button>
|
||||
</template>
|
||||
<template #cell(resolved)="errors">
|
||||
<gl-button
|
||||
ref="resolveError"
|
||||
v-gl-tooltip
|
||||
:title="__('Resolve')"
|
||||
@click="updateIssueStatus(errors.item.id, 'resolved')"
|
||||
>
|
||||
<gl-icon name="check-circle" :size="12" />
|
||||
</gl-button>
|
||||
<template #cell(status)="errors">
|
||||
<gl-button-group>
|
||||
<gl-button
|
||||
v-for="button in $options.statusButtons"
|
||||
:key="button.status"
|
||||
:ref="button.title.toLowerCase() + 'Error'"
|
||||
v-gl-tooltip.hover
|
||||
:title="button.title"
|
||||
@click="updateIssueStatus(errors.item.id, button.status)"
|
||||
>
|
||||
<gl-icon :name="button.icon" :size="12" />
|
||||
</gl-button>
|
||||
</gl-button-group>
|
||||
</template>
|
||||
<template #cell(details)="errors">
|
||||
<gl-button
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ export default () => {
|
|||
issueId,
|
||||
projectPath,
|
||||
issueUpdatePath,
|
||||
issueDetailsPath,
|
||||
issueStackTracePath,
|
||||
projectIssuesPath,
|
||||
} = domEl.dataset;
|
||||
|
|
@ -36,7 +35,6 @@ export default () => {
|
|||
issueId,
|
||||
projectPath,
|
||||
issueUpdatePath,
|
||||
issueDetailsPath,
|
||||
issueStackTracePath,
|
||||
projectIssuesPath,
|
||||
csrfToken: csrf.token,
|
||||
|
|
|
|||
|
|
@ -1,21 +1,29 @@
|
|||
query errorDetails($fullPath: ID!, $errorId: ID!) {
|
||||
project(fullPath: $fullPath) {
|
||||
sentryDetailedError(id: $errorId) {
|
||||
id
|
||||
sentryId
|
||||
title
|
||||
userCount
|
||||
count
|
||||
status
|
||||
firstSeen
|
||||
lastSeen
|
||||
message
|
||||
culprit
|
||||
externalUrl
|
||||
firstReleaseShortVersion
|
||||
lastReleaseShortVersion
|
||||
gitlabCommit
|
||||
gitlabCommitPath
|
||||
sentryErrors {
|
||||
detailedError(id: $errorId) {
|
||||
id
|
||||
sentryId
|
||||
title
|
||||
userCount
|
||||
count
|
||||
status
|
||||
firstSeen
|
||||
lastSeen
|
||||
message
|
||||
culprit
|
||||
tags {
|
||||
level
|
||||
logger
|
||||
}
|
||||
externalUrl
|
||||
externalBaseUrl
|
||||
firstReleaseShortVersion
|
||||
lastReleaseShortVersion
|
||||
gitlabCommit
|
||||
gitlabCommitPath
|
||||
gitlabIssuePath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,36 +5,11 @@ import Poll from '~/lib/utils/poll';
|
|||
import { __ } from '~/locale';
|
||||
|
||||
let stackTracePoll;
|
||||
let detailPoll;
|
||||
|
||||
const stopPolling = poll => {
|
||||
if (poll) poll.stop();
|
||||
};
|
||||
|
||||
export function startPollingDetails({ commit }, endpoint) {
|
||||
detailPoll = new Poll({
|
||||
resource: service,
|
||||
method: 'getSentryData',
|
||||
data: { endpoint },
|
||||
successCallback: ({ data }) => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
commit(types.SET_ERROR, data.error);
|
||||
commit(types.SET_LOADING, false);
|
||||
|
||||
stopPolling(detailPoll);
|
||||
},
|
||||
errorCallback: () => {
|
||||
commit(types.SET_LOADING, false);
|
||||
createFlash(__('Failed to load error details from Sentry.'));
|
||||
},
|
||||
});
|
||||
|
||||
detailPoll.makeRequest();
|
||||
}
|
||||
|
||||
export function startPollingStacktrace({ commit }, endpoint) {
|
||||
stackTracePoll = new Poll({
|
||||
resource: service,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,2 @@
|
|||
export const SET_ERROR = 'SET_ERRORS';
|
||||
export const SET_LOADING = 'SET_LOADING';
|
||||
export const SET_LOADING_STACKTRACE = 'SET_LOADING_STACKTRACE';
|
||||
export const SET_STACKTRACE_DATA = 'SET_STACKTRACE_DATA';
|
||||
|
|
|
|||
|
|
@ -1,12 +1,6 @@
|
|||
import * as types from './mutation_types';
|
||||
|
||||
export default {
|
||||
[types.SET_ERROR](state, data) {
|
||||
state.error = data;
|
||||
},
|
||||
[types.SET_LOADING](state, loading) {
|
||||
state.loading = loading;
|
||||
},
|
||||
[types.SET_LOADING_STACKTRACE](state, data) {
|
||||
state.loadingStacktrace = data;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
export default () => ({
|
||||
error: {},
|
||||
stacktraceData: {},
|
||||
loading: true,
|
||||
loadingStacktrace: true,
|
||||
updatingResolveStatus: false,
|
||||
updatingIgnoreStatus: false,
|
||||
|
|
|
|||
|
|
@ -18,17 +18,17 @@ export default {
|
|||
<div class="ide-nav-form p-0">
|
||||
<tabs stop-propagation>
|
||||
<tab active>
|
||||
<template slot="title">
|
||||
{{ __('Merge Requests') }}
|
||||
</template>
|
||||
<merge-request-search-list />
|
||||
</tab>
|
||||
<tab>
|
||||
<template slot="title">
|
||||
{{ __('Branches') }}
|
||||
</template>
|
||||
<branches-search-list />
|
||||
</tab>
|
||||
<tab>
|
||||
<template slot="title">
|
||||
{{ __('Merge Requests') }}
|
||||
</template>
|
||||
<merge-request-search-list />
|
||||
</tab>
|
||||
</tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ide-new-btn d-none">
|
||||
<div class="ide-new-btn">
|
||||
<div
|
||||
:class="{
|
||||
show: isOpen,
|
||||
|
|
|
|||
|
|
@ -1224,6 +1224,8 @@ $ide-commit-header-height: 48px;
|
|||
}
|
||||
|
||||
.ide-new-btn {
|
||||
display: none;
|
||||
|
||||
.btn {
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@
|
|||
cursor: pointer;
|
||||
|
||||
@media (min-width: map-get($grid-breakpoints, md)) {
|
||||
// The `-1` below is to prevent two borders from clashing up against eachother -
|
||||
// The `+11` is to ensure the file header border shows when scrolled -
|
||||
// the bottom of the compare-versions header and the top of the file header
|
||||
$mr-file-header-top: $mr-version-controls-height + $header-height + $mr-tabs-height - 1;
|
||||
$mr-file-header-top: $mr-version-controls-height + $header-height + $mr-tabs-height + 11;
|
||||
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
|
|
@ -547,7 +547,7 @@ table.code {
|
|||
|
||||
.diff-stats {
|
||||
align-items: center;
|
||||
padding: 0 0.25rem;
|
||||
padding: 0 1rem;
|
||||
|
||||
.diff-stats-group {
|
||||
padding: 0 0.25rem;
|
||||
|
|
@ -559,7 +559,7 @@ table.code {
|
|||
|
||||
&.is-compare-versions-header {
|
||||
.diff-stats-group {
|
||||
padding: 0 0.5rem;
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1054,8 +1054,8 @@ table.code {
|
|||
.diff-tree-list {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
$top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
|
||||
top: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
|
||||
$top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 11px;
|
||||
top: $top-pos;
|
||||
max-height: calc(100vh - #{$top-pos});
|
||||
z-index: 202;
|
||||
|
||||
|
|
@ -1092,10 +1092,7 @@ table.code {
|
|||
|
||||
.tree-list-scroll {
|
||||
max-height: 100%;
|
||||
padding-top: $grid-size;
|
||||
padding-bottom: $grid-size;
|
||||
border-top: 1px solid $border-color;
|
||||
border-bottom: 1px solid $border-color;
|
||||
overflow-y: scroll;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -708,7 +708,7 @@
|
|||
.mr-version-controls {
|
||||
position: relative;
|
||||
z-index: 203;
|
||||
background: $gray-light;
|
||||
background: $white-light;
|
||||
color: $gl-text-color;
|
||||
margin-top: -1px;
|
||||
|
||||
|
|
@ -732,7 +732,7 @@
|
|||
}
|
||||
|
||||
.content-block {
|
||||
padding: $gl-padding-top $gl-padding;
|
||||
padding: $gl-padding;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ module ConfirmEmailWarning
|
|||
protected
|
||||
|
||||
def show_confirm_warning?
|
||||
html_request? && request.get? && Feature.enabled?(:soft_email_confirmation)
|
||||
html_request? && request.get?
|
||||
end
|
||||
|
||||
def set_confirm_warning
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class ConfirmationsController < Devise::ConfirmationsController
|
|||
protected
|
||||
|
||||
def after_resending_confirmation_instructions_path_for(resource)
|
||||
Feature.enabled?(:soft_email_confirmation) ? stored_location_for(resource) || dashboard_projects_path : users_almost_there_path
|
||||
stored_location_for(resource) || dashboard_projects_path
|
||||
end
|
||||
|
||||
def after_confirmation_path_for(resource_name, resource)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController
|
|||
skip_cross_project_access_check :index
|
||||
|
||||
def index
|
||||
@snippet_counts = Snippets::CountService
|
||||
.new(current_user, author: current_user)
|
||||
.execute
|
||||
|
||||
@snippets = SnippetsFinder.new(current_user, author: current_user, scope: params[:scope])
|
||||
.execute
|
||||
.page(params[:page])
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ class Projects::SnippetsController < Projects::ApplicationController
|
|||
respond_to :html
|
||||
|
||||
def index
|
||||
@snippet_counts = Snippets::CountService
|
||||
.new(current_user, project: @project)
|
||||
.execute
|
||||
|
||||
@snippets = SnippetsFinder.new(current_user, project: @project, scope: params[:scope])
|
||||
.execute
|
||||
.page(params[:page])
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
|
||||
def welcome
|
||||
return redirect_to new_user_registration_path unless current_user
|
||||
return redirect_to stored_location_or_dashboard_or_almost_there_path(current_user) if current_user.role.present? && !current_user.setup_for_company.nil?
|
||||
return redirect_to stored_location_or_dashboard(current_user) if current_user.role.present? && !current_user.setup_for_company.nil?
|
||||
|
||||
current_user.name = nil if current_user.name == current_user.username
|
||||
end
|
||||
|
|
@ -65,7 +65,7 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
if result[:status] == :success
|
||||
track_experiment_event(:signup_flow, 'end') # We want this event to be tracked when the user is _in_ the experimental group
|
||||
set_flash_message! :notice, :signed_up
|
||||
redirect_to stored_location_or_dashboard_or_almost_there_path(current_user)
|
||||
redirect_to stored_location_or_dashboard(current_user)
|
||||
else
|
||||
render :welcome
|
||||
end
|
||||
|
|
@ -112,12 +112,12 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
|
||||
return users_sign_up_welcome_path if experiment_enabled?(:signup_flow)
|
||||
|
||||
stored_location_or_dashboard_or_almost_there_path(user)
|
||||
stored_location_or_dashboard(user)
|
||||
end
|
||||
|
||||
def after_inactive_sign_up_path_for(resource)
|
||||
Gitlab::AppLogger.info(user_created_message)
|
||||
Feature.enabled?(:soft_email_confirmation) ? dashboard_projects_path : users_almost_there_path
|
||||
dashboard_projects_path
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -179,18 +179,10 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
Gitlab::Utils.to_boolean(params[:terms_opt_in])
|
||||
end
|
||||
|
||||
def confirmed_or_unconfirmed_access_allowed(user)
|
||||
user.confirmed? || Feature.enabled?(:soft_email_confirmation) || experiment_enabled?(:signup_flow)
|
||||
end
|
||||
|
||||
def stored_location_or_dashboard(user)
|
||||
stored_location_for(user) || dashboard_projects_path
|
||||
end
|
||||
|
||||
def stored_location_or_dashboard_or_almost_there_path(user)
|
||||
confirmed_or_unconfirmed_access_allowed(user) ? stored_location_or_dashboard(user) : users_almost_there_path
|
||||
end
|
||||
|
||||
# Part of an experiment to build a new sign up flow. Will be resolved
|
||||
# with https://gitlab.com/gitlab-org/growth/engineering/issues/64
|
||||
def choose_layout
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ module Projects::ErrorTrackingHelper
|
|||
{
|
||||
'issue-id' => issue_id,
|
||||
'project-path' => project.full_path,
|
||||
'issue-details-path' => details_project_error_tracking_index_path(*opts),
|
||||
'issue-update-path' => update_project_error_tracking_index_path(*opts),
|
||||
'project-issues-path' => project_issues_path(project),
|
||||
'issue-stack-trace-path' => stack_trace_project_error_tracking_index_path(*opts)
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ module Clusters
|
|||
end
|
||||
|
||||
def ingress_service
|
||||
cluster.kubeclient.get_service('istio-ingressgateway', 'istio-system')
|
||||
cluster.kubeclient.get_service('istio-ingressgateway', Clusters::Kubernetes::ISTIO_SYSTEM_NAMESPACE)
|
||||
end
|
||||
|
||||
def uninstall_command
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ module Serverless
|
|||
belongs_to :knative, class_name: 'Clusters::Applications::Knative', foreign_key: 'clusters_applications_knative_id'
|
||||
belongs_to :creator, class_name: 'User', optional: true
|
||||
|
||||
attr_encrypted :key,
|
||||
mode: :per_attribute_iv,
|
||||
key: Settings.attr_encrypted_db_key_base_truncated,
|
||||
algorithm: 'aes-256-gcm'
|
||||
|
||||
validates :pages_domain, :knative, presence: true
|
||||
validates :uuid, presence: true, uniqueness: true, length: { is: Gitlab::Serverless::Domain::UUID_LENGTH },
|
||||
format: { with: HEX_REGEXP, message: 'only allows hex characters' }
|
||||
|
|
|
|||
|
|
@ -1638,13 +1638,6 @@ class User < ApplicationRecord
|
|||
super
|
||||
end
|
||||
|
||||
# override from Devise::Confirmable
|
||||
def confirmation_period_valid?
|
||||
return false if Feature.disabled?(:soft_email_confirmation)
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_private_profile_to_false
|
||||
|
|
|
|||
|
|
@ -12,5 +12,7 @@ module Clusters
|
|||
GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME = 'gitlab-knative-serving-rolebinding'
|
||||
GITLAB_CROSSPLANE_DATABASE_ROLE_NAME = 'gitlab-crossplane-database-role'
|
||||
GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME = 'gitlab-crossplane-database-rolebinding'
|
||||
KNATIVE_SERVING_NAMESPACE = 'knative-serving'
|
||||
ISTIO_SYSTEM_NAMESPACE = 'istio-system'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'openssl'
|
||||
|
||||
module Clusters
|
||||
module Kubernetes
|
||||
class ConfigureIstioIngressService
|
||||
PASSTHROUGH_RESOURCE = Kubeclient::Resource.new(
|
||||
mode: 'PASSTHROUGH'
|
||||
).freeze
|
||||
|
||||
MTLS_RESOURCE = Kubeclient::Resource.new(
|
||||
mode: 'MUTUAL',
|
||||
privateKey: '/etc/istio/ingressgateway-certs/tls.key',
|
||||
serverCertificate: '/etc/istio/ingressgateway-certs/tls.crt',
|
||||
caCertificates: '/etc/istio/ingressgateway-ca-certs/cert.pem'
|
||||
).freeze
|
||||
|
||||
def initialize(cluster:)
|
||||
@cluster = cluster
|
||||
@platform = cluster.platform
|
||||
@kubeclient = platform.kubeclient
|
||||
@knative = cluster.application_knative
|
||||
end
|
||||
|
||||
def execute
|
||||
return configure_certificates if serverless_domain_cluster
|
||||
|
||||
configure_passthrough
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :cluster, :platform, :kubeclient, :knative
|
||||
|
||||
def serverless_domain_cluster
|
||||
knative&.serverless_domain_cluster
|
||||
end
|
||||
|
||||
def configure_certificates
|
||||
create_or_update_istio_cert_and_key
|
||||
set_gateway_wildcard_https(MTLS_RESOURCE)
|
||||
end
|
||||
|
||||
def create_or_update_istio_cert_and_key
|
||||
name = OpenSSL::X509::Name.parse("CN=#{knative.hostname}")
|
||||
|
||||
key = OpenSSL::PKey::RSA.new(2048)
|
||||
|
||||
cert = OpenSSL::X509::Certificate.new
|
||||
cert.version = 2
|
||||
cert.serial = 0
|
||||
cert.not_before = Time.now
|
||||
cert.not_after = Time.now + 1000.years
|
||||
|
||||
cert.public_key = key.public_key
|
||||
cert.subject = name
|
||||
cert.issuer = name
|
||||
cert.sign(key, OpenSSL::Digest::SHA256.new)
|
||||
|
||||
serverless_domain_cluster.update!(
|
||||
key: key.to_pem,
|
||||
certificate: cert.to_pem
|
||||
)
|
||||
|
||||
kubeclient.create_or_update_secret(istio_ca_certs_resource)
|
||||
kubeclient.create_or_update_secret(istio_certs_resource)
|
||||
end
|
||||
|
||||
def istio_ca_certs_resource
|
||||
Gitlab::Kubernetes::GenericSecret.new(
|
||||
'istio-ingressgateway-ca-certs',
|
||||
{
|
||||
'cert.pem': Base64.strict_encode64(serverless_domain_cluster.certificate)
|
||||
},
|
||||
Clusters::Kubernetes::ISTIO_SYSTEM_NAMESPACE
|
||||
).generate
|
||||
end
|
||||
|
||||
def istio_certs_resource
|
||||
Gitlab::Kubernetes::TlsSecret.new(
|
||||
'istio-ingressgateway-certs',
|
||||
serverless_domain_cluster.certificate,
|
||||
serverless_domain_cluster.key,
|
||||
Clusters::Kubernetes::ISTIO_SYSTEM_NAMESPACE
|
||||
).generate
|
||||
end
|
||||
|
||||
def set_gateway_wildcard_https(tls_resource)
|
||||
gateway_resource = gateway
|
||||
gateway_resource.spec.servers.each do |server|
|
||||
next unless server.hosts == ['*'] && server.port.name == 'https'
|
||||
|
||||
server.tls = tls_resource
|
||||
end
|
||||
kubeclient.update_gateway(gateway_resource)
|
||||
end
|
||||
|
||||
def configure_passthrough
|
||||
set_gateway_wildcard_https(PASSTHROUGH_RESOURCE)
|
||||
end
|
||||
|
||||
def gateway
|
||||
kubeclient.get_gateway('knative-ingress-gateway', Clusters::Kubernetes::KNATIVE_SERVING_NAMESPACE)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Service for calculating visible Snippet counts via one query
|
||||
# for the given user or project.
|
||||
#
|
||||
# Authorisation level checks will be included, ensuring the correct
|
||||
# counts will be returned for the given user (if any).
|
||||
#
|
||||
# Basic usage:
|
||||
#
|
||||
# user = User.find(1)
|
||||
#
|
||||
# Snippets::CountService.new(user, author: user).execute
|
||||
# #=> {
|
||||
# are_public: 1,
|
||||
# are_internal: 1,
|
||||
# are_private: 1,
|
||||
# all: 3
|
||||
# }
|
||||
#
|
||||
# Counts can be scoped to a project:
|
||||
#
|
||||
# user = User.find(1)
|
||||
# project = Project.find(1)
|
||||
#
|
||||
# Snippets::CountService.new(user, project: project).execute
|
||||
# #=> {
|
||||
# are_public: 1,
|
||||
# are_internal: 1,
|
||||
# are_private: 0,
|
||||
# all: 2
|
||||
# }
|
||||
#
|
||||
# Either a project or an author *must* be supplied.
|
||||
module Snippets
|
||||
class CountService
|
||||
def initialize(current_user, author: nil, project: nil)
|
||||
if !author && !project
|
||||
raise(
|
||||
ArgumentError, 'Must provide either an author or a project'
|
||||
)
|
||||
end
|
||||
|
||||
@snippets_finder = SnippetsFinder.new(current_user, author: author, project: project)
|
||||
end
|
||||
|
||||
def execute
|
||||
counts = snippet_counts
|
||||
return {} unless counts
|
||||
|
||||
counts.slice(
|
||||
:are_public,
|
||||
:are_private,
|
||||
:are_internal,
|
||||
:are_public_or_internal,
|
||||
:total
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def snippet_counts
|
||||
@snippets_finder.execute
|
||||
.reorder(nil)
|
||||
.select("
|
||||
count(case when snippets.visibility_level=#{Snippet::PUBLIC} and snippets.secret is FALSE then 1 else null end) as are_public,
|
||||
count(case when snippets.visibility_level=#{Snippet::INTERNAL} then 1 else null end) as are_internal,
|
||||
count(case when snippets.visibility_level=#{Snippet::PRIVATE} then 1 else null end) as are_private,
|
||||
count(case when visibility_level=#{Snippet::PUBLIC} OR visibility_level=#{Snippet::INTERNAL} then 1 else null end) as are_public_or_internal,
|
||||
count(*) as total
|
||||
")
|
||||
.first
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
= render 'dashboard/snippets_head'
|
||||
- if current_user.snippets.exists?
|
||||
= render partial: 'snippets/snippets_scope_menu', locals: { include_private: true }
|
||||
= render partial: 'snippets/snippets_scope_menu', locals: { include_private: true, counts: @snippet_counts }
|
||||
|
||||
- if current_user.snippets.exists?
|
||||
= render partial: 'shared/snippets/list', locals: { link_project: true }
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
- if current_user
|
||||
.top-area
|
||||
- include_private = @project.team.member?(current_user) || current_user.admin?
|
||||
= render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private }
|
||||
= render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private, counts: @snippet_counts }
|
||||
|
||||
- if new_project_snippet_link.present?
|
||||
.nav-controls
|
||||
|
|
|
|||
|
|
@ -7,25 +7,25 @@
|
|||
= _("All")
|
||||
%span.badge.badge-pill
|
||||
- if include_private
|
||||
= subject.snippets.count
|
||||
= counts[:total]
|
||||
- else
|
||||
= subject.snippets.public_and_internal_only.count
|
||||
= counts[:are_public_or_internal]
|
||||
|
||||
- if include_private
|
||||
%li{ class: active_when(params[:scope] == "are_private") }
|
||||
= link_to subject_snippets_path(subject, scope: 'are_private') do
|
||||
= _("Private")
|
||||
%span.badge.badge-pill
|
||||
= subject.snippets.are_private.count
|
||||
= counts[:are_private]
|
||||
|
||||
%li{ class: active_when(params[:scope] == "are_internal") }
|
||||
= link_to subject_snippets_path(subject, scope: 'are_internal') do
|
||||
= _("Internal")
|
||||
%span.badge.badge-pill
|
||||
= subject.snippets.are_internal.count
|
||||
= counts[:are_internal]
|
||||
|
||||
%li{ class: active_when(params[:scope] == "are_public") }
|
||||
= link_to subject_snippets_path(subject, scope: 'are_public') do
|
||||
= _("Public")
|
||||
%span.badge.badge-pill
|
||||
= subject.snippets.are_public.count
|
||||
= counts[:are_public]
|
||||
|
|
|
|||
|
|
@ -231,6 +231,12 @@
|
|||
:latency_sensitive:
|
||||
:resource_boundary: :unknown
|
||||
:weight: 1
|
||||
- :name: gcp_cluster:cluster_configure_istio
|
||||
:feature_category: :kubernetes_management
|
||||
:has_external_dependencies: true
|
||||
:latency_sensitive:
|
||||
:resource_boundary: :unknown
|
||||
:weight: 1
|
||||
- :name: gcp_cluster:cluster_install_app
|
||||
:feature_category: :kubernetes_management
|
||||
:has_external_dependencies: true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ClusterConfigureIstioWorker
|
||||
include ApplicationWorker
|
||||
include ClusterQueue
|
||||
|
||||
worker_has_external_dependencies!
|
||||
|
||||
def perform(cluster_id)
|
||||
Clusters::Cluster.find_by_id(cluster_id).try do |cluster|
|
||||
Clusters::Kubernetes::ConfigureIstioIngressService.new(cluster: cluster).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Restyle changes header & file tree
|
||||
merge_request: 22364
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allow a grace period for new users to confirm their email
|
||||
merge_request: 24371
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix bug with snippet counts not being scoped to current authorisation
|
||||
merge_request: 21705
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Switch order of tabs in Web IDE nav dropdown
|
||||
merge_request: 24199
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update GraphicsMagick from 1.3.33 to 1.3.34
|
||||
merge_request: 24225
|
||||
author: Takuya Noguchi
|
||||
type: security
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddCertAndKeyToServerlessDomainCluster < ActiveRecord::Migration[5.2]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
add_column :serverless_domain_cluster, :encrypted_key, :text
|
||||
add_column :serverless_domain_cluster, :encrypted_key_iv, :string, limit: 255
|
||||
add_column :serverless_domain_cluster, :certificate, :text
|
||||
end
|
||||
end
|
||||
|
|
@ -3786,6 +3786,9 @@ ActiveRecord::Schema.define(version: 2020_02_04_131054) do
|
|||
t.bigint "creator_id"
|
||||
t.datetime_with_timezone "created_at", null: false
|
||||
t.datetime_with_timezone "updated_at", null: false
|
||||
t.text "encrypted_key"
|
||||
t.string "encrypted_key_iv", limit: 255
|
||||
t.text "certificate"
|
||||
t.index ["clusters_applications_knative_id"], name: "idx_serverless_domain_cluster_on_clusters_applications_knative", unique: true
|
||||
t.index ["creator_id"], name: "index_serverless_domain_cluster_on_creator_id"
|
||||
t.index ["pages_domain_id"], name: "index_serverless_domain_cluster_on_pages_domain_id"
|
||||
|
|
|
|||
|
|
@ -609,3 +609,16 @@ Could not authenticate you from Ldapmain because "Connection timed out - user sp
|
|||
If your configured LDAP provider and/or endpoint is offline or otherwise unreachable by GitLab, no LDAP user will be able to authenticate and log in. GitLab does not cache or store credentials for LDAP users to provide authentication during an LDAP outage.
|
||||
|
||||
Contact your LDAP provider or administrator if you are seeing this error.
|
||||
|
||||
### No file specified as Settingslogic source
|
||||
|
||||
If `sudo gitlab-ctl reconfigure` fails with the following error, or you are seeing it in
|
||||
the logs, you may have malformed YAML in `/etc/gitlab/gitlab.rb`:
|
||||
|
||||
```plaintext
|
||||
Errno::ENOENT: No such file or directory - No file specified as Settingslogic source
|
||||
```
|
||||
|
||||
This issue is frequently due to the spacing in your YAML file. To fix the problem,
|
||||
verify the syntax with **spacing** against the
|
||||
[documentation for the configuration of LDAP](#configuration).
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ GitLab Pages expect to run on their own virtual host. In your DNS server/provide
|
|||
you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the
|
||||
host that GitLab runs. For example, an entry would look like this:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
*.example.io. 1800 IN A 192.0.2.1
|
||||
*.example.io. 1800 IN AAAA 2001::1
|
||||
```
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ GitLab Pages expect to run on their own virtual host. In your DNS server/provide
|
|||
you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the
|
||||
host that GitLab runs. For example, an entry would look like this:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
*.example.io. 1800 IN A 192.0.2.1
|
||||
```
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ The Pages daemon doesn't listen to the outside world.
|
|||
order to enable the pages daemon. In `gitlab_pages_options` the
|
||||
`-pages-domain` must match the `host` setting that you set above.
|
||||
|
||||
```
|
||||
```ini
|
||||
gitlab_pages_enabled=true
|
||||
gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090"
|
||||
```
|
||||
|
|
@ -170,7 +170,7 @@ outside world.
|
|||
|
||||
1. In `gitlab.yml`, set the port to `443` and https to `true`:
|
||||
|
||||
```shell
|
||||
```yaml
|
||||
## GitLab Pages
|
||||
pages:
|
||||
enabled: true
|
||||
|
|
@ -188,9 +188,9 @@ outside world.
|
|||
The `-root-cert` and `-root-key` settings are the wildcard TLS certificates
|
||||
of the `example.io` domain:
|
||||
|
||||
```
|
||||
```ini
|
||||
gitlab_pages_enabled=true
|
||||
gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key
|
||||
gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key"
|
||||
```
|
||||
|
||||
1. Copy the `gitlab-pages-ssl` NGINX configuration file:
|
||||
|
|
@ -256,7 +256,7 @@ world. Custom domains are supported, but no TLS.
|
|||
`-pages-domain` and `-listen-http` must match the `host` and `external_http`
|
||||
settings that you set above respectively:
|
||||
|
||||
```
|
||||
```ini
|
||||
gitlab_pages_enabled=true
|
||||
gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80"
|
||||
```
|
||||
|
|
@ -325,9 +325,9 @@ world. Custom domains and TLS are supported.
|
|||
The `-root-cert` and `-root-key` settings are the wildcard TLS certificates
|
||||
of the `example.io` domain:
|
||||
|
||||
```
|
||||
```ini
|
||||
gitlab_pages_enabled=true
|
||||
gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80 -listen-https 192.0.2.2:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key
|
||||
gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80 -listen-https 192.0.2.2:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key"
|
||||
```
|
||||
|
||||
1. Copy the `gitlab-pages-ssl` NGINX configuration file:
|
||||
|
|
|
|||
|
|
@ -199,6 +199,13 @@ and they will assist you with any issues you are having.
|
|||
helm upgrade <release name> <chart path> -f gitlab.yaml
|
||||
```
|
||||
|
||||
- How to get the manifest for a release. It can be useful because it contains the info about
|
||||
all Kubernetes resources and dependent charts:
|
||||
|
||||
```shell
|
||||
helm get manifest <release name>
|
||||
```
|
||||
|
||||
## Installation of minimal GitLab config via Minukube on macOS
|
||||
|
||||
This section is based on [Developing for Kubernetes with Minikube](https://docs.gitlab.com/charts/development/minikube/index.html)
|
||||
|
|
|
|||
|
|
@ -85,9 +85,9 @@ To manually enable GitLab CI/CD for your repository:
|
|||
|
||||
The web hook URL should be set to the GitLab API to
|
||||
[trigger pull mirroring](../../api/projects.md#start-the-pull-mirroring-process-for-a-project-starter),
|
||||
using the GitLab personal access token we just created.
|
||||
using the GitLab personal access token we just created:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
https://gitlab.com/api/v4/projects/<NAMESPACE>%2F<PROJECT>/mirror/pull?private_token=<PERSONAL_ACCESS_TOKEN>
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ requiring a single keyword to enable the feature for any job.
|
|||
|
||||
Consider a monorepo as follows:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
./service_a
|
||||
./service_b
|
||||
./service_c
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ project:
|
|||
1. Create a new project by selecting **Import project from ➔ Repo by URL**
|
||||
1. Add the following URL:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
https://gitlab.com/gitlab-examples/maven/simple-maven-dep.git
|
||||
```
|
||||
|
||||
|
|
@ -164,7 +164,7 @@ The deployment occurs only if we're pushing or merging to `master` branch, so th
|
|||
Done! Now you have all the changes in the GitLab repo, and a pipeline has already been started for this commit. In the **Pipelines** tab you can see what's happening.
|
||||
If the deployment has been successful, the deploy job log will output:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] BUILD SUCCESS
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
|
|
@ -188,7 +188,7 @@ We'll use again a Maven app that can be cloned from our example project:
|
|||
1. Create a new project by selecting **Import project from ➔ Repo by URL**
|
||||
1. Add the following URL:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
https://gitlab.com/gitlab-examples/maven/simple-maven-app.git
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ To use Dpl you need at least Ruby 1.9.3 with ability to install gems.
|
|||
|
||||
Dpl can be installed on any machine with:
|
||||
|
||||
```
|
||||
```shell
|
||||
gem install dpl
|
||||
```
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ having to test it on a CI server.
|
|||
|
||||
If you don't have Ruby installed you can do it on Debian-compatible Linux with:
|
||||
|
||||
```
|
||||
```shell
|
||||
apt-get update
|
||||
apt-get install ruby-dev
|
||||
```
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ sudo nano /etc/nginx/sites-available/default
|
|||
|
||||
The configuration should be like this.
|
||||
|
||||
```
|
||||
```nginx
|
||||
server {
|
||||
root /var/www/app/current/public;
|
||||
server_name example.com;
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ user following [the upstream installation guide][phpenv-installation].
|
|||
|
||||
Using phpenv also allows to easily configure the PHP environment with:
|
||||
|
||||
```
|
||||
```shell
|
||||
phpenv config-add my_config.ini
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ mix ecto.create
|
|||
|
||||
We expect to see this output at the end of the command:
|
||||
|
||||
```shell
|
||||
```plaintext
|
||||
Generated hello_gitlab_ci app
|
||||
The database for HelloGitlabCi.Repo has been created
|
||||
```
|
||||
|
|
@ -136,7 +136,7 @@ mix phoenix.server
|
|||
|
||||
This will be the output to this command:
|
||||
|
||||
```shell
|
||||
```plaintext
|
||||
[info] Running HelloGitlabCi.Endpoint with Cowboy using http://localhost:4000
|
||||
23 May 11:44:35 - info: compiling
|
||||
23 May 11:44:37 - info: compiled 6 files into 2 files, copied 3 in 9.8 sec
|
||||
|
|
@ -229,7 +229,7 @@ mix test
|
|||
|
||||
Our expected result is this:
|
||||
|
||||
```shell
|
||||
```plaintext
|
||||
....
|
||||
|
||||
Finished in 0.7 seconds
|
||||
|
|
@ -265,20 +265,20 @@ project.
|
|||
As we are focusing on testing (not deploying), you can use the [elixir:latest](https://hub.docker.com/_/elixir) docker image, which already has the
|
||||
dependencies for running Phoenix tests installed, such as Elixir and Erlang:
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
image: elixir:latest
|
||||
```
|
||||
|
||||
- We'll only use `postgres`, so we can delete the `mysql` and `redis` lines from the `services` section:
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
services:
|
||||
- postgres:latest
|
||||
```
|
||||
|
||||
- Now, we'll create a new section called `variables`, before the `before_script` section:
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
variables:
|
||||
POSTGRES_DB: hello_gitlab_ci_test
|
||||
POSTGRES_HOST: postgres
|
||||
|
|
@ -293,7 +293,7 @@ project.
|
|||
|
||||
- In the `before_script` section, we'll add some commands to prepare everything for the test:
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
before_script:
|
||||
- mix local.rebar --force
|
||||
- mix local.hex --force
|
||||
|
|
@ -310,7 +310,7 @@ project.
|
|||
|
||||
Let's take a look at the updated file after the changes:
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
image: elixir:latest
|
||||
|
||||
services:
|
||||
|
|
@ -353,7 +353,7 @@ actual running build job.
|
|||
Click on build's ID to watch the entire process. If everything went as expected, we can wait for the
|
||||
**Build succeeded** at the end of the process! :)
|
||||
|
||||
```
|
||||
```shell
|
||||
$ mix test
|
||||
....
|
||||
|
||||
|
|
|
|||
|
|
@ -114,11 +114,11 @@ SSH key.
|
|||
You can generate the SSH key from the machine that GitLab Runner is installed
|
||||
on, and use that key for all projects that are run on this machine.
|
||||
|
||||
1. First, you need to login to the server that runs your jobs.
|
||||
1. First, log in to the server that runs your jobs.
|
||||
|
||||
1. Then from the terminal login as the `gitlab-runner` user:
|
||||
1. Then, from the terminal, log in as the `gitlab-runner` user:
|
||||
|
||||
```
|
||||
```shell
|
||||
sudo su - gitlab-runner
|
||||
```
|
||||
|
||||
|
|
@ -132,7 +132,7 @@ on, and use that key for all projects that are run on this machine.
|
|||
If you are accessing a private GitLab repository you need to add it as a
|
||||
[deploy key](../../ssh/README.md#deploy-keys).
|
||||
|
||||
Once done, try to login to the remote server in order to accept the fingerprint:
|
||||
Once done, try to log in to the remote server in order to accept the fingerprint:
|
||||
|
||||
```shell
|
||||
ssh example.com
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ The action is irreversible.
|
|||
|
||||
To trigger a job you need to send a `POST` request to GitLab's API endpoint:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
POST /projects/:id/trigger/pipeline
|
||||
```
|
||||
|
||||
|
|
@ -185,7 +185,7 @@ Now, whenever a new tag is pushed on project A, the job will run and the
|
|||
To trigger a job from a webhook of another project you need to add the following
|
||||
webhook URL for Push and Tag events (change the project ID, ref and token):
|
||||
|
||||
```
|
||||
```plaintext
|
||||
https://gitlab.example.com/api/v4/projects/9/ref/master/trigger/pipeline?token=TOKEN
|
||||
```
|
||||
|
||||
|
|
@ -195,7 +195,7 @@ You can pass any number of arbitrary variables in the trigger API call and they
|
|||
will be available in GitLab CI so that they can be used in your `.gitlab-ci.yml`
|
||||
file. The parameter is of the form:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
variables[key]=value
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ The current stages are:
|
|||
## Default image
|
||||
|
||||
The default image is currently
|
||||
`registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33`.
|
||||
`registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.34`.
|
||||
|
||||
It includes Ruby 2.6.5, Go 1.12, Git 2.24, Git LFS 2.9, Chrome 73, Node 12, Yarn 1.16,
|
||||
PostgreSQL 9.6, and Graphics Magick 1.3.33.
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ Otherwise it's pretty likely that you will encounter problems described in the [
|
|||
|
||||
Make sure that the backup script on both servers can connect to the database.
|
||||
|
||||
```
|
||||
```shell
|
||||
# On your CI server:
|
||||
# Omnibus
|
||||
sudo chown gitlab-ci:gitlab-ci /var/opt/gitlab/gitlab-ci/builds
|
||||
|
|
@ -64,7 +64,7 @@ sudo -u gitlab_ci -H bundle exec rake backup:create RAILS_ENV=production
|
|||
|
||||
Also check on your GitLab server.
|
||||
|
||||
```
|
||||
```shell
|
||||
# On your GitLab server:
|
||||
# Omnibus
|
||||
sudo gitlab-backup create SKIP=repositories,uploads
|
||||
|
|
@ -89,7 +89,7 @@ MySQL and your GitLab server uses PostgreSQL you need to pass a special option
|
|||
during the 'Moving data' part. **If your CI server uses PostgreSQL and your
|
||||
GitLab server uses MySQL you cannot migrate your CI data to GitLab 8.0.**
|
||||
|
||||
```
|
||||
```shell
|
||||
# On your CI server:
|
||||
# Omnibus
|
||||
sudo gitlab-ci-rake env:info
|
||||
|
|
@ -99,7 +99,7 @@ cd /home/gitlab_ci/gitlab-ci
|
|||
sudo -u gitlab_ci -H bundle exec rake env:info RAILS_ENV=production
|
||||
```
|
||||
|
||||
```
|
||||
```shell
|
||||
# On your GitLab server:
|
||||
# Omnibus
|
||||
sudo gitlab-rake gitlab:env:info
|
||||
|
|
@ -149,7 +149,7 @@ Now upgrade GitLab CI to version 8.0. If you are using Omnibus packages,
|
|||
|
||||
Disable GitLab CI after upgrading to 8.0.
|
||||
|
||||
```
|
||||
```shell
|
||||
# On your CI server:
|
||||
# Omnibus
|
||||
sudo gitlab-ctl stop ci-unicorn
|
||||
|
|
@ -171,7 +171,7 @@ GitLab server. On Omnibus GitLab servers you will have to add a line to
|
|||
`/etc/gitlab/gitlab.rb`. On GitLab servers installed from source you will have
|
||||
to replace the contents of `/home/git/gitlab/config/secrets.yml`.
|
||||
|
||||
```
|
||||
```shell
|
||||
# On your CI server:
|
||||
# Omnibus
|
||||
sudo gitlab-ci-rake backup:show_secrets
|
||||
|
|
@ -188,7 +188,7 @@ PostgreSQL, add `MYSQL_TO_POSTGRESQL=1` to the end of the rake command. When
|
|||
the command finishes it will print the path to your data export archive; you
|
||||
will need this file later.
|
||||
|
||||
```
|
||||
```shell
|
||||
# On your CI server:
|
||||
# Omnibus
|
||||
sudo chown gitlab-ci:gitlab-ci /var/opt/gitlab/gitlab-ci/builds
|
||||
|
|
@ -209,7 +209,7 @@ this, below we use SSH agent forwarding and 'scp', which will be easy and fast
|
|||
for most setups. You can also copy the data archive first from the CI server to
|
||||
your laptop and then from your laptop to the GitLab server.
|
||||
|
||||
```
|
||||
```shell
|
||||
# Start from your laptop
|
||||
ssh -A ci_admin@ci_server.example
|
||||
# Now on the CI server
|
||||
|
|
@ -221,7 +221,7 @@ scp /path/to/12345_gitlab_ci_backup.tar gitlab_admin@gitlab_server.example:~
|
|||
Make the CI data archive discoverable for GitLab. We assume below that you
|
||||
store backups in the default path, adjust the command if necessary.
|
||||
|
||||
```
|
||||
```shell
|
||||
# On your GitLab server:
|
||||
# Omnibus
|
||||
sudo mv /path/to/12345_gitlab_ci_backup.tar /var/opt/gitlab/backups/
|
||||
|
|
@ -235,7 +235,7 @@ sudo mv /path/to/12345_gitlab_ci_backup.tar /home/git/gitlab/tmp/backups/
|
|||
This step will delete any existing CI data on your GitLab server. There should
|
||||
be no CI data yet because you turned CI on the GitLab server off earlier.
|
||||
|
||||
```
|
||||
```shell
|
||||
# On your GitLab server:
|
||||
# Omnibus
|
||||
sudo chown git:git /var/opt/gitlab/gitlab-ci/builds
|
||||
|
|
@ -248,7 +248,7 @@ sudo -u git -H bundle exec rake ci:migrate RAILS_ENV=production
|
|||
|
||||
### 6. Restart GitLab
|
||||
|
||||
```
|
||||
```shell
|
||||
# On your GitLab server:
|
||||
# Omnibus
|
||||
sudo gitlab-ctl hup unicorn
|
||||
|
|
@ -347,7 +347,7 @@ restoration](../raketasks/backup_restore.md) guide.
|
|||
|
||||
If you see errors like this:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
Missing `secret_key_base` or `db_key_base` for 'production' environment. The secrets will be generated and stored in `config/secrets.yml`
|
||||
rake aborted!
|
||||
Errno::EACCES: Permission denied @ rb_sysopen - config/secrets.yml
|
||||
|
|
@ -360,13 +360,13 @@ The fix for this is to update to Omnibus 7.14 first and then update it to 8.0.
|
|||
|
||||
To fix that issue you have to change builds/ folder permission before doing final backup:
|
||||
|
||||
```
|
||||
```shell
|
||||
sudo chown -R gitlab-ci:gitlab-ci /var/opt/gitlab/gitlab-ci/builds
|
||||
```
|
||||
|
||||
Then before executing `ci:migrate` you need to fix builds folder permission:
|
||||
|
||||
```
|
||||
```shell
|
||||
sudo chown git:git /var/opt/gitlab/gitlab-ci/builds
|
||||
```
|
||||
|
||||
|
|
@ -450,7 +450,7 @@ EOF
|
|||
|
||||
Source installations:
|
||||
|
||||
```
|
||||
```shell
|
||||
cd /home/gitlab_ci/gitlab-ci
|
||||
sudo -u gitlab_ci -H bundle exec rails dbconsole production <<EOF
|
||||
... COPY SQL STATEMENTS FROM ABOVE ...
|
||||
|
|
|
|||
|
|
@ -291,15 +291,9 @@ As the DAST job belongs to a separate `dast` stage that runs after all
|
|||
[default stages](../../../ci/yaml/README.md#stages),
|
||||
don't forget to add `stage: dast` when you override the template job definition.
|
||||
|
||||
## Available variables
|
||||
### Available variables
|
||||
|
||||
DAST can be [configured](#customizing-the-dast-settings) using environment variables.
|
||||
Since it's a wrapper around the ZAP scanning scripts
|
||||
([baseline](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan)
|
||||
or [full](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) scan), it
|
||||
accepts all arguments those scripts recognize (the arguments are the same).
|
||||
The choice of the scan type depends on the `DAST_FULL_SCAN_ENABLED` environment
|
||||
variable value.
|
||||
|
||||
| Environment variable | Required | Description |
|
||||
|-----------------------------| ----------|--------------------------------------------------------------------------------|
|
||||
|
|
@ -314,14 +308,83 @@ variable value.
|
|||
| `DAST_FULL_SCAN_ENABLED` | no | Switches the tool to execute [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) instead of [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan). Boolean. `true`, `True`, or `1` are considered as true value, otherwise false. Defaults to `false`. |
|
||||
| `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` | no | Requires [domain validation](#domain-validation) when running DAST full scans. Boolean. `true`, `True`, or `1` are considered as true value, otherwise false. Defaults to `false`. |
|
||||
|
||||
## Reports JSON format
|
||||
### DAST command-line options
|
||||
|
||||
Not all DAST configuration is available via environment variables. To find out all possible options, run the following configuration.
|
||||
Available command-line options will be printed to the job log:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
template: DAST.gitlab-ci.yml
|
||||
|
||||
dast:
|
||||
script:
|
||||
- /analyze --help
|
||||
```
|
||||
|
||||
You must then overwrite the `script` command to pass in the appropriate argument. For example, AJAX spidering can be enabled by using `-j`, as shown in the following configuration:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
template: DAST.gitlab-ci.yml
|
||||
|
||||
dast:
|
||||
script:
|
||||
- export DAST_WEBSITE=${DAST_WEBSITE:-$(cat environment_url.txt)}
|
||||
- /analyze -j -t $DAST_WEBSITE
|
||||
```
|
||||
|
||||
### Custom ZAProxy configuration
|
||||
|
||||
The ZAProxy server contains many [useful configurable values](https://gitlab.com/gitlab-org/gitlab/issues/36437#note_245801885).
|
||||
Many key/values for `-config` remain undocumented, but there is an untested list of [possible keys](https://gitlab.com/gitlab-org/gitlab/issues/36437#note_244981023).
|
||||
Note that these options are not supported by DAST, and may break the DAST scan when used. An example of how to rewrite the Authorization header value with `TOKEN` follows:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
template: DAST.gitlab-ci.yml
|
||||
|
||||
dast:
|
||||
script:
|
||||
- export DAST_WEBSITE=${DAST_WEBSITE:-$(cat environment_url.txt)}
|
||||
- /analyze -z"-config replacer.full_list\(0\).description=auth -config replacer.full_list\(0\).enabled=true -config replacer.full_list\(0\).matchtype=REQ_HEADER -config replacer.full_list\(0\).matchstr=Authorization -config replacer.full_list\(0\).regex=false -config replacer.full_list\(0\).replacement=TOKEN" -t $DAST_WEBSITE
|
||||
```
|
||||
|
||||
## Reports
|
||||
|
||||
The DAST job can emit various reports.
|
||||
|
||||
### JSON
|
||||
|
||||
CAUTION: **Caution:**
|
||||
The JSON report artifacts are not a public API of DAST and their format may change in the future.
|
||||
The JSON report artifacts are not a public API of DAST and their format is expected to change in the future.
|
||||
|
||||
The DAST tool emits a JSON report report file. Sample report files can be found in the [DAST repository](https://gitlab.com/gitlab-org/security-products/dast/tree/master/test/end-to-end/expect).
|
||||
The DAST tool always emits a JSON report report file called `gl-dast-report.json` and sample reports can be found in the [DAST repository](https://gitlab.com/gitlab-org/security-products/dast/tree/master/test/end-to-end/expect).
|
||||
|
||||
There are two formats of data in the JSON document that are used side by side: the proprietary ZAP format which will be eventually deprecated, and a "common" format which will be the default in the future.
|
||||
There are two formats of data in the JSON report that are used side by side: the proprietary ZAP format which will be eventually deprecated, and a "common" format which will be the default in the future.
|
||||
|
||||
### Other formats
|
||||
|
||||
Reports can also be generated in Markdown, HTML, and XML.
|
||||
|
||||
Reports can be published as artifacts using the following configuration:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
template: DAST.gitlab-ci.yml
|
||||
|
||||
dast:
|
||||
script:
|
||||
- export DAST_WEBSITE=${DAST_WEBSITE:-$(cat environment_url.txt)}
|
||||
- /analyze -r report.html -w report.md -x report.xml -t $DAST_WEBSITE
|
||||
- cp /zap/wrk/report.{html,md,xml} "$PWD"
|
||||
artifacts:
|
||||
paths:
|
||||
- report.html
|
||||
- report.md
|
||||
- report.xml
|
||||
- gl-dast-report.json
|
||||
```
|
||||
|
||||
## Security Dashboard
|
||||
|
||||
|
|
@ -329,6 +392,20 @@ The Security Dashboard is a good place to get an overview of all the security
|
|||
vulnerabilities in your groups, projects and pipelines. Read more about the
|
||||
[Security Dashboard](../security_dashboard/index.md).
|
||||
|
||||
## Bleeding-edge vulnerability definitions
|
||||
|
||||
ZAProxy first creates rules in the `alpha` class. After a testing period with the community, they are promoted to `beta`. DAST uses `beta` definitions by default. To request `alpha` definitions, use `-a` as shown in the following configuration:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
template: DAST.gitlab-ci.yml
|
||||
|
||||
dast:
|
||||
script:
|
||||
- export DAST_WEBSITE=${DAST_WEBSITE:-$(cat environment_url.txt)}
|
||||
- /analyze -a -t $DAST_WEBSITE
|
||||
```
|
||||
|
||||
## Interacting with the vulnerabilities
|
||||
|
||||
Once a vulnerability is found, you can interact with it. Read more on how to
|
||||
|
|
|
|||
|
|
@ -695,14 +695,33 @@ Major upgrades might require additional setup steps, please consult
|
|||
the official [upgrade guide](https://docs.cilium.io/en/stable/install/upgrade/) for more
|
||||
information.
|
||||
|
||||
By default, the drop log for traffic is logged out by the
|
||||
By default, Cilium will drop all non-whitelisted packets upon policy
|
||||
deployment. The audit mode is scheduled for release in
|
||||
[Cilium 1.8](https://github.com/cilium/cilium/pull/9970). In the audit
|
||||
mode non-whitelisted packets will not be dropped, instead audit
|
||||
notifications will be generated. GitLab provides alternative Docker
|
||||
images for Cilium with the audit patch included. You can switch to the
|
||||
custom build and enable the audit mode by adding the following to
|
||||
`.gitlab/managed-apps/cilium/values.yaml`:
|
||||
|
||||
```yml
|
||||
global:
|
||||
registry: registry.gitlab.com/gitlab-org/defend/cilium
|
||||
policyAuditMode: true
|
||||
|
||||
agent:
|
||||
monitor:
|
||||
eventTypes: ["drop", "audit"]
|
||||
```
|
||||
|
||||
The Cilium monitor log for traffic is logged out by the
|
||||
`cilium-monitor` sidecar container. You can check these logs via:
|
||||
|
||||
```shell
|
||||
kubectl -n gitlab-managed-apps logs cilium-XXXX cilium-monitor
|
||||
```
|
||||
|
||||
Drop logging can be disabled via `.gitlab/managed-apps/cilium/values.yaml`:
|
||||
You can disable the monitor log via `.gitlab/managed-apps/cilium/values.yaml`:
|
||||
|
||||
```yml
|
||||
agent:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Kubernetes
|
||||
class GenericSecret
|
||||
attr_reader :name, :data, :namespace_name
|
||||
|
||||
def initialize(name, data, namespace_name)
|
||||
@name = name
|
||||
@data = data
|
||||
@namespace_name = namespace_name
|
||||
end
|
||||
|
||||
def generate
|
||||
::Kubeclient::Resource.new(
|
||||
type: generic_secret_type,
|
||||
metadata: metadata,
|
||||
data: data
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generic_secret_type
|
||||
'Opaque'
|
||||
end
|
||||
|
||||
def metadata
|
||||
{
|
||||
name: name,
|
||||
namespace: namespace_name
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Kubernetes
|
||||
class TlsSecret
|
||||
attr_reader :name, :cert, :key, :namespace_name
|
||||
|
||||
def initialize(name, cert, key, namespace_name)
|
||||
@name = name
|
||||
@cert = cert
|
||||
@key = key
|
||||
@namespace_name = namespace_name
|
||||
end
|
||||
|
||||
def generate
|
||||
::Kubeclient::Resource.new(
|
||||
type: tls_secret_type,
|
||||
metadata: metadata,
|
||||
data: data
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tls_secret_type
|
||||
'kubernetes.io/tls'
|
||||
end
|
||||
|
||||
def metadata
|
||||
{
|
||||
name: name,
|
||||
namespace: namespace_name
|
||||
}
|
||||
end
|
||||
|
||||
def data
|
||||
{
|
||||
'tls.crt': Base64.strict_encode64(cert),
|
||||
'tls.key': Base64.strict_encode64(key)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -13,8 +13,8 @@ module Gitlab
|
|||
|
||||
def initialize(root, max_size: DEFAULT_MAX_SIZE, max_depth: DEFAULT_MAX_DEPTH)
|
||||
@root = root
|
||||
@max_size = max_size
|
||||
@max_depth = max_depth
|
||||
@max_size = max_size || DEFAULT_MAX_SIZE
|
||||
@max_depth = max_depth || DEFAULT_MAX_DEPTH
|
||||
@size = 0
|
||||
@depth = 0
|
||||
|
||||
|
|
|
|||
|
|
@ -8292,11 +8292,6 @@ msgstr ""
|
|||
msgid "Fetching licenses failed. You are not permitted to perform this action."
|
||||
msgstr ""
|
||||
|
||||
msgid "File"
|
||||
msgid_plural "Files"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "File Hooks"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -11931,10 +11926,10 @@ msgstr ""
|
|||
msgid "MergeRequest|Error loading full diff. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "MergeRequest|Filter files or search with %{modifier_key}+p"
|
||||
msgid "MergeRequest|No files found"
|
||||
msgstr ""
|
||||
|
||||
msgid "MergeRequest|No files found"
|
||||
msgid "MergeRequest|Search files (%{modifier_key}P)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Merged"
|
||||
|
|
@ -22185,6 +22180,9 @@ msgstr ""
|
|||
msgid "among other things"
|
||||
msgstr ""
|
||||
|
||||
msgid "and"
|
||||
msgstr ""
|
||||
|
||||
msgid "any-approver for the merge request already exists"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -22615,6 +22613,11 @@ msgstr ""
|
|||
msgid "failed to dismiss associated finding(id=%{finding_id}): %{message}"
|
||||
msgstr ""
|
||||
|
||||
msgid "file"
|
||||
msgid_plural "files"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "finding is not found or is already attached to a vulnerability"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ConfirmEmailWarning do
|
||||
before do
|
||||
stub_feature_flags(soft_email_confirmation: true)
|
||||
end
|
||||
|
||||
controller(ApplicationController) do
|
||||
# `described_class` is not available in this context
|
||||
include ConfirmEmailWarning
|
||||
|
|
|
|||
|
|
@ -17,5 +17,14 @@ describe Dashboard::SnippetsController do
|
|||
create(:personal_snippet, :public, author: user)
|
||||
end
|
||||
end
|
||||
|
||||
it 'fetches snippet counts via the snippet count service' do
|
||||
service = double(:count_service, execute: {})
|
||||
expect(Snippets::CountService)
|
||||
.to receive(:new).with(user, author: user)
|
||||
.and_return(service)
|
||||
|
||||
get :index
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,6 +27,15 @@ describe Projects::SnippetsController do
|
|||
end
|
||||
end
|
||||
|
||||
it 'fetches snippet counts via the snippet count service' do
|
||||
service = double(:count_service, execute: {})
|
||||
expect(Snippets::CountService)
|
||||
.to receive(:new).with(nil, project: project)
|
||||
.and_return(service)
|
||||
|
||||
get :index, params: { namespace_id: project.namespace, project_id: project }
|
||||
end
|
||||
|
||||
context 'when the project snippet is private' do
|
||||
let!(:project_snippet) { create(:project_snippet, :private, project: project, author: user) }
|
||||
|
||||
|
|
|
|||
|
|
@ -77,34 +77,14 @@ describe RegistrationsController do
|
|||
context 'when send_user_confirmation_email is true' do
|
||||
before do
|
||||
stub_application_setting(send_user_confirmation_email: true)
|
||||
allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
|
||||
end
|
||||
|
||||
context 'when soft email confirmation is not enabled' do
|
||||
before do
|
||||
stub_feature_flags(soft_email_confirmation: false)
|
||||
allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
|
||||
end
|
||||
it 'authenticates the user and sends a confirmation email' do
|
||||
post(:create, params: user_params)
|
||||
|
||||
it 'does not authenticate the user and sends a confirmation email' do
|
||||
post(:create, params: user_params)
|
||||
|
||||
expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
|
||||
expect(subject.current_user).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when soft email confirmation is enabled' do
|
||||
before do
|
||||
stub_feature_flags(soft_email_confirmation: true)
|
||||
allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
|
||||
end
|
||||
|
||||
it 'authenticates the user and sends a confirmation email' do
|
||||
post(:create, params: user_params)
|
||||
|
||||
expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
|
||||
expect(response).to redirect_to(dashboard_projects_path)
|
||||
end
|
||||
expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
|
||||
expect(response).to redirect_to(dashboard_projects_path)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -5,5 +5,41 @@ FactoryBot.define do
|
|||
pages_domain { create(:pages_domain) }
|
||||
knative { create(:clusters_applications_knative) }
|
||||
creator { create(:user) }
|
||||
|
||||
certificate do
|
||||
'-----BEGIN CERTIFICATE-----
|
||||
MIICGzCCAYSgAwIBAgIBATANBgkqhkiG9w0BAQUFADAbMRkwFwYDVQQDExB0ZXN0
|
||||
LWNlcnRpZmljYXRlMB4XDTE2MDIxMjE0MzIwMFoXDTIwMDQxMjE0MzIwMFowGzEZ
|
||||
MBcGA1UEAxMQdGVzdC1jZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
|
||||
gYkCgYEApL4J9L0ZxFJ1hI1LPIflAlAGvm6ZEvoT4qKU5Xf2JgU7/2geNR1qlNFa
|
||||
SvCc08Knupp5yTgmvyK/Xi09U0N82vvp4Zvr/diSc4A/RA6Mta6egLySNT438kdT
|
||||
nY2tR5feoTLwQpX0t4IMlwGQGT5h6Of2fKmDxzuwuyffcIHqLdsCAwEAAaNvMG0w
|
||||
DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUxl9WSxBprB0z0ibJs3rXEk0+95AwCwYD
|
||||
VR0PBAQDAgXgMBEGCWCGSAGG+EIBAQQEAwIGQDAeBglghkgBhvhCAQ0EERYPeGNh
|
||||
IGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAA4GBAGC4T8SlFHK0yPSa+idGLQFQ
|
||||
joZp2JHYvNlTPkRJ/J4TcXxBTJmArcQgTIuNoBtC+0A/SwdK4MfTCUY4vNWNdese
|
||||
5A4K65Nb7Oh1AdQieTBHNXXCdyFsva9/ScfQGEl7p55a52jOPs0StPd7g64uvjlg
|
||||
YHi2yesCrOvVXt+lgPTd
|
||||
-----END CERTIFICATE-----'
|
||||
end
|
||||
|
||||
key do
|
||||
'-----BEGIN PRIVATE KEY-----
|
||||
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKS+CfS9GcRSdYSN
|
||||
SzyH5QJQBr5umRL6E+KilOV39iYFO/9oHjUdapTRWkrwnNPCp7qaeck4Jr8iv14t
|
||||
PVNDfNr76eGb6/3YknOAP0QOjLWunoC8kjU+N/JHU52NrUeX3qEy8EKV9LeCDJcB
|
||||
kBk+Yejn9nypg8c7sLsn33CB6i3bAgMBAAECgYA2D26w80T7WZvazYr86BNMePpd
|
||||
j2mIAqx32KZHzt/lhh40J/SRtX9+Kl0Y7nBoRR5Ja9u/HkAIxNxLiUjwg9r6cpg/
|
||||
uITEF5nMt7lAk391BuI+7VOZZGbJDsq2ulPd6lO+C8Kq/PI/e4kXcIjeH6KwQsuR
|
||||
5vrXfBZ3sQfflaiN4QJBANBt8JY2LIGQF8o89qwUpRL5vbnKQ4IzZ5+TOl4RLR7O
|
||||
AQpJ81tGuINghO7aunctb6rrcKJrxmEH1whzComybrMCQQDKV49nOBudRBAIgG4K
|
||||
EnLzsRKISUHMZSJiYTYnablof8cKw1JaQduw7zgrUlLwnroSaAGX88+Jw1f5n2Lh
|
||||
Vlg5AkBDdUGnrDLtYBCDEQYZHblrkc7ZAeCllDOWjxUV+uMqlCv8A4Ey6omvY57C
|
||||
m6I8DkWVAQx8VPtozhvHjUw80rZHAkB55HWHAM3h13axKG0htCt7klhPsZHpx6MH
|
||||
EPjGlXIT+aW2XiPmK3ZlCDcWIenE+lmtbOpI159Wpk8BGXs/s/xBAkEAlAY3ymgx
|
||||
63BDJEwvOb2IaP8lDDxNsXx9XJNVvQbv5n15vNsLHbjslHfAhAbxnLQ1fLhUPqSi
|
||||
nNp/xedE1YxutQ==
|
||||
-----END PRIVATE KEY-----'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@ describe 'Dashboard snippets' do
|
|||
visit dashboard_snippets_path
|
||||
end
|
||||
|
||||
it_behaves_like 'tabs with counts' do
|
||||
let_it_be(:counts) { { all: '3', public: '1', private: '1', internal: '1' } }
|
||||
end
|
||||
|
||||
it 'contains all snippets of logged user' do
|
||||
expect(page).to have_selector('.snippet-row', count: 3)
|
||||
|
||||
|
|
|
|||
|
|
@ -153,24 +153,6 @@ describe 'Invites' do
|
|||
context 'email confirmation enabled' do
|
||||
let(:send_email_confirmation) { true }
|
||||
|
||||
context 'when soft email confirmation is not enabled' do
|
||||
before do
|
||||
# stub_feature_flags(soft_email_confirmation: false)
|
||||
allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
|
||||
end
|
||||
|
||||
it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
|
||||
fill_in_sign_up_form(new_user)
|
||||
confirm_email(new_user)
|
||||
fill_in_sign_in_form(new_user)
|
||||
|
||||
expect(current_path).to eq(root_path)
|
||||
expect(page).to have_content(project.full_name)
|
||||
visit group_path(group)
|
||||
expect(page).to have_content(group.full_name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when soft email confirmation is enabled' do
|
||||
before do
|
||||
allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
|
||||
|
|
@ -198,32 +180,14 @@ describe 'Invites' do
|
|||
context 'the user sign-up using a different email address' do
|
||||
let(:invite_email) { build_stubbed(:user).email }
|
||||
|
||||
context 'when soft email confirmation is not enabled' do
|
||||
before do
|
||||
stub_feature_flags(soft_email_confirmation: false)
|
||||
allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
|
||||
end
|
||||
|
||||
it 'signs up and redirects to the invitation page' do
|
||||
fill_in_sign_up_form(new_user)
|
||||
confirm_email(new_user)
|
||||
fill_in_sign_in_form(new_user)
|
||||
|
||||
expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
|
||||
end
|
||||
before do
|
||||
allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
|
||||
end
|
||||
|
||||
context 'when soft email confirmation is enabled' do
|
||||
before do
|
||||
stub_feature_flags(soft_email_confirmation: true)
|
||||
allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
|
||||
end
|
||||
it 'signs up and redirects to the invitation page' do
|
||||
fill_in_sign_up_form(new_user)
|
||||
|
||||
it 'signs up and redirects to the invitation page' do
|
||||
fill_in_sign_up_form(new_user)
|
||||
|
||||
expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
|
||||
end
|
||||
expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ describe 'Merge request > User sees versions', :js do
|
|||
expect(page).to have_content 'latest version'
|
||||
end
|
||||
|
||||
expect(page).to have_content '8 Files'
|
||||
expect(page).to have_content '8 files'
|
||||
end
|
||||
|
||||
it_behaves_like 'allows commenting',
|
||||
|
|
@ -84,7 +84,7 @@ describe 'Merge request > User sees versions', :js do
|
|||
end
|
||||
|
||||
it 'shows comments that were last relevant at that version' do
|
||||
expect(page).to have_content '5 Files'
|
||||
expect(page).to have_content '5 files'
|
||||
|
||||
position = Gitlab::Diff::Position.new(
|
||||
old_path: ".gitmodules",
|
||||
|
|
@ -128,12 +128,10 @@ describe 'Merge request > User sees versions', :js do
|
|||
diff_id: merge_request_diff3.id,
|
||||
start_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9'
|
||||
)
|
||||
expect(page).to have_content '4 Files'
|
||||
expect(page).to have_content '4 files'
|
||||
|
||||
additions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-addition')
|
||||
.ancestor('.diff-stats-group').text
|
||||
deletions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-deletion')
|
||||
.ancestor('.diff-stats-group').text
|
||||
additions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group .js-file-addition-line').text
|
||||
deletions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group .js-file-deletion-line').text
|
||||
|
||||
expect(additions_content).to eq '15'
|
||||
expect(deletions_content).to eq '6'
|
||||
|
|
@ -156,12 +154,10 @@ describe 'Merge request > User sees versions', :js do
|
|||
end
|
||||
|
||||
it 'show diff between new and old version' do
|
||||
additions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-addition')
|
||||
.ancestor('.diff-stats-group').text
|
||||
deletions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-deletion')
|
||||
.ancestor('.diff-stats-group').text
|
||||
additions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group .js-file-addition-line').text
|
||||
deletions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group .js-file-deletion-line').text
|
||||
|
||||
expect(page).to have_content '4 Files'
|
||||
expect(page).to have_content '4 files'
|
||||
expect(additions_content).to eq '15'
|
||||
expect(deletions_content).to eq '6'
|
||||
end
|
||||
|
|
@ -171,7 +167,7 @@ describe 'Merge request > User sees versions', :js do
|
|||
page.within '.mr-version-dropdown' do
|
||||
expect(page).to have_content 'latest version'
|
||||
end
|
||||
expect(page).to have_content '8 Files'
|
||||
expect(page).to have_content '8 files'
|
||||
end
|
||||
|
||||
it_behaves_like 'allows commenting',
|
||||
|
|
@ -197,7 +193,7 @@ describe 'Merge request > User sees versions', :js do
|
|||
find('.btn-default').click
|
||||
click_link 'version 1'
|
||||
end
|
||||
expect(page).to have_content '0 Files'
|
||||
expect(page).to have_content '0 files'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -223,7 +219,7 @@ describe 'Merge request > User sees versions', :js do
|
|||
expect(page).to have_content 'version 1'
|
||||
end
|
||||
|
||||
expect(page).to have_content '0 Files'
|
||||
expect(page).to have_content '0 files'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,16 @@ describe 'Projects > Snippets > User views snippets' do
|
|||
it_behaves_like 'paginated snippets'
|
||||
end
|
||||
|
||||
context 'filtering by visibility' do
|
||||
before do
|
||||
visit_project_snippets
|
||||
end
|
||||
|
||||
it_behaves_like 'tabs with counts' do
|
||||
let_it_be(:counts) { { all: '1', public: '0', private: '1', internal: '0' } }
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows snippets' do
|
||||
visit_project_snippets
|
||||
|
||||
|
|
|
|||
|
|
@ -797,7 +797,6 @@ describe 'Login' do
|
|||
|
||||
before do
|
||||
stub_application_setting(send_user_confirmation_email: true)
|
||||
stub_feature_flags(soft_email_confirmation: true)
|
||||
allow(User).to receive(:allow_unconfirmed_access_for).and_return grace_period
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -129,63 +129,29 @@ shared_examples 'Signup' do
|
|||
stub_application_setting(send_user_confirmation_email: true)
|
||||
end
|
||||
|
||||
context 'when soft email confirmation is not enabled' do
|
||||
before do
|
||||
stub_feature_flags(soft_email_confirmation: false)
|
||||
it 'creates the user account and sends a confirmation email' do
|
||||
visit new_user_registration_path
|
||||
|
||||
fill_in 'new_user_username', with: new_user.username
|
||||
fill_in 'new_user_email', with: new_user.email
|
||||
|
||||
if Gitlab::Experimentation.enabled?(:signup_flow)
|
||||
fill_in 'new_user_first_name', with: new_user.first_name
|
||||
fill_in 'new_user_last_name', with: new_user.last_name
|
||||
else
|
||||
fill_in 'new_user_name', with: new_user.name
|
||||
fill_in 'new_user_email_confirmation', with: new_user.email
|
||||
end
|
||||
|
||||
it 'creates the user account and sends a confirmation email' do
|
||||
visit new_user_registration_path
|
||||
fill_in 'new_user_password', with: new_user.password
|
||||
|
||||
fill_in 'new_user_username', with: new_user.username
|
||||
fill_in 'new_user_email', with: new_user.email
|
||||
expect { click_button 'Register' }.to change { User.count }.by(1)
|
||||
|
||||
if Gitlab::Experimentation.enabled?(:signup_flow)
|
||||
fill_in 'new_user_first_name', with: new_user.first_name
|
||||
fill_in 'new_user_last_name', with: new_user.last_name
|
||||
else
|
||||
fill_in 'new_user_name', with: new_user.name
|
||||
fill_in 'new_user_email_confirmation', with: new_user.email
|
||||
end
|
||||
|
||||
fill_in 'new_user_password', with: new_user.password
|
||||
|
||||
expect { click_button 'Register' }.to change { User.count }.by(1)
|
||||
|
||||
expect(current_path).to eq users_almost_there_path
|
||||
expect(page).to have_content('Please check your email to confirm your account')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when soft email confirmation is enabled' do
|
||||
before do
|
||||
stub_feature_flags(soft_email_confirmation: true)
|
||||
end
|
||||
|
||||
it 'creates the user account and sends a confirmation email' do
|
||||
visit new_user_registration_path
|
||||
|
||||
fill_in 'new_user_username', with: new_user.username
|
||||
fill_in 'new_user_email', with: new_user.email
|
||||
|
||||
if Gitlab::Experimentation.enabled?(:signup_flow)
|
||||
fill_in 'new_user_first_name', with: new_user.first_name
|
||||
fill_in 'new_user_last_name', with: new_user.last_name
|
||||
else
|
||||
fill_in 'new_user_name', with: new_user.name
|
||||
fill_in 'new_user_email_confirmation', with: new_user.email
|
||||
end
|
||||
|
||||
fill_in 'new_user_password', with: new_user.password
|
||||
|
||||
expect { click_button 'Register' }.to change { User.count }.by(1)
|
||||
|
||||
if Gitlab::Experimentation.enabled?(:signup_flow)
|
||||
expect(current_path).to eq users_sign_up_welcome_path
|
||||
else
|
||||
expect(current_path).to eq dashboard_projects_path
|
||||
expect(page).to have_content("Please check your email (#{new_user.email}) to verify that you own this address and unlock the power of CI/CD.")
|
||||
end
|
||||
if Gitlab::Experimentation.enabled?(:signup_flow)
|
||||
expect(current_path).to eq users_sign_up_welcome_path
|
||||
else
|
||||
expect(current_path).to eq dashboard_projects_path
|
||||
expect(page).to have_content("Please check your email (#{new_user.email}) to verify that you own this address and unlock the power of CI/CD.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -49,8 +49,7 @@ describe('CompareVersions', () => {
|
|||
|
||||
expect(treeListBtn.exists()).toBe(true);
|
||||
expect(treeListBtn.attributes('title')).toBe('Hide file browser');
|
||||
expect(treeListBtn.findAll(Icon).length).not.toBe(0);
|
||||
expect(treeListBtn.find(Icon).props('name')).toBe('collapse-left');
|
||||
expect(treeListBtn.find(Icon).props('name')).toBe('file-tree');
|
||||
});
|
||||
|
||||
it('should render comparison dropdowns with correct values', () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import DiffStats from '~/diffs/components/diff_stats.vue';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
describe('diff_stats', () => {
|
||||
it('does not render a group if diffFileLengths is empty', () => {
|
||||
|
|
@ -37,18 +37,18 @@ describe('diff_stats', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const findFileLine = name => wrapper.find(name);
|
||||
const findIcon = name =>
|
||||
wrapper
|
||||
.findAll(Icon)
|
||||
.filter(c => c.attributes('name') === name)
|
||||
.at(0).element.parentNode;
|
||||
|
||||
const additions = findIcon('file-addition');
|
||||
const deletions = findIcon('file-deletion');
|
||||
const additions = findFileLine('.js-file-addition-line');
|
||||
const deletions = findFileLine('.js-file-deletion-line');
|
||||
const filesChanged = findIcon('doc-code');
|
||||
|
||||
expect(additions.textContent).toContain('100');
|
||||
expect(deletions.textContent).toContain('200');
|
||||
expect(additions.text()).toBe('100');
|
||||
expect(deletions.text()).toBe('200');
|
||||
expect(filesChanged.textContent).toContain('300');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -37,14 +37,13 @@ describe('ErrorDetails', () => {
|
|||
projectPath: '/root/gitlab-test',
|
||||
listPath: '/error_tracking',
|
||||
issueUpdatePath: '/123',
|
||||
issueDetailsPath: '/123/details',
|
||||
issueStackTracePath: '/stacktrace',
|
||||
projectIssuesPath: '/test-project/issues/',
|
||||
csrfToken: 'fakeToken',
|
||||
},
|
||||
});
|
||||
wrapper.setData({
|
||||
GQLerror: {
|
||||
error: {
|
||||
id: 'gid://gitlab/Gitlab::ErrorTracking::DetailedError/129381',
|
||||
sentryId: 129381,
|
||||
title: 'Issue title',
|
||||
|
|
@ -59,7 +58,6 @@ describe('ErrorDetails', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
actions = {
|
||||
startPollingDetails: () => {},
|
||||
startPollingStacktrace: () => {},
|
||||
updateIgnoreStatus: jest.fn(),
|
||||
updateResolveStatus: jest.fn().mockResolvedValue({ closed_issue_iid: 1 }),
|
||||
|
|
@ -71,8 +69,6 @@ describe('ErrorDetails', () => {
|
|||
};
|
||||
|
||||
const state = {
|
||||
error: {},
|
||||
loading: true,
|
||||
stacktraceData: {},
|
||||
loadingStacktrace: true,
|
||||
};
|
||||
|
|
@ -93,7 +89,7 @@ describe('ErrorDetails', () => {
|
|||
$apollo: {
|
||||
query,
|
||||
queries: {
|
||||
GQLerror: {
|
||||
error: {
|
||||
loading: true,
|
||||
stopPolling: jest.fn(),
|
||||
},
|
||||
|
|
@ -122,9 +118,7 @@ describe('ErrorDetails', () => {
|
|||
|
||||
describe('Error details', () => {
|
||||
beforeEach(() => {
|
||||
store.state.details.loading = false;
|
||||
store.state.details.error.id = 1;
|
||||
mocks.$apollo.queries.GQLerror.loading = false;
|
||||
mocks.$apollo.queries.error.loading = false;
|
||||
mountComponent();
|
||||
});
|
||||
|
||||
|
|
@ -138,16 +132,22 @@ describe('ErrorDetails', () => {
|
|||
|
||||
describe('Badges', () => {
|
||||
it('should show language and error level badges', () => {
|
||||
store.state.details.error.tags = { level: 'error', logger: 'ruby' };
|
||||
mountComponent();
|
||||
wrapper.setData({
|
||||
error: {
|
||||
tags: { level: 'error', logger: 'ruby' },
|
||||
},
|
||||
});
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.findAll(GlBadge).length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT show the badge if the tag is not present', () => {
|
||||
store.state.details.error.tags = { level: 'error' };
|
||||
mountComponent();
|
||||
wrapper.setData({
|
||||
error: {
|
||||
tags: { level: 'error' },
|
||||
},
|
||||
});
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.findAll(GlBadge).length).toBe(1);
|
||||
});
|
||||
|
|
@ -156,8 +156,11 @@ describe('ErrorDetails', () => {
|
|||
it.each(Object.keys(severityLevel))(
|
||||
'should set correct severity level variant for %s badge',
|
||||
level => {
|
||||
store.state.details.error.tags = { level: severityLevel[level] };
|
||||
mountComponent();
|
||||
wrapper.setData({
|
||||
error: {
|
||||
tags: { level: severityLevel[level] },
|
||||
},
|
||||
});
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.find(GlBadge).attributes('variant')).toEqual(
|
||||
severityLevelVariant[severityLevel[level]],
|
||||
|
|
@ -167,8 +170,11 @@ describe('ErrorDetails', () => {
|
|||
);
|
||||
|
||||
it('should fallback for ERROR severityLevelVariant when severityLevel is unknown', () => {
|
||||
store.state.details.error.tags = { level: 'someNewErrorLevel' };
|
||||
mountComponent();
|
||||
wrapper.setData({
|
||||
error: {
|
||||
tags: { level: 'someNewErrorLevel' },
|
||||
},
|
||||
});
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.find(GlBadge).attributes('variant')).toEqual(
|
||||
severityLevelVariant[severityLevel.ERROR],
|
||||
|
|
@ -180,7 +186,6 @@ describe('ErrorDetails', () => {
|
|||
describe('Stacktrace', () => {
|
||||
it('should show stacktrace', () => {
|
||||
store.state.details.loadingStacktrace = false;
|
||||
mountComponent();
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
|
||||
expect(wrapper.find(Stacktrace).exists()).toBe(true);
|
||||
|
|
@ -190,9 +195,10 @@ describe('ErrorDetails', () => {
|
|||
it('should NOT show stacktrace if no entries', () => {
|
||||
store.state.details.loadingStacktrace = false;
|
||||
store.getters = { 'details/sentryUrl': () => 'sentry.io', 'details/stacktrace': () => [] };
|
||||
mountComponent();
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
|
||||
expect(wrapper.find(Stacktrace).exists()).toBe(false);
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
|
||||
expect(wrapper.find(Stacktrace).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -331,19 +337,18 @@ describe('ErrorDetails', () => {
|
|||
});
|
||||
|
||||
describe('GitLab issue link', () => {
|
||||
const gitlabIssue = 'https://gitlab.example.com/issues/1';
|
||||
const findGitLabLink = () => wrapper.find(`[href="${gitlabIssue}"]`);
|
||||
const gitlabIssuePath = 'https://gitlab.example.com/issues/1';
|
||||
const findGitLabLink = () => wrapper.find(`[href="${gitlabIssuePath}"]`);
|
||||
const findCreateIssueButton = () => wrapper.find('[data-qa-selector="create_issue_button"]');
|
||||
const findViewIssueButton = () => wrapper.find('[data-qa-selector="view_issue_button"]');
|
||||
|
||||
describe('is present', () => {
|
||||
beforeEach(() => {
|
||||
store.state.details.loading = false;
|
||||
store.state.details.error = {
|
||||
id: 1,
|
||||
gitlab_issue: gitlabIssue,
|
||||
};
|
||||
mountComponent();
|
||||
wrapper.setData({
|
||||
error: {
|
||||
gitlabIssuePath,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should display the View issue button', () => {
|
||||
|
|
@ -361,12 +366,11 @@ describe('ErrorDetails', () => {
|
|||
|
||||
describe('is not present', () => {
|
||||
beforeEach(() => {
|
||||
store.state.details.loading = false;
|
||||
store.state.details.error = {
|
||||
id: 1,
|
||||
gitlab_issue: null,
|
||||
};
|
||||
mountComponent();
|
||||
wrapper.setData({
|
||||
error: {
|
||||
gitlabIssuePath: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not display the View issue button', () => {
|
||||
|
|
@ -390,9 +394,9 @@ describe('ErrorDetails', () => {
|
|||
const findGitLabCommitLink = () => wrapper.find(`[href$="${gitlabCommitPath}"]`);
|
||||
|
||||
it('should display a link', () => {
|
||||
mocks.$apollo.queries.GQLerror.loading = false;
|
||||
mocks.$apollo.queries.error.loading = false;
|
||||
wrapper.setData({
|
||||
GQLerror: {
|
||||
error: {
|
||||
gitlabCommit,
|
||||
gitlabCommitPath,
|
||||
},
|
||||
|
|
@ -403,9 +407,9 @@ describe('ErrorDetails', () => {
|
|||
});
|
||||
|
||||
it('should not display a link', () => {
|
||||
mocks.$apollo.queries.GQLerror.loading = false;
|
||||
mocks.$apollo.queries.error.loading = false;
|
||||
wrapper.setData({
|
||||
GQLerror: {
|
||||
error: {
|
||||
gitlabCommit: null,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ describe('ErrorTrackingList', () => {
|
|||
expect(actions.updateStatus).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
{
|
||||
endpoint: '/project/test/-/error_tracking/3.json',
|
||||
endpoint: `/project/test/-/error_tracking/${errorsList[0].id}.json`,
|
||||
redirectUrl: '/error_tracking',
|
||||
status: 'ignored',
|
||||
},
|
||||
|
|
@ -267,7 +267,7 @@ describe('ErrorTrackingList', () => {
|
|||
expect(actions.updateStatus).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
{
|
||||
endpoint: '/project/test/-/error_tracking/3.json',
|
||||
endpoint: `/project/test/-/error_tracking/${errorsList[0].id}.json`,
|
||||
redirectUrl: '/error_tracking',
|
||||
status: 'resolved',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,53 +26,6 @@ describe('Sentry error details store actions', () => {
|
|||
}
|
||||
});
|
||||
|
||||
describe('startPollingDetails', () => {
|
||||
const endpoint = '123/details';
|
||||
it('should commit SET_ERROR with received response', done => {
|
||||
const payload = { error: { id: 1 } };
|
||||
mockedAdapter.onGet().reply(200, payload);
|
||||
testAction(
|
||||
actions.startPollingDetails,
|
||||
{ endpoint },
|
||||
{},
|
||||
[
|
||||
{ type: types.SET_ERROR, payload: payload.error },
|
||||
{ type: types.SET_LOADING, payload: false },
|
||||
],
|
||||
[],
|
||||
() => {
|
||||
done();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should show flash on API error', done => {
|
||||
mockedAdapter.onGet().reply(400);
|
||||
|
||||
testAction(
|
||||
actions.startPollingDetails,
|
||||
{ endpoint },
|
||||
{},
|
||||
[{ type: types.SET_LOADING, payload: false }],
|
||||
[],
|
||||
() => {
|
||||
expect(createFlash).toHaveBeenCalledTimes(1);
|
||||
done();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should not restart polling when receiving an empty 204 response', done => {
|
||||
mockedRestart = jest.spyOn(Poll.prototype, 'restart');
|
||||
mockedAdapter.onGet().reply(204);
|
||||
|
||||
testAction(actions.startPollingDetails, { endpoint }, {}, [], [], () => {
|
||||
expect(mockedRestart).toHaveBeenCalledTimes(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('startPollingStacktrace', () => {
|
||||
const endpoint = '123/stacktrace';
|
||||
it('should commit SET_ERROR with received response', done => {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export const utilsMockData = [
|
|||
content: [
|
||||
{
|
||||
text:
|
||||
'Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33',
|
||||
'Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.34',
|
||||
},
|
||||
],
|
||||
section: 'prepare-executor',
|
||||
|
|
|
|||
|
|
@ -83,7 +83,6 @@ describe Projects::ErrorTrackingHelper do
|
|||
describe '#error_details_data' do
|
||||
let(:issue_id) { 1234 }
|
||||
let(:route_params) { [project.owner, project, issue_id, { format: :json }] }
|
||||
let(:details_path) { details_namespace_project_error_tracking_index_path(*route_params) }
|
||||
let(:project_path) { project.full_path }
|
||||
let(:stack_trace_path) { stack_trace_namespace_project_error_tracking_index_path(*route_params) }
|
||||
let(:issues_path) { project_issues_path(project) }
|
||||
|
|
@ -98,10 +97,6 @@ describe Projects::ErrorTrackingHelper do
|
|||
expect(result['project-path']).to eq project_path
|
||||
end
|
||||
|
||||
it 'returns the correct details path' do
|
||||
expect(result['issue-details-path']).to eq details_path
|
||||
end
|
||||
|
||||
it 'returns the correct stack trace path' do
|
||||
expect(result['issue-stack-trace-path']).to eq stack_trace_path
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Kubernetes::GenericSecret do
|
||||
let(:secret) { described_class.new(name, data, namespace) }
|
||||
let(:name) { 'example-name' }
|
||||
let(:data) { 'example-data' }
|
||||
let(:namespace) { 'example-namespace' }
|
||||
|
||||
describe '#generate' do
|
||||
subject { secret.generate }
|
||||
|
||||
let(:resource) do
|
||||
::Kubeclient::Resource.new(
|
||||
type: 'Opaque',
|
||||
metadata: { name: name, namespace: namespace },
|
||||
data: data
|
||||
)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(resource) }
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Kubernetes::TlsSecret do
|
||||
let(:secret) { described_class.new(name, cert, key, namespace) }
|
||||
let(:name) { 'example-name' }
|
||||
let(:cert) { 'example-cert' }
|
||||
let(:key) { 'example-key' }
|
||||
let(:namespace) { 'example-namespace' }
|
||||
|
||||
let(:data) do
|
||||
{
|
||||
'tls.crt': Base64.strict_encode64(cert),
|
||||
'tls.key': Base64.strict_encode64(key)
|
||||
}
|
||||
end
|
||||
|
||||
describe '#generate' do
|
||||
subject { secret.generate }
|
||||
|
||||
let(:resource) do
|
||||
::Kubeclient::Resource.new(
|
||||
type: 'kubernetes.io/tls',
|
||||
metadata: { name: name, namespace: namespace },
|
||||
data: data
|
||||
)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(resource) }
|
||||
end
|
||||
end
|
||||
|
|
@ -17,29 +17,45 @@ describe Gitlab::Utils::DeepSize do
|
|||
|
||||
let(:max_size) { 1.kilobyte }
|
||||
let(:max_depth) { 10 }
|
||||
let(:deep_size) { described_class.new(data, max_size: max_size, max_depth: max_depth) }
|
||||
|
||||
describe '#evaluate' do
|
||||
context 'when data within size and depth limits' do
|
||||
it 'returns true' do
|
||||
expect(deep_size).to be_valid
|
||||
subject(:deep_size) { described_class.new(data, max_size: max_size, max_depth: max_depth) }
|
||||
|
||||
it { expect(described_class::DEFAULT_MAX_SIZE).to eq(1.megabyte) }
|
||||
it { expect(described_class::DEFAULT_MAX_DEPTH).to eq(100) }
|
||||
|
||||
describe '#initialize' do
|
||||
context 'when max_size is nil' do
|
||||
let(:max_size) { nil }
|
||||
|
||||
it 'sets max_size to DEFAULT_MAX_SIZE' do
|
||||
expect(subject.instance_variable_get(:@max_size)).to eq(described_class::DEFAULT_MAX_SIZE)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when max_depth is nil' do
|
||||
let(:max_depth) { nil }
|
||||
|
||||
it 'sets max_depth to DEFAULT_MAX_DEPTH' do
|
||||
expect(subject.instance_variable_get(:@max_depth)).to eq(described_class::DEFAULT_MAX_DEPTH)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#valid?' do
|
||||
context 'when data within size and depth limits' do
|
||||
it { is_expected.to be_valid }
|
||||
end
|
||||
|
||||
context 'when data not within size limit' do
|
||||
let(:max_size) { 200.bytes }
|
||||
|
||||
it 'returns false' do
|
||||
expect(deep_size).not_to be_valid
|
||||
end
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
||||
context 'when data not within depth limit' do
|
||||
let(:max_depth) { 2 }
|
||||
|
||||
it 'returns false' do
|
||||
expect(deep_size).not_to be_valid
|
||||
end
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -50,4 +50,12 @@ describe ::Serverless::DomainCluster do
|
|||
describe 'domain' do
|
||||
it { is_expected.to respond_to(:domain) }
|
||||
end
|
||||
|
||||
describe 'certificate' do
|
||||
it { is_expected.to respond_to(:certificate) }
|
||||
end
|
||||
|
||||
describe 'key' do
|
||||
it { is_expected.to respond_to(:key) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,197 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Clusters::Kubernetes::ConfigureIstioIngressService, '#execute' do
|
||||
include KubernetesHelpers
|
||||
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:api_url) { 'https://kubernetes.example.com' }
|
||||
let(:project) { cluster.project }
|
||||
let(:environment) { create(:environment, project: project) }
|
||||
let(:cluster_project) { cluster.cluster_project }
|
||||
let(:namespace) { "#{project.name}-#{project.id}-#{environment.slug}" }
|
||||
let(:kubeclient) { cluster.kubeclient }
|
||||
|
||||
subject do
|
||||
described_class.new(
|
||||
cluster: cluster
|
||||
).execute
|
||||
end
|
||||
|
||||
before do
|
||||
stub_kubeclient_discover_istio(api_url)
|
||||
stub_kubeclient_create_secret(api_url, namespace: namespace)
|
||||
stub_kubeclient_put_secret(api_url, "#{namespace}-token", namespace: namespace)
|
||||
|
||||
stub_kubeclient_get_secret(
|
||||
api_url,
|
||||
{
|
||||
metadata_name: "#{namespace}-token",
|
||||
token: Base64.encode64('sample-token'),
|
||||
namespace: namespace
|
||||
}
|
||||
)
|
||||
|
||||
stub_kubeclient_get_secret(
|
||||
api_url,
|
||||
{
|
||||
metadata_name: 'istio-ingressgateway-ca-certs',
|
||||
namespace: 'istio-system'
|
||||
}
|
||||
)
|
||||
|
||||
stub_kubeclient_get_secret(
|
||||
api_url,
|
||||
{
|
||||
metadata_name: 'istio-ingressgateway-certs',
|
||||
namespace: 'istio-system'
|
||||
}
|
||||
)
|
||||
|
||||
stub_kubeclient_put_secret(api_url, 'istio-ingressgateway-ca-certs', namespace: 'istio-system')
|
||||
stub_kubeclient_put_secret(api_url, 'istio-ingressgateway-certs', namespace: 'istio-system')
|
||||
stub_kubeclient_get_gateway(api_url, 'knative-ingress-gateway', namespace: 'knative-serving')
|
||||
stub_kubeclient_put_gateway(api_url, 'knative-ingress-gateway', namespace: 'knative-serving')
|
||||
end
|
||||
|
||||
context 'without a serverless_domain_cluster' do
|
||||
it 'configures gateway to use PASSTHROUGH' do
|
||||
subject
|
||||
|
||||
expect(WebMock).to have_requested(:put, api_url + '/apis/networking.istio.io/v1alpha3/namespaces/knative-serving/gateways/knative-ingress-gateway').with(
|
||||
body: hash_including(
|
||||
apiVersion: "networking.istio.io/v1alpha3",
|
||||
kind: "Gateway",
|
||||
metadata: {
|
||||
generation: 1,
|
||||
labels: {
|
||||
"networking.knative.dev/ingress-provider" => "istio",
|
||||
"serving.knative.dev/release" => "v0.7.0"
|
||||
},
|
||||
name: "knative-ingress-gateway",
|
||||
namespace: "knative-serving",
|
||||
selfLink: "/apis/networking.istio.io/v1alpha3/namespaces/knative-serving/gateways/knative-ingress-gateway"
|
||||
},
|
||||
spec: {
|
||||
selector: {
|
||||
istio: "ingressgateway"
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
hosts: ["*"],
|
||||
port: {
|
||||
name: "http",
|
||||
number: 80,
|
||||
protocol: "HTTP"
|
||||
}
|
||||
},
|
||||
{
|
||||
hosts: ["*"],
|
||||
port: {
|
||||
name: "https",
|
||||
number: 443,
|
||||
protocol: "HTTPS"
|
||||
},
|
||||
tls: {
|
||||
mode: "PASSTHROUGH"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a serverless_domain_cluster' do
|
||||
let(:serverless_domain_cluster) { create(:serverless_domain_cluster) }
|
||||
let(:certificate) { OpenSSL::X509::Certificate.new(serverless_domain_cluster.certificate) }
|
||||
|
||||
before do
|
||||
cluster.application_knative = serverless_domain_cluster.knative
|
||||
end
|
||||
|
||||
it 'configures certificates' do
|
||||
subject
|
||||
|
||||
expect(serverless_domain_cluster.reload.key).not_to be_blank
|
||||
expect(serverless_domain_cluster.reload.certificate).not_to be_blank
|
||||
|
||||
expect(certificate.subject.to_s).to include(serverless_domain_cluster.knative.hostname)
|
||||
|
||||
expect(certificate.not_before).to be_within(1.minute).of(Time.now)
|
||||
expect(certificate.not_after).to be_within(1.minute).of(Time.now + 1000.years)
|
||||
|
||||
expect(WebMock).to have_requested(:put, api_url + '/api/v1/namespaces/istio-system/secrets/istio-ingressgateway-ca-certs').with(
|
||||
body: hash_including(
|
||||
metadata: {
|
||||
name: 'istio-ingressgateway-ca-certs',
|
||||
namespace: 'istio-system'
|
||||
},
|
||||
type: 'Opaque'
|
||||
)
|
||||
)
|
||||
|
||||
expect(WebMock).to have_requested(:put, api_url + '/api/v1/namespaces/istio-system/secrets/istio-ingressgateway-certs').with(
|
||||
body: hash_including(
|
||||
metadata: {
|
||||
name: 'istio-ingressgateway-certs',
|
||||
namespace: 'istio-system'
|
||||
},
|
||||
type: 'kubernetes.io/tls'
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
it 'configures gateway to use MUTUAL' do
|
||||
subject
|
||||
|
||||
expect(WebMock).to have_requested(:put, api_url + '/apis/networking.istio.io/v1alpha3/namespaces/knative-serving/gateways/knative-ingress-gateway').with(
|
||||
body: {
|
||||
apiVersion: "networking.istio.io/v1alpha3",
|
||||
kind: "Gateway",
|
||||
metadata: {
|
||||
generation: 1,
|
||||
labels: {
|
||||
"networking.knative.dev/ingress-provider" => "istio",
|
||||
"serving.knative.dev/release" => "v0.7.0"
|
||||
},
|
||||
name: "knative-ingress-gateway",
|
||||
namespace: "knative-serving",
|
||||
selfLink: "/apis/networking.istio.io/v1alpha3/namespaces/knative-serving/gateways/knative-ingress-gateway"
|
||||
},
|
||||
spec: {
|
||||
selector: {
|
||||
istio: "ingressgateway"
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
hosts: ["*"],
|
||||
port: {
|
||||
name: "http",
|
||||
number: 80,
|
||||
protocol: "HTTP"
|
||||
}
|
||||
},
|
||||
{
|
||||
hosts: ["*"],
|
||||
port: {
|
||||
name: "https",
|
||||
number: 443,
|
||||
protocol: "HTTPS"
|
||||
},
|
||||
tls: {
|
||||
mode: "MUTUAL",
|
||||
privateKey: "/etc/istio/ingressgateway-certs/tls.key",
|
||||
serverCertificate: "/etc/istio/ingressgateway-certs/tls.crt",
|
||||
caCertificates: "/etc/istio/ingressgateway-ca-certs/cert.pem"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Snippets::CountService do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
|
||||
describe '#new' do
|
||||
it 'raises an error if no author or project' do
|
||||
expect { described_class.new(user) }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'uses the SnippetsFinder to scope snippets by user' do
|
||||
expect(SnippetsFinder)
|
||||
.to receive(:new)
|
||||
.with(user, author: user, project: nil)
|
||||
|
||||
described_class.new(user, author: user)
|
||||
end
|
||||
|
||||
it 'allows scoping to project' do
|
||||
expect(SnippetsFinder)
|
||||
.to receive(:new)
|
||||
.with(user, author: nil, project: project)
|
||||
|
||||
described_class.new(user, project: project)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
subject { described_class.new(user, author: user).execute }
|
||||
|
||||
it 'returns a hash of counts' do
|
||||
expect(subject).to match({
|
||||
are_public: 0,
|
||||
are_internal: 0,
|
||||
are_private: 0,
|
||||
are_public_or_internal: 0,
|
||||
total: 0
|
||||
})
|
||||
end
|
||||
|
||||
it 'only counts snippets the user has access to' do
|
||||
create(:personal_snippet, :private, author: user)
|
||||
create(:project_snippet, :private, author: user)
|
||||
create(:project_snippet, :private, author: create(:user))
|
||||
|
||||
expect(subject).to match({
|
||||
are_public: 0,
|
||||
are_internal: 0,
|
||||
are_private: 1,
|
||||
are_public_or_internal: 0,
|
||||
total: 1
|
||||
})
|
||||
end
|
||||
|
||||
it 'returns an empty hash if select returns nil' do
|
||||
allow_next_instance_of(described_class) do |instance|
|
||||
allow(instance).to receive(:snippet_counts).and_return(nil)
|
||||
end
|
||||
|
||||
expect(subject).to match({})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -18,3 +18,35 @@ RSpec.shared_examples 'paginated snippets' do |remote: false|
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'tabs with counts' do
|
||||
let(:tabs) { page.all('.snippet-scope-menu li') }
|
||||
|
||||
it 'shows a tab for All snippets and count' do
|
||||
tab = tabs[0]
|
||||
|
||||
expect(tab.text).to include('All')
|
||||
expect(tab.find('.badge').text).to eq(counts[:all])
|
||||
end
|
||||
|
||||
it 'shows a tab for Private snippets and count' do
|
||||
tab = tabs[1]
|
||||
|
||||
expect(tab.text).to include('Private')
|
||||
expect(tab.find('.badge').text).to eq(counts[:private])
|
||||
end
|
||||
|
||||
it 'shows a tab for Internal snippets and count' do
|
||||
tab = tabs[2]
|
||||
|
||||
expect(tab.text).to include('Internal')
|
||||
expect(tab.find('.badge').text).to eq(counts[:internal])
|
||||
end
|
||||
|
||||
it 'shows a tab for Public snippets and count' do
|
||||
tab = tabs[3]
|
||||
|
||||
expect(tab.text).to include('Public')
|
||||
expect(tab.find('.badge').text).to eq(counts[:public])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe ClusterConfigureIstioWorker do
|
||||
describe '#perform' do
|
||||
shared_examples 'configure istio service' do
|
||||
it 'configures istio' do
|
||||
expect_any_instance_of(Clusters::Kubernetes::ConfigureIstioIngressService).to receive(:execute)
|
||||
|
||||
described_class.new.perform(cluster.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provider type is gcp' do
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
|
||||
it_behaves_like 'configure istio service'
|
||||
end
|
||||
|
||||
context 'when provider type is aws' do
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_aws) }
|
||||
|
||||
it_behaves_like 'configure istio service'
|
||||
end
|
||||
|
||||
context 'when provider type is user' do
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_user) }
|
||||
|
||||
it_behaves_like 'configure istio service'
|
||||
end
|
||||
|
||||
context 'when cluster does not exist' do
|
||||
it 'does not provision a cluster' do
|
||||
expect_any_instance_of(Clusters::Kubernetes::ConfigureIstioIngressService).not_to receive(:execute)
|
||||
|
||||
described_class.new.perform(123)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue