Commit 26384c9a authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 79cbe31b
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: stages:
- sync - sync
......
...@@ -78,7 +78,7 @@ ...@@ -78,7 +78,7 @@
- .default-retry - .default-retry
- .default-before_script - .default-before_script
- .assets-compile-cache - .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 stage: prepare
services: services:
- docker:19.03.0-dind - docker:19.03.0-dind
......
...@@ -203,7 +203,7 @@ ...@@ -203,7 +203,7 @@
- name: redis:alpine - name: redis:alpine
.use-pg10: .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: services:
- name: postgres:10.9 - name: postgres:10.9
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
...@@ -217,7 +217,7 @@ ...@@ -217,7 +217,7 @@
- name: elasticsearch:6.4.2 - name: elasticsearch:6.4.2
.use-pg10-ee: .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: services:
- name: postgres:10.9 - name: postgres:10.9
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
......
...@@ -374,7 +374,7 @@ export default { ...@@ -374,7 +374,7 @@ export default {
<div <div
:data-can-create-note="getNoteableData.current_user.can_create_note" :data-can-create-note="getNoteableData.current_user.can_create_note"
class="files d-flex prepend-top-default" class="files d-flex"
> >
<div <div
v-show="showTreeList" v-show="showTreeList"
......
<script> <script>
/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui'; import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
...@@ -63,9 +62,6 @@ export default { ...@@ -63,9 +62,6 @@ export default {
showDropdowns() { showDropdowns() {
return !this.commit && this.mergeRequestDiffs.length; return !this.commit && this.mergeRequestDiffs.length;
}, },
fileTreeIcon() {
return this.showTreeList ? 'collapse-left' : 'expand-left';
},
toggleFileBrowserTitle() { toggleFileBrowserTitle() {
return this.showTreeList ? __('Hide file browser') : __('Show file browser'); return this.showTreeList ? __('Hide file browser') : __('Show file browser');
}, },
...@@ -91,7 +87,7 @@ export default { ...@@ -91,7 +87,7 @@ export default {
</script> </script>
<template> <template>
<div class="mr-version-controls border-top border-bottom"> <div class="mr-version-controls border-top">
<div <div
class="mr-version-menus-container content-block" class="mr-version-menus-container content-block"
:class="{ :class="{
...@@ -108,17 +104,17 @@ export default { ...@@ -108,17 +104,17 @@ export default {
:title="toggleFileBrowserTitle" :title="toggleFileBrowserTitle"
@click="toggleShowTreeList" @click="toggleShowTreeList"
> >
<icon :name="fileTreeIcon" /> <icon name="file-tree" />
</button> </button>
<div v-if="showDropdowns" class="d-flex align-items-center compare-versions-container"> <div v-if="showDropdowns" class="d-flex align-items-center compare-versions-container">
Changes between {{ __('Compare') }}
<compare-versions-dropdown <compare-versions-dropdown
:other-versions="mergeRequestDiffs" :other-versions="mergeRequestDiffs"
:merge-request-version="mergeRequestDiff" :merge-request-version="mergeRequestDiff"
:show-commit-count="true" :show-commit-count="true"
class="mr-version-dropdown" class="mr-version-dropdown"
/> />
and {{ __('and') }}
<compare-versions-dropdown <compare-versions-dropdown
:other-versions="comparableDiffs" :other-versions="comparableDiffs"
:base-version-path="baseVersionPath" :base-version-path="baseVersionPath"
......
...@@ -224,7 +224,7 @@ export default { ...@@ -224,7 +224,7 @@ export default {
<div <div
v-if="!diffFile.submodule && addMergeRequestButtons" 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" /> <diff-stats :added-lines="diffFile.added_lines" :removed-lines="diffFile.removed_lines" />
<div class="btn-group" role="group"> <div class="btn-group" role="group">
......
...@@ -22,7 +22,7 @@ export default { ...@@ -22,7 +22,7 @@ export default {
}, },
computed: { computed: {
filesText() { filesText() {
return n__('File', 'Files', this.diffFilesLength); return n__('file', 'files', this.diffFilesLength);
}, },
isCompareVersionsHeader() { isCompareVersionsHeader() {
return Boolean(this.diffFilesLength); return Boolean(this.diffFilesLength);
...@@ -44,13 +44,21 @@ export default { ...@@ -44,13 +44,21 @@ export default {
> >
<div v-if="hasDiffFiles" class="diff-stats-group"> <div v-if="hasDiffFiles" class="diff-stats-group">
<icon name="doc-code" class="diff-stats-icon text-secondary" /> <icon name="doc-code" class="diff-stats-icon text-secondary" />
<strong>{{ diffFilesLength }} {{ filesText }}</strong> <span class="text-secondary bold">{{ diffFilesLength }} {{ filesText }}</span>
</div> </div>
<div class="diff-stats-group cgreen"> <div
<icon name="file-addition" class="diff-stats-icon" /> <strong>{{ addedLines }}</strong> 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>
<div class="diff-stats-group cred"> <div
<icon name="file-deletion" class="diff-stats-icon" /> <strong>{{ removedLines }}</strong> 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>
</div> </div>
</template> </template>
...@@ -58,8 +58,8 @@ export default { ...@@ -58,8 +58,8 @@ export default {
this.search = ''; this.search = '';
}, },
}, },
searchPlaceholder: sprintf(s__('MergeRequest|Filter files or search with %{modifier_key}+p'), { searchPlaceholder: sprintf(s__('MergeRequest|Search files (%{modifier_key}P)'), {
modifier_key: /Mac/i.test(navigator.userAgent) ? 'cmd' : 'ctrl', modifier_key: /Mac/i.test(navigator.userAgent) ? '' : 'Ctrl+',
}), }),
}; };
</script> </script>
......
...@@ -54,10 +54,6 @@ export default { ...@@ -54,10 +54,6 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
issueDetailsPath: {
type: String,
required: true,
},
issueStackTracePath: { issueStackTracePath: {
type: String, type: String,
required: true, required: true,
...@@ -72,7 +68,7 @@ export default { ...@@ -72,7 +68,7 @@ export default {
}, },
}, },
apollo: { apollo: {
GQLerror: { error: {
query, query,
variables() { variables() {
return { return {
...@@ -81,19 +77,19 @@ export default { ...@@ -81,19 +77,19 @@ export default {
}; };
}, },
pollInterval: 2000, pollInterval: 2000,
update: data => data.project.sentryDetailedError, update: data => data.project.sentryErrors.detailedError,
error: () => createFlash(__('Failed to load error details from Sentry.')), error: () => createFlash(__('Failed to load error details from Sentry.')),
result(res) { result(res) {
if (res.data.project?.sentryDetailedError) { if (res.data.project?.sentryErrors?.detailedError) {
this.$apollo.queries.GQLerror.stopPolling(); this.$apollo.queries.error.stopPolling();
this.setStatus(this.GQLerror.status); this.setStatus(this.error.status);
} }
}, },
}, },
}, },
data() { data() {
return { return {
GQLerror: null, error: null,
issueCreationInProgress: false, issueCreationInProgress: false,
isAlertVisible: false, isAlertVisible: false,
closedIssueId: null, closedIssueId: null,
...@@ -101,8 +97,6 @@ export default { ...@@ -101,8 +97,6 @@ export default {
}, },
computed: { computed: {
...mapState('details', [ ...mapState('details', [
'error',
'loading',
'loadingStacktrace', 'loadingStacktrace',
'stacktraceData', 'stacktraceData',
'updatingResolveStatus', 'updatingResolveStatus',
...@@ -114,28 +108,23 @@ export default { ...@@ -114,28 +108,23 @@ export default {
return sprintf( return sprintf(
__('Reported %{timeAgo} by %{reportedBy}'), __('Reported %{timeAgo} by %{reportedBy}'),
{ {
reportedBy: `<strong>${this.GQLerror.culprit}</strong>`, reportedBy: `<strong>${this.error.culprit}</strong>`,
timeAgo: this.timeFormatted(this.stacktraceData.date_received), timeAgo: this.timeFormatted(this.stacktraceData.date_received),
}, },
false, false,
); );
}, },
firstReleaseLink() { firstReleaseLink() {
return `${this.error.external_base_url}/releases/${this.GQLerror.firstReleaseShortVersion}`; return `${this.error.externalBaseUrl}/releases/${this.error.firstReleaseShortVersion}`;
}, },
lastReleaseLink() { lastReleaseLink() {
return `${this.error.external_base_url}releases/${this.GQLerror.lastReleaseShortVersion}`; return `${this.error.externalBaseUrl}/releases/${this.error.lastReleaseShortVersion}`;
},
showDetails() {
return Boolean(
!this.loading && !this.$apollo.queries.GQLerror.loading && this.error && this.GQLerror,
);
}, },
showStacktrace() { showStacktrace() {
return Boolean(!this.loadingStacktrace && this.stacktrace && this.stacktrace.length); return Boolean(this.stacktrace?.length);
}, },
issueTitle() { issueTitle() {
return this.GQLerror.title; return this.error.title;
}, },
issueDescription() { issueDescription() {
return sprintf( return sprintf(
...@@ -144,13 +133,13 @@ export default { ...@@ -144,13 +133,13 @@ export default {
), ),
{ {
description: '# Error Details:\n', description: '# Error Details:\n',
errorUrl: `${this.GQLerror.externalUrl}\n`, errorUrl: `${this.error.externalUrl}\n`,
firstSeen: `\n${this.GQLerror.firstSeen}\n`, firstSeen: `\n${this.error.firstSeen}\n`,
lastSeen: `${this.GQLerror.lastSeen}\n`, lastSeen: `${this.error.lastSeen}\n`,
countLabel: n__('- Event', '- Events', this.GQLerror.count), countLabel: n__('- Event', '- Events', this.error.count),
count: `${this.GQLerror.count}\n`, count: `${this.error.count}\n`,
userCountLabel: n__('- User', '- Users', this.GQLerror.userCount), userCountLabel: n__('- User', '- Users', this.error.userCount),
userCount: `${this.GQLerror.userCount}\n`, userCount: `${this.error.userCount}\n`,
}, },
false, false,
); );
...@@ -171,12 +160,10 @@ export default { ...@@ -171,12 +160,10 @@ export default {
}, },
}, },
mounted() { mounted() {
this.startPollingDetails(this.issueDetailsPath);
this.startPollingStacktrace(this.issueStackTracePath); this.startPollingStacktrace(this.issueStackTracePath);
}, },
methods: { methods: {
...mapActions('details', [ ...mapActions('details', [
'startPollingDetails',
'startPollingStacktrace', 'startPollingStacktrace',
'updateStatus', 'updateStatus',
'setStatus', 'setStatus',
...@@ -214,10 +201,10 @@ export default { ...@@ -214,10 +201,10 @@ export default {
<template> <template>
<div> <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" /> <gl-loading-icon :size="3" />
</div> </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-alert v-if="isAlertVisible" @dismiss="isAlertVisible = false">
<gl-sprintf <gl-sprintf
:message=" :message="
...@@ -232,7 +219,7 @@ export default { ...@@ -232,7 +219,7 @@ export default {
<div class="top-area align-items-center justify-content-between py-3"> <div class="top-area align-items-center justify-content-between py-3">
<span v-if="!loadingStacktrace && stacktrace" v-html="reported"></span> <span v-if="!loadingStacktrace && stacktrace" v-html="reported"></span>
<div class="d-inline-flex"> <div class="d-inline-flex ml-lg-auto">
<loading-button <loading-button
:label="ignoreBtnLabel" :label="ignoreBtnLabel"
:loading="updatingIgnoreStatus" :loading="updatingIgnoreStatus"
...@@ -247,10 +234,10 @@ export default { ...@@ -247,10 +234,10 @@ export default {
@click="onResolveStatusUpdate" @click="onResolveStatusUpdate"
/> />
<gl-button <gl-button
v-if="error.gitlab_issue" v-if="error.gitlabIssuePath"
class="ml-2" class="ml-2"
data-qa-selector="view_issue_button" data-qa-selector="view_issue_button"
:href="error.gitlab_issue" :href="error.gitlabIssuePath"
variant="success" variant="success"
> >
{{ __('View issue') }} {{ __('View issue') }}
...@@ -264,13 +251,13 @@ export default { ...@@ -264,13 +251,13 @@ export default {
<gl-form-input class="hidden" name="issue[title]" :value="issueTitle" /> <gl-form-input class="hidden" name="issue[title]" :value="issueTitle" />
<input name="issue[description]" :value="issueDescription" type="hidden" /> <input name="issue[description]" :value="issueDescription" type="hidden" />
<gl-form-input <gl-form-input
:value="GQLerror.sentryId" :value="error.sentryId"
class="hidden" class="hidden"
name="issue[sentry_issue_attributes][sentry_issue_identifier]" name="issue[sentry_issue_attributes][sentry_issue_identifier]"
/> />
<gl-form-input :value="csrfToken" class="hidden" name="authenticity_token" /> <gl-form-input :value="csrfToken" class="hidden" name="authenticity_token" />
<loading-button <loading-button
v-if="!error.gitlab_issue" v-if="!error.gitlabIssuePath"
class="btn-success" class="btn-success"
:label="__('Create issue')" :label="__('Create issue')"
:loading="issueCreationInProgress" :loading="issueCreationInProgress"
...@@ -281,8 +268,8 @@ export default { ...@@ -281,8 +268,8 @@ export default {
</div> </div>
</div> </div>
<div> <div>
<tooltip-on-truncate :title="GQLerror.title" truncate-target="child" placement="top"> <tooltip-on-truncate :title="error.title" truncate-target="child" placement="top">
<h2 class="text-truncate">{{ GQLerror.title }}</h2> <h2 class="text-truncate">{{ error.title }}</h2>
</tooltip-on-truncate> </tooltip-on-truncate>
<template v-if="error.tags"> <template v-if="error.tags">
<gl-badge <gl-badge
...@@ -297,53 +284,51 @@ export default { ...@@ -297,53 +284,51 @@ export default {
</gl-badge> </gl-badge>
</template> </template>
<ul> <ul>
<li v-if="GQLerror.gitlabCommit"> <li v-if="error.gitlabCommit">
<strong class="bold">{{ __('GitLab commit') }}:</strong> <strong class="bold">{{ __('GitLab commit') }}:</strong>
<gl-link :href="GQLerror.gitlabCommitPath"> <gl-link :href="error.gitlabCommitPath">
<span>{{ GQLerror.gitlabCommit.substr(0, 10) }}</span> <span>{{ error.gitlabCommit.substr(0, 10) }}</span>
</gl-link> </gl-link>
</li> </li>
<li v-if="error.gitlab_issue"> <li v-if="error.gitlabIssuePath">
<strong class="bold">{{ __('GitLab Issue') }}:</strong> <strong class="bold">{{ __('GitLab Issue') }}:</strong>
<gl-link :href="error.gitlab_issue"> <gl-link :href="error.gitlabIssuePath">
<span>{{ error.gitlab_issue }}</span> <span>{{ error.gitlabIssuePath }}</span>
</gl-link> </gl-link>
</li> </li>
<li> <li>
<strong class="bold">{{ __('Sentry event') }}:</strong> <strong class="bold">{{ __('Sentry event') }}:</strong>
<gl-link <gl-link
v-track-event="trackClickErrorLinkToSentryOptions(GQLerror.externalUrl)" v-track-event="trackClickErrorLinkToSentryOptions(error.externalUrl)"
class="d-inline-flex align-items-center" class="d-inline-flex align-items-center"
:href="GQLerror.externalUrl" :href="error.externalUrl"
target="_blank" 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" /> <icon name="external-link" class="ml-1 flex-shrink-0" />
</gl-link> </gl-link>
</li> </li>
<li v-if="GQLerror.firstReleaseShortVersion"> <li v-if="error.firstReleaseShortVersion">
<strong class="bold">{{ __('First seen') }}:</strong> <strong class="bold">{{ __('First seen') }}:</strong>
{{ formatDate(GQLerror.firstSeen) }} {{ formatDate(error.firstSeen) }}
<gl-link :href="firstReleaseLink" target="_blank"> <gl-link :href="firstReleaseLink" target="_blank">
<span> <span>{{ __('Release') }}: {{ error.firstReleaseShortVersion.substr(0, 10) }}</span>
{{ __('Release') }}: {{ GQLerror.firstReleaseShortVersion.substr(0, 10) }}
</span>
</gl-link> </gl-link>
</li> </li>
<li v-if="GQLerror.lastReleaseShortVersion"> <li v-if="error.lastReleaseShortVersion">
<strong class="bold">{{ __('Last seen') }}:</strong> <strong class="bold">{{ __('Last seen') }}:</strong>
{{ formatDate(GQLerror.lastSeen) }} {{ formatDate(error.lastSeen) }}
<gl-link :href="lastReleaseLink" target="_blank"> <gl-link :href="lastReleaseLink" target="_blank">
<span>{{ __('Release') }}: {{ GQLerror.lastReleaseShortVersion.substr(0, 10) }}</span> <span>{{ __('Release') }}: {{ error.lastReleaseShortVersion.substr(0, 10) }}</span>
</gl-link> </gl-link>
</li> </li>
<li> <li>
<strong class="bold">{{ __('Events') }}:</strong> <strong class="bold">{{ __('Events') }}:</strong>
<span>{{ GQLerror.count }}</span> <span>{{ error.count }}</span>
</li> </li>
<li> <li>
<strong class="bold">{{ __('Users') }}:</strong> <strong class="bold">{{ __('Users') }}:</strong>
<span>{{ GQLerror.userCount }}</span> <span>{{ error.userCount }}</span>
</li> </li>
</ul> </ul>
...@@ -351,7 +336,7 @@ export default { ...@@ -351,7 +336,7 @@ export default {
<gl-loading-icon :size="3" /> <gl-loading-icon :size="3" />
</div> </div>
<template v-if="showStacktrace"> <template v-else-if="showStacktrace">
<h3 class="my-4">{{ __('Stack trace') }}</h3> <h3 class="my-4">{{ __('Stack trace') }}</h3>
<stacktrace :entries="stacktrace" /> <stacktrace :entries="stacktrace" />
</template> </template>
......
...@@ -13,6 +13,7 @@ import { ...@@ -13,6 +13,7 @@ import {
GlDropdownDivider, GlDropdownDivider,
GlTooltipDirective, GlTooltipDirective,
GlPagination, GlPagination,
GlButtonGroup,
} from '@gitlab/ui'; } from '@gitlab/ui';
import AccessorUtils from '~/lib/utils/accessor'; import AccessorUtils from '~/lib/utils/accessor';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
...@@ -20,12 +21,16 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; ...@@ -20,12 +21,16 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { __ } from '~/locale'; import { __ } from '~/locale';
import _ from 'underscore'; 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 { export default {
FIRST_PAGE: 1, FIRST_PAGE: 1,
PREV_PAGE: 1, PREV_PAGE: 1,
NEXT_PAGE: 2, NEXT_PAGE: 2,
statusButtons: [
{ status: 'ignored', icon: 'eye-slash', title: __('Ignore') },
{ status: 'resolved', icon: 'check-circle', title: __('Resolve') },
],
fields: [ fields: [
{ {
key: 'error', key: 'error',
...@@ -48,20 +53,13 @@ export default { ...@@ -48,20 +53,13 @@ export default {
{ {
key: 'lastSeen', key: 'lastSeen',
label: __('Last seen'), label: __('Last seen'),
thClass: '', thClass: 'w-15p',
tdClass: `${tableDataClass}`, tdClass: `${tableDataClass}`,
}, },
{ {
key: 'ignore', key: 'status',
label: '',
thClass: 'w-3rem',
tdClass: `${tableDataClass} pl-0`,
},
{
key: 'resolved',
label: '', label: '',
thClass: 'w-3rem', tdClass: `${tableDataClass} text-right`,
tdClass: `${tableDataClass} pl-0`,
}, },
{ {
key: 'details', key: 'details',
...@@ -88,6 +86,7 @@ export default { ...@@ -88,6 +86,7 @@ export default {
Icon, Icon,
GlPagination, GlPagination,
TimeAgo, TimeAgo,
GlButtonGroup,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -332,25 +331,19 @@ export default { ...@@ -332,25 +331,19 @@ export default {
<time-ago :time="errors.item.lastSeen" class="text-secondary" /> <time-ago :time="errors.item.lastSeen" class="text-secondary" />
</div> </div>
</template> </template>
<template #cell(ignore)="errors"> <template #cell(status)="errors">
<gl-button-group>
<gl-button <gl-button
ref="ignoreError" v-for="button in $options.statusButtons"
:key="button.status"
:ref="button.title.toLowerCase() + 'Error'"
v-gl-tooltip.hover v-gl-tooltip.hover
:title="__('Ignore')" :title="button.title"
@click="updateIssueStatus(errors.item.id, 'ignored')" @click="updateIssueStatus(errors.item.id, button.status)"
>
<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-icon :name="button.icon" :size="12" />
</gl-button> </gl-button>
</gl-button-group>
</template> </template>
<template #cell(details)="errors"> <template #cell(details)="errors">
<gl-button <gl-button
......
...@@ -26,7 +26,6 @@ export default () => { ...@@ -26,7 +26,6 @@ export default () => {
issueId, issueId,
projectPath, projectPath,
issueUpdatePath, issueUpdatePath,
issueDetailsPath,
issueStackTracePath, issueStackTracePath,
projectIssuesPath, projectIssuesPath,
} = domEl.dataset; } = domEl.dataset;
...@@ -36,7 +35,6 @@ export default () => { ...@@ -36,7 +35,6 @@ export default () => {
issueId, issueId,
projectPath, projectPath,
issueUpdatePath, issueUpdatePath,
issueDetailsPath,
issueStackTracePath, issueStackTracePath,
projectIssuesPath, projectIssuesPath,
csrfToken: csrf.token, csrfToken: csrf.token,
......
query errorDetails($fullPath: ID!, $errorId: ID!) { query errorDetails($fullPath: ID!, $errorId: ID!) {
project(fullPath: $fullPath) { project(fullPath: $fullPath) {
sentryDetailedError(id: $errorId) { sentryErrors {
detailedError(id: $errorId) {
id id
sentryId sentryId
title title
...@@ -11,11 +12,18 @@ query errorDetails($fullPath: ID!, $errorId: ID!) { ...@@ -11,11 +12,18 @@ query errorDetails($fullPath: ID!, $errorId: ID!) {
lastSeen lastSeen
message message
culprit culprit
tags {
level
logger
}
externalUrl externalUrl
externalBaseUrl
firstReleaseShortVersion firstReleaseShortVersion
lastReleaseShortVersion lastReleaseShortVersion
gitlabCommit gitlabCommit
gitlabCommitPath gitlabCommitPath
gitlabIssuePath
}
} }
} }
} }
...@@ -5,36 +5,11 @@ import Poll from '~/lib/utils/poll'; ...@@ -5,36 +5,11 @@ import Poll from '~/lib/utils/poll';
import { __ } from '~/locale'; import { __ } from '~/locale';
let stackTracePoll; let stackTracePoll;
let detailPoll;
const stopPolling = poll => { const stopPolling = poll => {
if (poll) poll.stop(); 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) { export function startPollingStacktrace({ commit }, endpoint) {
stackTracePoll = new Poll({ stackTracePoll = new Poll({
resource: service, resource: service,
......
export const SET_ERROR = 'SET_ERRORS';
export const SET_LOADING = 'SET_LOADING';
export const SET_LOADING_STACKTRACE = 'SET_LOADING_STACKTRACE'; export const SET_LOADING_STACKTRACE = 'SET_LOADING_STACKTRACE';
export const SET_STACKTRACE_DATA = 'SET_STACKTRACE_DATA'; export const SET_STACKTRACE_DATA = 'SET_STACKTRACE_DATA';
import * as types from './mutation_types'; import * as types from './mutation_types';
export default { export default {
[types.SET_ERROR](state, data) {
state.error = data;
},
[types.SET_LOADING](state, loading) {
state.loading = loading;
},
[types.SET_LOADING_STACKTRACE](state, data) { [types.SET_LOADING_STACKTRACE](state, data) {
state.loadingStacktrace = data; state.loadingStacktrace = data;
}, },
......
export default () => ({ export default () => ({
error: {},
stacktraceData: {}, stacktraceData: {},
loading: true,
loadingStacktrace: true, loadingStacktrace: true,
updatingResolveStatus: false, updatingResolveStatus: false,
updatingIgnoreStatus: false, updatingIgnoreStatus: false,
......
...@@ -19,15 +19,15 @@ export default { ...@@ -19,15 +19,15 @@ export default {
<tabs stop-propagation> <tabs stop-propagation>
<tab active> <tab active>
<template slot="title"> <template slot="title">
{{ __('Merge Requests') }} {{ __('Branches') }}
</template> </template>
<merge-request-search-list /> <branches-search-list />
</tab> </tab>
<tab> <tab>
<template slot="title"> <template slot="title">
{{ __('Branches') }} {{ __('Merge Requests') }}
</template> </template>
<branches-search-list /> <merge-request-search-list />
</tab> </tab>
</tabs> </tabs>
</div> </div>
......
...@@ -51,7 +51,7 @@ export default { ...@@ -51,7 +51,7 @@ export default {
</script> </script>
<template> <template>
<div class="ide-new-btn d-none"> <div class="ide-new-btn">
<div <div
:class="{ :class="{
show: isOpen, show: isOpen,
......
...@@ -1224,6 +1224,8 @@ $ide-commit-header-height: 48px; ...@@ -1224,6 +1224,8 @@ $ide-commit-header-height: 48px;
} }
.ide-new-btn { .ide-new-btn {
display: none;
.btn { .btn {
padding: 2px 5px; padding: 2px 5px;
} }
......
...@@ -14,9 +14,9 @@ ...@@ -14,9 +14,9 @@
cursor: pointer; cursor: pointer;
@media (min-width: map-get($grid-breakpoints, md)) { @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 // 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: -webkit-sticky;
position: sticky; position: sticky;
...@@ -547,7 +547,7 @@ table.code { ...@@ -547,7 +547,7 @@ table.code {
.diff-stats { .diff-stats {
align-items: center; align-items: center;
padding: 0 0.25rem; padding: 0 1rem;
.diff-stats-group { .diff-stats-group {
padding: 0 0.25rem; padding: 0 0.25rem;
...@@ -559,7 +559,7 @@ table.code { ...@@ -559,7 +559,7 @@ table.code {
&.is-compare-versions-header { &.is-compare-versions-header {
.diff-stats-group { .diff-stats-group {
padding: 0 0.5rem; padding: 0 0.25rem;
} }
} }
} }
...@@ -1054,8 +1054,8 @@ table.code { ...@@ -1054,8 +1054,8 @@ table.code {
.diff-tree-list { .diff-tree-list {
position: -webkit-sticky; position: -webkit-sticky;
position: sticky; position: sticky;
$top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px; $top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 11px;
top: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px; top: $top-pos;
max-height: calc(100vh - #{$top-pos}); max-height: calc(100vh - #{$top-pos});
z-index: 202; z-index: 202;
...@@ -1092,10 +1092,7 @@ table.code { ...@@ -1092,10 +1092,7 @@ table.code {
.tree-list-scroll { .tree-list-scroll {
max-height: 100%; max-height: 100%;
padding-top: $grid-size;
padding-bottom: $grid-size; padding-bottom: $grid-size;
border-top: 1px solid $border-color;
border-bottom: 1px solid $border-color;
overflow-y: scroll; overflow-y: scroll;
overflow-x: auto; overflow-x: auto;
} }
......
...@@ -708,7 +708,7 @@ ...@@ -708,7 +708,7 @@
.mr-version-controls { .mr-version-controls {
position: relative; position: relative;
z-index: 203; z-index: 203;
background: $gray-light; background: $white-light;
color: $gl-text-color; color: $gl-text-color;
margin-top: -1px; margin-top: -1px;
...@@ -732,7 +732,7 @@ ...@@ -732,7 +732,7 @@
} }
.content-block { .content-block {
padding: $gl-padding-top $gl-padding; padding: $gl-padding;
border-bottom: 0; border-bottom: 0;
} }
......
...@@ -10,7 +10,7 @@ module ConfirmEmailWarning ...@@ -10,7 +10,7 @@ module ConfirmEmailWarning
protected protected
def show_confirm_warning? def show_confirm_warning?
html_request? && request.get? && Feature.enabled?(:soft_email_confirmation) html_request? && request.get?
end end
def set_confirm_warning def set_confirm_warning
......
...@@ -11,7 +11,7 @@ class ConfirmationsController < Devise::ConfirmationsController ...@@ -11,7 +11,7 @@ class ConfirmationsController < Devise::ConfirmationsController
protected protected
def after_resending_confirmation_instructions_path_for(resource) 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 end
def after_confirmation_path_for(resource_name, resource) def after_confirmation_path_for(resource_name, resource)
......
...@@ -7,6 +7,10 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController ...@@ -7,6 +7,10 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController
skip_cross_project_access_check :index skip_cross_project_access_check :index
def 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]) @snippets = SnippetsFinder.new(current_user, author: current_user, scope: params[:scope])
.execute .execute
.page(params[:page]) .page(params[:page])
......
...@@ -30,6 +30,10 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -30,6 +30,10 @@ class Projects::SnippetsController < Projects::ApplicationController
respond_to :html respond_to :html
def index def index
@snippet_counts = Snippets::CountService
.new(current_user, project: @project)
.execute
@snippets = SnippetsFinder.new(current_user, project: @project, scope: params[:scope]) @snippets = SnippetsFinder.new(current_user, project: @project, scope: params[:scope])
.execute .execute
.page(params[:page]) .page(params[:page])
......
...@@ -53,7 +53,7 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -53,7 +53,7 @@ class RegistrationsController < Devise::RegistrationsController
def welcome def welcome
return redirect_to new_user_registration_path unless current_user 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 current_user.name = nil if current_user.name == current_user.username
end end
...@@ -65,7 +65,7 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -65,7 +65,7 @@ class RegistrationsController < Devise::RegistrationsController
if result[:status] == :success 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 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 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 else
render :welcome render :welcome
end end
...@@ -112,12 +112,12 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -112,12 +112,12 @@ class RegistrationsController < Devise::RegistrationsController
return users_sign_up_welcome_path if experiment_enabled?(:signup_flow) 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 end
def after_inactive_sign_up_path_for(resource) def after_inactive_sign_up_path_for(resource)
Gitlab::AppLogger.info(user_created_message) Gitlab::AppLogger.info(user_created_message)
Feature.enabled?(:soft_email_confirmation) ? dashboard_projects_path : users_almost_there_path dashboard_projects_path
end end
private private
...@@ -179,18 +179,10 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -179,18 +179,10 @@ class RegistrationsController < Devise::RegistrationsController
Gitlab::Utils.to_boolean(params[:terms_opt_in]) Gitlab::Utils.to_boolean(params[:terms_opt_in])
end 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) def stored_location_or_dashboard(user)
stored_location_for(user) || dashboard_projects_path stored_location_for(user) || dashboard_projects_path
end 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 # Part of an experiment to build a new sign up flow. Will be resolved
# with https://gitlab.com/gitlab-org/growth/engineering/issues/64 # with https://gitlab.com/gitlab-org/growth/engineering/issues/64
def choose_layout def choose_layout
......
...@@ -22,7 +22,6 @@ module Projects::ErrorTrackingHelper ...@@ -22,7 +22,6 @@ module Projects::ErrorTrackingHelper
{ {
'issue-id' => issue_id, 'issue-id' => issue_id,
'project-path' => project.full_path, '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), 'issue-update-path' => update_project_error_tracking_index_path(*opts),
'project-issues-path' => project_issues_path(project), 'project-issues-path' => project_issues_path(project),
'issue-stack-trace-path' => stack_trace_project_error_tracking_index_path(*opts) 'issue-stack-trace-path' => stack_trace_project_error_tracking_index_path(*opts)
......
...@@ -74,7 +74,7 @@ module Clusters ...@@ -74,7 +74,7 @@ module Clusters
end end
def ingress_service def ingress_service
cluster.kubeclient.get_service('istio-ingressgateway', 'istio-system') cluster.kubeclient.get_service('istio-ingressgateway', Clusters::Kubernetes::ISTIO_SYSTEM_NAMESPACE)
end end
def uninstall_command def uninstall_command
......
...@@ -10,6 +10,11 @@ module Serverless ...@@ -10,6 +10,11 @@ module Serverless
belongs_to :knative, class_name: 'Clusters::Applications::Knative', foreign_key: 'clusters_applications_knative_id' belongs_to :knative, class_name: 'Clusters::Applications::Knative', foreign_key: 'clusters_applications_knative_id'
belongs_to :creator, class_name: 'User', optional: true 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 :pages_domain, :knative, presence: true
validates :uuid, presence: true, uniqueness: true, length: { is: Gitlab::Serverless::Domain::UUID_LENGTH }, validates :uuid, presence: true, uniqueness: true, length: { is: Gitlab::Serverless::Domain::UUID_LENGTH },
format: { with: HEX_REGEXP, message: 'only allows hex characters' } format: { with: HEX_REGEXP, message: 'only allows hex characters' }
......
...@@ -1638,13 +1638,6 @@ class User < ApplicationRecord ...@@ -1638,13 +1638,6 @@ class User < ApplicationRecord
super super
end end
# override from Devise::Confirmable
def confirmation_period_valid?
return false if Feature.disabled?(:soft_email_confirmation)
super
end
private private
def default_private_profile_to_false def default_private_profile_to_false
......
...@@ -12,5 +12,7 @@ module Clusters ...@@ -12,5 +12,7 @@ module Clusters
GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME = 'gitlab-knative-serving-rolebinding' GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME = 'gitlab-knative-serving-rolebinding'
GITLAB_CROSSPLANE_DATABASE_ROLE_NAME = 'gitlab-crossplane-database-role' GITLAB_CROSSPLANE_DATABASE_ROLE_NAME = 'gitlab-crossplane-database-role'
GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME = 'gitlab-crossplane-database-rolebinding' GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME = 'gitlab-crossplane-database-rolebinding'
KNATIVE_SERVING_NAMESPACE = 'knative-serving'
ISTIO_SYSTEM_NAMESPACE = 'istio-system'
end end
end end
# 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
# 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 @@ ...@@ -5,7 +5,7 @@
= render 'dashboard/snippets_head' = render 'dashboard/snippets_head'
- if current_user.snippets.exists? - 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? - if current_user.snippets.exists?
= render partial: 'shared/snippets/list', locals: { link_project: true } = render partial: 'shared/snippets/list', locals: { link_project: true }
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
- if current_user - if current_user
.top-area .top-area
- include_private = @project.team.member?(current_user) || current_user.admin? - 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? - if new_project_snippet_link.present?
.nav-controls .nav-controls
......
...@@ -7,25 +7,25 @@ ...@@ -7,25 +7,25 @@
= _("All") = _("All")
%span.badge.badge-pill %span.badge.badge-pill
- if include_private - if include_private
= subject.snippets.count = counts[:total]
- else - else
= subject.snippets.public_and_internal_only.count = counts[:are_public_or_internal]
- if include_private - if include_private
%li{ class: active_when(params[:scope] == "are_private") } %li{ class: active_when(params[:scope] == "are_private") }
= link_to subject_snippets_path(subject, scope: 'are_private') do = link_to subject_snippets_path(subject, scope: 'are_private') do
= _("Private") = _("Private")
%span.badge.badge-pill %span.badge.badge-pill
= subject.snippets.are_private.count = counts[:are_private]
%li{ class: active_when(params[:scope] == "are_internal") } %li{ class: active_when(params[:scope] == "are_internal") }
= link_to subject_snippets_path(subject, scope: 'are_internal') do = link_to subject_snippets_path(subject, scope: 'are_internal') do
= _("Internal") = _("Internal")
%span.badge.badge-pill %span.badge.badge-pill
= subject.snippets.are_internal.count = counts[:are_internal]
%li{ class: active_when(params[:scope] == "are_public") } %li{ class: active_when(params[:scope] == "are_public") }
= link_to subject_snippets_path(subject, scope: 'are_public') do = link_to subject_snippets_path(subject, scope: 'are_public') do
= _("Public") = _("Public")
%span.badge.badge-pill %span.badge.badge-pill
= subject.snippets.are_public.count = counts[:are_public]
...@@ -231,6 +231,12 @@ ...@@ -231,6 +231,12 @@
:latency_sensitive: :latency_sensitive:
:resource_boundary: :unknown :resource_boundary: :unknown
:weight: 1 :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 - :name: gcp_cluster:cluster_install_app
:feature_category: :kubernetes_management :feature_category: :kubernetes_management
:has_external_dependencies: true :has_external_dependencies: true
......
# 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
---
title: Restyle changes header & file tree
merge_request: 22364
author:
type: changed
---
title: Allow a grace period for new users to confirm their email
merge_request: 24371
author:
type: added
---
title: Fix bug with snippet counts not being scoped to current authorisation
merge_request: 21705
author:
type: fixed
---
title: Switch order of tabs in Web IDE nav dropdown
merge_request: 24199
author:
type: changed
---
title: Update GraphicsMagick from 1.3.33 to 1.3.34
merge_request: 24225
author: Takuya Noguchi
type: security
# 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 ...@@ -3786,6 +3786,9 @@ ActiveRecord::Schema.define(version: 2020_02_04_131054) do
t.bigint "creator_id" t.bigint "creator_id"
t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_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 ["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 ["creator_id"], name: "index_serverless_domain_cluster_on_creator_id"
t.index ["pages_domain_id"], name: "index_serverless_domain_cluster_on_pages_domain_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 ...@@ -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. 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. 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 ...@@ -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 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: 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 A 192.0.2.1
*.example.io. 1800 IN AAAA 2001::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 ...@@ -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 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: 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 A 192.0.2.1
``` ```
...@@ -131,7 +131,7 @@ The Pages daemon doesn't listen to the outside world. ...@@ -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 order to enable the pages daemon. In `gitlab_pages_options` the
`-pages-domain` must match the `host` setting that you set above. `-pages-domain` must match the `host` setting that you set above.
``` ```ini
gitlab_pages_enabled=true gitlab_pages_enabled=true
gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090" 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. ...@@ -170,7 +170,7 @@ outside world.
1. In `gitlab.yml`, set the port to `443` and https to `true`: 1. In `gitlab.yml`, set the port to `443` and https to `true`:
```shell ```yaml
## GitLab Pages ## GitLab Pages
pages: pages:
enabled: true enabled: true
...@@ -188,9 +188,9 @@ outside world. ...@@ -188,9 +188,9 @@ outside world.
The `-root-cert` and `-root-key` settings are the wildcard TLS certificates The `-root-cert` and `-root-key` settings are the wildcard TLS certificates
of the `example.io` domain: of the `example.io` domain:
``` ```ini
gitlab_pages_enabled=true 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: 1. Copy the `gitlab-pages-ssl` NGINX configuration file:
...@@ -256,7 +256,7 @@ world. Custom domains are supported, but no TLS. ...@@ -256,7 +256,7 @@ world. Custom domains are supported, but no TLS.
`-pages-domain` and `-listen-http` must match the `host` and `external_http` `-pages-domain` and `-listen-http` must match the `host` and `external_http`
settings that you set above respectively: settings that you set above respectively:
``` ```ini
gitlab_pages_enabled=true 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" 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. ...@@ -325,9 +325,9 @@ world. Custom domains and TLS are supported.
The `-root-cert` and `-root-key` settings are the wildcard TLS certificates The `-root-cert` and `-root-key` settings are the wildcard TLS certificates
of the `example.io` domain: of the `example.io` domain:
``` ```ini
gitlab_pages_enabled=true 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: 1. Copy the `gitlab-pages-ssl` NGINX configuration file:
......
...@@ -199,6 +199,13 @@ and they will assist you with any issues you are having. ...@@ -199,6 +199,13 @@ and they will assist you with any issues you are having.
helm upgrade <release name> <chart path> -f gitlab.yaml 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 ## 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) 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: ...@@ -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 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), [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> 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. ...@@ -23,7 +23,7 @@ requiring a single keyword to enable the feature for any job.
Consider a monorepo as follows: Consider a monorepo as follows:
``` ```plaintext
./service_a ./service_a
./service_b ./service_b
./service_c ./service_c
......
...@@ -39,7 +39,7 @@ project: ...@@ -39,7 +39,7 @@ project:
1. Create a new project by selecting **Import project from ➔ Repo by URL** 1. Create a new project by selecting **Import project from ➔ Repo by URL**
1. Add the following URL: 1. Add the following URL:
``` ```plaintext
https://gitlab.com/gitlab-examples/maven/simple-maven-dep.git 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 ...@@ -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. 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: If the deployment has been successful, the deploy job log will output:
``` ```plaintext
[INFO] ------------------------------------------------------------------------ [INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS [INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------ [INFO] ------------------------------------------------------------------------
...@@ -188,7 +188,7 @@ We'll use again a Maven app that can be cloned from our example project: ...@@ -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. Create a new project by selecting **Import project from ➔ Repo by URL**
1. Add the following URL: 1. Add the following URL:
``` ```plaintext
https://gitlab.com/gitlab-examples/maven/simple-maven-app.git 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. ...@@ -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: Dpl can be installed on any machine with:
``` ```shell
gem install dpl gem install dpl
``` ```
...@@ -27,7 +27,7 @@ having to test it on a CI server. ...@@ -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: If you don't have Ruby installed you can do it on Debian-compatible Linux with:
``` ```shell
apt-get update apt-get update
apt-get install ruby-dev apt-get install ruby-dev
``` ```
......
...@@ -163,7 +163,7 @@ sudo nano /etc/nginx/sites-available/default ...@@ -163,7 +163,7 @@ sudo nano /etc/nginx/sites-available/default
The configuration should be like this. The configuration should be like this.
``` ```nginx
server { server {
root /var/www/app/current/public; root /var/www/app/current/public;
server_name example.com; server_name example.com;
......
...@@ -179,7 +179,7 @@ user following [the upstream installation guide][phpenv-installation]. ...@@ -179,7 +179,7 @@ user following [the upstream installation guide][phpenv-installation].
Using phpenv also allows to easily configure the PHP environment with: Using phpenv also allows to easily configure the PHP environment with:
``` ```shell
phpenv config-add my_config.ini phpenv config-add my_config.ini
``` ```
......
...@@ -115,7 +115,7 @@ mix ecto.create ...@@ -115,7 +115,7 @@ mix ecto.create
We expect to see this output at the end of the command: We expect to see this output at the end of the command:
```shell ```plaintext
Generated hello_gitlab_ci app Generated hello_gitlab_ci app
The database for HelloGitlabCi.Repo has been created The database for HelloGitlabCi.Repo has been created
``` ```
...@@ -136,7 +136,7 @@ mix phoenix.server ...@@ -136,7 +136,7 @@ mix phoenix.server
This will be the output to this command: This will be the output to this command:
```shell ```plaintext
[info] Running HelloGitlabCi.Endpoint with Cowboy using http://localhost:4000 [info] Running HelloGitlabCi.Endpoint with Cowboy using http://localhost:4000
23 May 11:44:35 - info: compiling 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 23 May 11:44:37 - info: compiled 6 files into 2 files, copied 3 in 9.8 sec
...@@ -229,7 +229,7 @@ mix test ...@@ -229,7 +229,7 @@ mix test
Our expected result is this: Our expected result is this:
```shell ```plaintext
.... ....
Finished in 0.7 seconds Finished in 0.7 seconds
...@@ -265,20 +265,20 @@ project. ...@@ -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 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: dependencies for running Phoenix tests installed, such as Elixir and Erlang:
```yml ```yaml
image: elixir:latest image: elixir:latest
``` ```
- We'll only use `postgres`, so we can delete the `mysql` and `redis` lines from the `services` section: - We'll only use `postgres`, so we can delete the `mysql` and `redis` lines from the `services` section:
```yml ```yaml
services: services:
- postgres:latest - postgres:latest
``` ```
- Now, we'll create a new section called `variables`, before the `before_script` section: - Now, we'll create a new section called `variables`, before the `before_script` section:
```yml ```yaml
variables: variables:
POSTGRES_DB: hello_gitlab_ci_test POSTGRES_DB: hello_gitlab_ci_test
POSTGRES_HOST: postgres POSTGRES_HOST: postgres
...@@ -293,7 +293,7 @@ project. ...@@ -293,7 +293,7 @@ project.
- In the `before_script` section, we'll add some commands to prepare everything for the test: - In the `before_script` section, we'll add some commands to prepare everything for the test:
```yml ```yaml
before_script: before_script:
- mix local.rebar --force - mix local.rebar --force
- mix local.hex --force - mix local.hex --force
...@@ -310,7 +310,7 @@ project. ...@@ -310,7 +310,7 @@ project.
Let's take a look at the updated file after the changes: Let's take a look at the updated file after the changes:
```yml ```yaml
image: elixir:latest image: elixir:latest
services: services:
...@@ -353,7 +353,7 @@ actual running build job. ...@@ -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 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! :) **Build succeeded** at the end of the process! :)
``` ```shell
$ mix test $ mix test
.... ....
......
...@@ -114,11 +114,11 @@ SSH key. ...@@ -114,11 +114,11 @@ SSH key.
You can generate the SSH key from the machine that GitLab Runner is installed 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. 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 sudo su - gitlab-runner
``` ```
...@@ -132,7 +132,7 @@ on, and use that key for all projects that are run on this machine. ...@@ -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 If you are accessing a private GitLab repository you need to add it as a
[deploy key](../../ssh/README.md#deploy-keys). [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 ```shell
ssh example.com ssh example.com
......
...@@ -110,7 +110,7 @@ The action is irreversible. ...@@ -110,7 +110,7 @@ The action is irreversible.
To trigger a job you need to send a `POST` request to GitLab's API endpoint: To trigger a job you need to send a `POST` request to GitLab's API endpoint:
``` ```plaintext
POST /projects/:id/trigger/pipeline 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 ...@@ -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 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): 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 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 ...@@ -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` will be available in GitLab CI so that they can be used in your `.gitlab-ci.yml`
file. The parameter is of the form: file. The parameter is of the form:
``` ```plaintext
variables[key]=value variables[key]=value
``` ```
......
...@@ -42,7 +42,7 @@ The current stages are: ...@@ -42,7 +42,7 @@ The current stages are:
## Default image ## Default image
The default image is currently 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, 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. 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 [ ...@@ -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. Make sure that the backup script on both servers can connect to the database.
``` ```shell
# On your CI server: # On your CI server:
# Omnibus # Omnibus
sudo chown gitlab-ci:gitlab-ci /var/opt/gitlab/gitlab-ci/builds 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 ...@@ -64,7 +64,7 @@ sudo -u gitlab_ci -H bundle exec rake backup:create RAILS_ENV=production
Also check on your GitLab server. Also check on your GitLab server.
``` ```shell
# On your GitLab server: # On your GitLab server:
# Omnibus # Omnibus
sudo gitlab-backup create SKIP=repositories,uploads 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 ...@@ -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 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.** GitLab server uses MySQL you cannot migrate your CI data to GitLab 8.0.**
``` ```shell
# On your CI server: # On your CI server:
# Omnibus # Omnibus
sudo gitlab-ci-rake env:info sudo gitlab-ci-rake env:info
...@@ -99,7 +99,7 @@ cd /home/gitlab_ci/gitlab-ci ...@@ -99,7 +99,7 @@ cd /home/gitlab_ci/gitlab-ci
sudo -u gitlab_ci -H bundle exec rake env:info RAILS_ENV=production sudo -u gitlab_ci -H bundle exec rake env:info RAILS_ENV=production
``` ```
``` ```shell
# On your GitLab server: # On your GitLab server:
# Omnibus # Omnibus
sudo gitlab-rake gitlab:env:info sudo gitlab-rake gitlab:env:info
...@@ -149,7 +149,7 @@ Now upgrade GitLab CI to version 8.0. If you are using Omnibus packages, ...@@ -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. Disable GitLab CI after upgrading to 8.0.
``` ```shell
# On your CI server: # On your CI server:
# Omnibus # Omnibus
sudo gitlab-ctl stop ci-unicorn sudo gitlab-ctl stop ci-unicorn
...@@ -171,7 +171,7 @@ GitLab server. On Omnibus GitLab servers you will have to add a line to ...@@ -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 `/etc/gitlab/gitlab.rb`. On GitLab servers installed from source you will have
to replace the contents of `/home/git/gitlab/config/secrets.yml`. to replace the contents of `/home/git/gitlab/config/secrets.yml`.
``` ```shell
# On your CI server: # On your CI server:
# Omnibus # Omnibus
sudo gitlab-ci-rake backup:show_secrets 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 ...@@ -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 the command finishes it will print the path to your data export archive; you
will need this file later. will need this file later.
``` ```shell
# On your CI server: # On your CI server:
# Omnibus # Omnibus
sudo chown gitlab-ci:gitlab-ci /var/opt/gitlab/gitlab-ci/builds 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 ...@@ -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 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. your laptop and then from your laptop to the GitLab server.
``` ```shell
# Start from your laptop # Start from your laptop
ssh -A ci_admin@ci_server.example ssh -A ci_admin@ci_server.example
# Now on the CI server # Now on the CI server
...@@ -221,7 +221,7 @@ scp /path/to/12345_gitlab_ci_backup.tar gitlab_admin@gitlab_server.example:~ ...@@ -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 Make the CI data archive discoverable for GitLab. We assume below that you
store backups in the default path, adjust the command if necessary. store backups in the default path, adjust the command if necessary.
``` ```shell
# On your GitLab server: # On your GitLab server:
# Omnibus # Omnibus
sudo mv /path/to/12345_gitlab_ci_backup.tar /var/opt/gitlab/backups/ 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/ ...@@ -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 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. be no CI data yet because you turned CI on the GitLab server off earlier.
``` ```shell
# On your GitLab server: # On your GitLab server:
# Omnibus # Omnibus
sudo chown git:git /var/opt/gitlab/gitlab-ci/builds 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 ...@@ -248,7 +248,7 @@ sudo -u git -H bundle exec rake ci:migrate RAILS_ENV=production
### 6. Restart GitLab ### 6. Restart GitLab
``` ```shell
# On your GitLab server: # On your GitLab server:
# Omnibus # Omnibus
sudo gitlab-ctl hup unicorn sudo gitlab-ctl hup unicorn
...@@ -347,7 +347,7 @@ restoration](../raketasks/backup_restore.md) guide. ...@@ -347,7 +347,7 @@ restoration](../raketasks/backup_restore.md) guide.
If you see errors like this: 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` Missing `secret_key_base` or `db_key_base` for 'production' environment. The secrets will be generated and stored in `config/secrets.yml`
rake aborted! rake aborted!
Errno::EACCES: Permission denied @ rb_sysopen - config/secrets.yml 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. ...@@ -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: 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 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: Then before executing `ci:migrate` you need to fix builds folder permission:
``` ```shell
sudo chown git:git /var/opt/gitlab/gitlab-ci/builds sudo chown git:git /var/opt/gitlab/gitlab-ci/builds
``` ```
...@@ -450,7 +450,7 @@ EOF ...@@ -450,7 +450,7 @@ EOF
Source installations: Source installations:
``` ```shell
cd /home/gitlab_ci/gitlab-ci cd /home/gitlab_ci/gitlab-ci
sudo -u gitlab_ci -H bundle exec rails dbconsole production <<EOF sudo -u gitlab_ci -H bundle exec rails dbconsole production <<EOF
... COPY SQL STATEMENTS FROM ABOVE ... ... COPY SQL STATEMENTS FROM ABOVE ...
......
...@@ -291,15 +291,9 @@ As the DAST job belongs to a separate `dast` stage that runs after all ...@@ -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), [default stages](../../../ci/yaml/README.md#stages),
don't forget to add `stage: dast` when you override the template job definition. 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. 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 | | Environment variable | Required | Description |
|-----------------------------| ----------|--------------------------------------------------------------------------------| |-----------------------------| ----------|--------------------------------------------------------------------------------|
...@@ -314,14 +308,83 @@ variable value. ...@@ -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_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`. | | `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:** 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 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 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.
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). ### Other formats
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. 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 ## Security Dashboard
...@@ -329,6 +392,20 @@ The Security Dashboard is a good place to get an overview of all the security ...@@ -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 vulnerabilities in your groups, projects and pipelines. Read more about the
[Security Dashboard](../security_dashboard/index.md). [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 ## Interacting with the vulnerabilities
Once a vulnerability is found, you can interact with it. Read more on how to 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 ...@@ -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 the official [upgrade guide](https://docs.cilium.io/en/stable/install/upgrade/) for more
information. 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: `cilium-monitor` sidecar container. You can check these logs via:
```shell ```shell
kubectl -n gitlab-managed-apps logs cilium-XXXX cilium-monitor 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 ```yml
agent: agent:
......
# 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
# 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 ...@@ -13,8 +13,8 @@ module Gitlab
def initialize(root, max_size: DEFAULT_MAX_SIZE, max_depth: DEFAULT_MAX_DEPTH) def initialize(root, max_size: DEFAULT_MAX_SIZE, max_depth: DEFAULT_MAX_DEPTH)
@root = root @root = root
@max_size = max_size @max_size = max_size || DEFAULT_MAX_SIZE
@max_depth = max_depth @max_depth = max_depth || DEFAULT_MAX_DEPTH
@size = 0 @size = 0
@depth = 0 @depth = 0
......
...@@ -8292,11 +8292,6 @@ msgstr "" ...@@ -8292,11 +8292,6 @@ msgstr ""
msgid "Fetching licenses failed. You are not permitted to perform this action." msgid "Fetching licenses failed. You are not permitted to perform this action."
msgstr "" msgstr ""
msgid "File"
msgid_plural "Files"
msgstr[0] ""
msgstr[1] ""
msgid "File Hooks" msgid "File Hooks"
msgstr "" msgstr ""
...@@ -11931,10 +11926,10 @@ msgstr "" ...@@ -11931,10 +11926,10 @@ msgstr ""
msgid "MergeRequest|Error loading full diff. Please try again." msgid "MergeRequest|Error loading full diff. Please try again."
msgstr "" msgstr ""
msgid "MergeRequest|Filter files or search with %{modifier_key}+p" msgid "MergeRequest|No files found"
msgstr "" msgstr ""
msgid "MergeRequest|No files found" msgid "MergeRequest|Search files (%{modifier_key}P)"
msgstr "" msgstr ""
msgid "Merged" msgid "Merged"
...@@ -22185,6 +22180,9 @@ msgstr "" ...@@ -22185,6 +22180,9 @@ msgstr ""
msgid "among other things" msgid "among other things"
msgstr "" msgstr ""
msgid "and"
msgstr ""
msgid "any-approver for the merge request already exists" msgid "any-approver for the merge request already exists"
msgstr "" msgstr ""
...@@ -22615,6 +22613,11 @@ msgstr "" ...@@ -22615,6 +22613,11 @@ msgstr ""
msgid "failed to dismiss associated finding(id=%{finding_id}): %{message}" msgid "failed to dismiss associated finding(id=%{finding_id}): %{message}"
msgstr "" msgstr ""
msgid "file"
msgid_plural "files"
msgstr[0] ""
msgstr[1] ""
msgid "finding is not found or is already attached to a vulnerability" msgid "finding is not found or is already attached to a vulnerability"
msgstr "" msgstr ""
......
...@@ -3,10 +3,6 @@ ...@@ -3,10 +3,6 @@
require 'spec_helper' require 'spec_helper'
describe ConfirmEmailWarning do describe ConfirmEmailWarning do
before do
stub_feature_flags(soft_email_confirmation: true)
end
controller(ApplicationController) do controller(ApplicationController) do
# `described_class` is not available in this context # `described_class` is not available in this context
include ConfirmEmailWarning include ConfirmEmailWarning
......
...@@ -17,5 +17,14 @@ describe Dashboard::SnippetsController do ...@@ -17,5 +17,14 @@ describe Dashboard::SnippetsController do
create(:personal_snippet, :public, author: user) create(:personal_snippet, :public, author: user)
end end
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
end end
...@@ -27,6 +27,15 @@ describe Projects::SnippetsController do ...@@ -27,6 +27,15 @@ describe Projects::SnippetsController do
end end
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 context 'when the project snippet is private' do
let!(:project_snippet) { create(:project_snippet, :private, project: project, author: user) } let!(:project_snippet) { create(:project_snippet, :private, project: project, author: user) }
......
...@@ -77,25 +77,6 @@ describe RegistrationsController do ...@@ -77,25 +77,6 @@ describe RegistrationsController do
context 'when send_user_confirmation_email is true' do context 'when send_user_confirmation_email is true' do
before do before do
stub_application_setting(send_user_confirmation_email: true) 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)
allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
end
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 allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
end end
...@@ -106,7 +87,6 @@ describe RegistrationsController do ...@@ -106,7 +87,6 @@ describe RegistrationsController do
expect(response).to redirect_to(dashboard_projects_path) expect(response).to redirect_to(dashboard_projects_path)
end end
end end
end
context 'when signup_enabled? is false' do context 'when signup_enabled? is false' do
it 'redirects to sign_in' do it 'redirects to sign_in' do
......
...@@ -5,5 +5,41 @@ FactoryBot.define do ...@@ -5,5 +5,41 @@ FactoryBot.define do
pages_domain { create(:pages_domain) } pages_domain { create(:pages_domain) }
knative { create(:clusters_applications_knative) } knative { create(:clusters_applications_knative) }
creator { create(:user) } 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
end end
...@@ -59,6 +59,10 @@ describe 'Dashboard snippets' do ...@@ -59,6 +59,10 @@ describe 'Dashboard snippets' do
visit dashboard_snippets_path visit dashboard_snippets_path
end 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 it 'contains all snippets of logged user' do
expect(page).to have_selector('.snippet-row', count: 3) expect(page).to have_selector('.snippet-row', count: 3)
......
...@@ -153,24 +153,6 @@ describe 'Invites' do ...@@ -153,24 +153,6 @@ describe 'Invites' do
context 'email confirmation enabled' do context 'email confirmation enabled' do
let(:send_email_confirmation) { true } 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 context 'when soft email confirmation is enabled' do
before do before do
allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
...@@ -198,24 +180,7 @@ describe 'Invites' do ...@@ -198,24 +180,7 @@ describe 'Invites' do
context 'the user sign-up using a different email address' do context 'the user sign-up using a different email address' do
let(:invite_email) { build_stubbed(:user).email } let(:invite_email) { build_stubbed(:user).email }
context 'when soft email confirmation is not enabled' do
before 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
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 allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
end end
...@@ -227,5 +192,4 @@ describe 'Invites' do ...@@ -227,5 +192,4 @@ describe 'Invites' do
end end
end end
end end
end
end end
...@@ -50,7 +50,7 @@ describe 'Merge request > User sees versions', :js do ...@@ -50,7 +50,7 @@ describe 'Merge request > User sees versions', :js do
expect(page).to have_content 'latest version' expect(page).to have_content 'latest version'
end end
expect(page).to have_content '8 Files' expect(page).to have_content '8 files'
end end
it_behaves_like 'allows commenting', it_behaves_like 'allows commenting',
...@@ -84,7 +84,7 @@ describe 'Merge request > User sees versions', :js do ...@@ -84,7 +84,7 @@ describe 'Merge request > User sees versions', :js do
end end
it 'shows comments that were last relevant at that version' do 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( position = Gitlab::Diff::Position.new(
old_path: ".gitmodules", old_path: ".gitmodules",
...@@ -128,12 +128,10 @@ describe 'Merge request > User sees versions', :js do ...@@ -128,12 +128,10 @@ describe 'Merge request > User sees versions', :js do
diff_id: merge_request_diff3.id, diff_id: merge_request_diff3.id,
start_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9' 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') additions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group .js-file-addition-line').text
.ancestor('.diff-stats-group').text deletions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group .js-file-deletion-line').text
deletions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-deletion')
.ancestor('.diff-stats-group').text
expect(additions_content).to eq '15' expect(additions_content).to eq '15'
expect(deletions_content).to eq '6' expect(deletions_content).to eq '6'
...@@ -156,12 +154,10 @@ describe 'Merge request > User sees versions', :js do ...@@ -156,12 +154,10 @@ describe 'Merge request > User sees versions', :js do
end end
it 'show diff between new and old version' do 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') additions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group .js-file-addition-line').text
.ancestor('.diff-stats-group').text deletions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group .js-file-deletion-line').text
deletions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-deletion')
.ancestor('.diff-stats-group').text
expect(page).to have_content '4 Files' expect(page).to have_content '4 files'
expect(additions_content).to eq '15' expect(additions_content).to eq '15'
expect(deletions_content).to eq '6' expect(deletions_content).to eq '6'
end end
...@@ -171,7 +167,7 @@ describe 'Merge request > User sees versions', :js do ...@@ -171,7 +167,7 @@ describe 'Merge request > User sees versions', :js do
page.within '.mr-version-dropdown' do page.within '.mr-version-dropdown' do
expect(page).to have_content 'latest version' expect(page).to have_content 'latest version'
end end
expect(page).to have_content '8 Files' expect(page).to have_content '8 files'
end end
it_behaves_like 'allows commenting', it_behaves_like 'allows commenting',
...@@ -197,7 +193,7 @@ describe 'Merge request > User sees versions', :js do ...@@ -197,7 +193,7 @@ describe 'Merge request > User sees versions', :js do
find('.btn-default').click find('.btn-default').click
click_link 'version 1' click_link 'version 1'
end end
expect(page).to have_content '0 Files' expect(page).to have_content '0 files'
end end
end end
...@@ -223,7 +219,7 @@ describe 'Merge request > User sees versions', :js do ...@@ -223,7 +219,7 @@ describe 'Merge request > User sees versions', :js do
expect(page).to have_content 'version 1' expect(page).to have_content 'version 1'
end end
expect(page).to have_content '0 Files' expect(page).to have_content '0 files'
end end
end end
......
...@@ -31,6 +31,16 @@ describe 'Projects > Snippets > User views snippets' do ...@@ -31,6 +31,16 @@ describe 'Projects > Snippets > User views snippets' do
it_behaves_like 'paginated snippets' it_behaves_like 'paginated snippets'
end 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 it 'shows snippets' do
visit_project_snippets visit_project_snippets
......
...@@ -797,7 +797,6 @@ describe 'Login' do ...@@ -797,7 +797,6 @@ describe 'Login' do
before do before do
stub_application_setting(send_user_confirmation_email: true) 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 allow(User).to receive(:allow_unconfirmed_access_for).and_return grace_period
end end
......
...@@ -129,39 +129,6 @@ shared_examples 'Signup' do ...@@ -129,39 +129,6 @@ shared_examples 'Signup' do
stub_application_setting(send_user_confirmation_email: true) stub_application_setting(send_user_confirmation_email: true)
end end
context 'when soft email confirmation is not enabled' do
before do
stub_feature_flags(soft_email_confirmation: false)
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)
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 it 'creates the user account and sends a confirmation email' do
visit new_user_registration_path visit new_user_registration_path
...@@ -188,7 +155,6 @@ shared_examples 'Signup' do ...@@ -188,7 +155,6 @@ shared_examples 'Signup' do
end end
end end
end end
end
context "when sigining up with different cased emails" do context "when sigining up with different cased emails" do
it "creates the user successfully" do it "creates the user successfully" do
......
...@@ -49,8 +49,7 @@ describe('CompareVersions', () => { ...@@ -49,8 +49,7 @@ describe('CompareVersions', () => {
expect(treeListBtn.exists()).toBe(true); expect(treeListBtn.exists()).toBe(true);
expect(treeListBtn.attributes('title')).toBe('Hide file browser'); expect(treeListBtn.attributes('title')).toBe('Hide file browser');
expect(treeListBtn.findAll(Icon).length).not.toBe(0); expect(treeListBtn.find(Icon).props('name')).toBe('file-tree');
expect(treeListBtn.find(Icon).props('name')).toBe('collapse-left');
}); });
it('should render comparison dropdowns with correct values', () => { it('should render comparison dropdowns with correct values', () => {
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import Icon from '~/vue_shared/components/icon.vue';
import DiffStats from '~/diffs/components/diff_stats.vue'; import DiffStats from '~/diffs/components/diff_stats.vue';
import Icon from '~/vue_shared/components/icon.vue';
describe('diff_stats', () => { describe('diff_stats', () => {
it('does not render a group if diffFileLengths is empty', () => { it('does not render a group if diffFileLengths is empty', () => {
...@@ -37,18 +37,18 @@ describe('diff_stats', () => { ...@@ -37,18 +37,18 @@ describe('diff_stats', () => {
}, },
}); });
const findFileLine = name => wrapper.find(name);
const findIcon = name => const findIcon = name =>
wrapper wrapper
.findAll(Icon) .findAll(Icon)
.filter(c => c.attributes('name') === name) .filter(c => c.attributes('name') === name)
.at(0).element.parentNode; .at(0).element.parentNode;
const additions = findFileLine('.js-file-addition-line');
const additions = findIcon('file-addition'); const deletions = findFileLine('.js-file-deletion-line');
const deletions = findIcon('file-deletion');
const filesChanged = findIcon('doc-code'); const filesChanged = findIcon('doc-code');
expect(additions.textContent).toContain('100'); expect(additions.text()).toBe('100');
expect(deletions.textContent).toContain('200'); expect(deletions.text()).toBe('200');
expect(filesChanged.textContent).toContain('300'); expect(filesChanged.textContent).toContain('300');
}); });
}); });
...@@ -37,14 +37,13 @@ describe('ErrorDetails', () => { ...@@ -37,14 +37,13 @@ describe('ErrorDetails', () => {
projectPath: '/root/gitlab-test', projectPath: '/root/gitlab-test',
listPath: '/error_tracking', listPath: '/error_tracking',
issueUpdatePath: '/123', issueUpdatePath: '/123',
issueDetailsPath: '/123/details',
issueStackTracePath: '/stacktrace', issueStackTracePath: '/stacktrace',
projectIssuesPath: '/test-project/issues/', projectIssuesPath: '/test-project/issues/',
csrfToken: 'fakeToken', csrfToken: 'fakeToken',
}, },
}); });
wrapper.setData({ wrapper.setData({
GQLerror: { error: {
id: 'gid://gitlab/Gitlab::ErrorTracking::DetailedError/129381', id: 'gid://gitlab/Gitlab::ErrorTracking::DetailedError/129381',
sentryId: 129381, sentryId: 129381,
title: 'Issue title', title: 'Issue title',
...@@ -59,7 +58,6 @@ describe('ErrorDetails', () => { ...@@ -59,7 +58,6 @@ describe('ErrorDetails', () => {
beforeEach(() => { beforeEach(() => {
actions = { actions = {
startPollingDetails: () => {},
startPollingStacktrace: () => {}, startPollingStacktrace: () => {},
updateIgnoreStatus: jest.fn(), updateIgnoreStatus: jest.fn(),
updateResolveStatus: jest.fn().mockResolvedValue({ closed_issue_iid: 1 }), updateResolveStatus: jest.fn().mockResolvedValue({ closed_issue_iid: 1 }),
...@@ -71,8 +69,6 @@ describe('ErrorDetails', () => { ...@@ -71,8 +69,6 @@ describe('ErrorDetails', () => {
}; };
const state = { const state = {
error: {},
loading: true,
stacktraceData: {}, stacktraceData: {},
loadingStacktrace: true, loadingStacktrace: true,
}; };
...@@ -93,7 +89,7 @@ describe('ErrorDetails', () => { ...@@ -93,7 +89,7 @@ describe('ErrorDetails', () => {
$apollo: { $apollo: {
query, query,
queries: { queries: {
GQLerror: { error: {
loading: true, loading: true,
stopPolling: jest.fn(), stopPolling: jest.fn(),
}, },
...@@ -122,9 +118,7 @@ describe('ErrorDetails', () => { ...@@ -122,9 +118,7 @@ describe('ErrorDetails', () => {
describe('Error details', () => { describe('Error details', () => {
beforeEach(() => { beforeEach(() => {
store.state.details.loading = false; mocks.$apollo.queries.error.loading = false;
store.state.details.error.id = 1;
mocks.$apollo.queries.GQLerror.loading = false;
mountComponent(); mountComponent();
}); });
...@@ -138,16 +132,22 @@ describe('ErrorDetails', () => { ...@@ -138,16 +132,22 @@ describe('ErrorDetails', () => {
describe('Badges', () => { describe('Badges', () => {
it('should show language and error level badges', () => { it('should show language and error level badges', () => {
store.state.details.error.tags = { level: 'error', logger: 'ruby' }; wrapper.setData({
mountComponent(); error: {
tags: { level: 'error', logger: 'ruby' },
},
});
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(wrapper.findAll(GlBadge).length).toBe(2); expect(wrapper.findAll(GlBadge).length).toBe(2);
}); });
}); });
it('should NOT show the badge if the tag is not present', () => { it('should NOT show the badge if the tag is not present', () => {
store.state.details.error.tags = { level: 'error' }; wrapper.setData({
mountComponent(); error: {
tags: { level: 'error' },
},
});
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(wrapper.findAll(GlBadge).length).toBe(1); expect(wrapper.findAll(GlBadge).length).toBe(1);
}); });
...@@ -156,8 +156,11 @@ describe('ErrorDetails', () => { ...@@ -156,8 +156,11 @@ describe('ErrorDetails', () => {
it.each(Object.keys(severityLevel))( it.each(Object.keys(severityLevel))(
'should set correct severity level variant for %s badge', 'should set correct severity level variant for %s badge',
level => { level => {
store.state.details.error.tags = { level: severityLevel[level] }; wrapper.setData({
mountComponent(); error: {
tags: { level: severityLevel[level] },
},
});
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlBadge).attributes('variant')).toEqual( expect(wrapper.find(GlBadge).attributes('variant')).toEqual(
severityLevelVariant[severityLevel[level]], severityLevelVariant[severityLevel[level]],
...@@ -167,8 +170,11 @@ describe('ErrorDetails', () => { ...@@ -167,8 +170,11 @@ describe('ErrorDetails', () => {
); );
it('should fallback for ERROR severityLevelVariant when severityLevel is unknown', () => { it('should fallback for ERROR severityLevelVariant when severityLevel is unknown', () => {
store.state.details.error.tags = { level: 'someNewErrorLevel' }; wrapper.setData({
mountComponent(); error: {
tags: { level: 'someNewErrorLevel' },
},
});
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlBadge).attributes('variant')).toEqual( expect(wrapper.find(GlBadge).attributes('variant')).toEqual(
severityLevelVariant[severityLevel.ERROR], severityLevelVariant[severityLevel.ERROR],
...@@ -180,7 +186,6 @@ describe('ErrorDetails', () => { ...@@ -180,7 +186,6 @@ describe('ErrorDetails', () => {
describe('Stacktrace', () => { describe('Stacktrace', () => {
it('should show stacktrace', () => { it('should show stacktrace', () => {
store.state.details.loadingStacktrace = false; store.state.details.loadingStacktrace = false;
mountComponent();
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.find(Stacktrace).exists()).toBe(true); expect(wrapper.find(Stacktrace).exists()).toBe(true);
...@@ -190,11 +195,12 @@ describe('ErrorDetails', () => { ...@@ -190,11 +195,12 @@ describe('ErrorDetails', () => {
it('should NOT show stacktrace if no entries', () => { it('should NOT show stacktrace if no entries', () => {
store.state.details.loadingStacktrace = false; store.state.details.loadingStacktrace = false;
store.getters = { 'details/sentryUrl': () => 'sentry.io', 'details/stacktrace': () => [] }; store.getters = { 'details/sentryUrl': () => 'sentry.io', 'details/stacktrace': () => [] };
mountComponent(); return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.find(Stacktrace).exists()).toBe(false); expect(wrapper.find(Stacktrace).exists()).toBe(false);
}); });
}); });
});
describe('When a user clicks the create issue button', () => { describe('When a user clicks the create issue button', () => {
beforeEach(() => { beforeEach(() => {
...@@ -331,19 +337,18 @@ describe('ErrorDetails', () => { ...@@ -331,19 +337,18 @@ describe('ErrorDetails', () => {
}); });
describe('GitLab issue link', () => { describe('GitLab issue link', () => {
const gitlabIssue = 'https://gitlab.example.com/issues/1'; const gitlabIssuePath = 'https://gitlab.example.com/issues/1';
const findGitLabLink = () => wrapper.find(`[href="${gitlabIssue}"]`); const findGitLabLink = () => wrapper.find(`[href="${gitlabIssuePath}"]`);
const findCreateIssueButton = () => wrapper.find('[data-qa-selector="create_issue_button"]'); const findCreateIssueButton = () => wrapper.find('[data-qa-selector="create_issue_button"]');
const findViewIssueButton = () => wrapper.find('[data-qa-selector="view_issue_button"]'); const findViewIssueButton = () => wrapper.find('[data-qa-selector="view_issue_button"]');
describe('is present', () => { describe('is present', () => {
beforeEach(() => { beforeEach(() => {
store.state.details.loading = false; wrapper.setData({
store.state.details.error = { error: {
id: 1, gitlabIssuePath,
gitlab_issue: gitlabIssue, },
}; });
mountComponent();
}); });
it('should display the View issue button', () => { it('should display the View issue button', () => {
...@@ -361,12 +366,11 @@ describe('ErrorDetails', () => { ...@@ -361,12 +366,11 @@ describe('ErrorDetails', () => {
describe('is not present', () => { describe('is not present', () => {
beforeEach(() => { beforeEach(() => {
store.state.details.loading = false; wrapper.setData({
store.state.details.error = { error: {
id: 1, gitlabIssuePath: null,
gitlab_issue: null, },
}; });
mountComponent();
}); });
it('should not display the View issue button', () => { it('should not display the View issue button', () => {
...@@ -390,9 +394,9 @@ describe('ErrorDetails', () => { ...@@ -390,9 +394,9 @@ describe('ErrorDetails', () => {
const findGitLabCommitLink = () => wrapper.find(`[href$="${gitlabCommitPath}"]`); const findGitLabCommitLink = () => wrapper.find(`[href$="${gitlabCommitPath}"]`);
it('should display a link', () => { it('should display a link', () => {
mocks.$apollo.queries.GQLerror.loading = false; mocks.$apollo.queries.error.loading = false;
wrapper.setData({ wrapper.setData({
GQLerror: { error: {
gitlabCommit, gitlabCommit,
gitlabCommitPath, gitlabCommitPath,
}, },
...@@ -403,9 +407,9 @@ describe('ErrorDetails', () => { ...@@ -403,9 +407,9 @@ describe('ErrorDetails', () => {
}); });
it('should not display a link', () => { it('should not display a link', () => {
mocks.$apollo.queries.GQLerror.loading = false; mocks.$apollo.queries.error.loading = false;
wrapper.setData({ wrapper.setData({
GQLerror: { error: {
gitlabCommit: null, gitlabCommit: null,
}, },
}); });
......
...@@ -239,7 +239,7 @@ describe('ErrorTrackingList', () => { ...@@ -239,7 +239,7 @@ describe('ErrorTrackingList', () => {
expect(actions.updateStatus).toHaveBeenCalledWith( expect(actions.updateStatus).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
{ {
endpoint: '/project/test/-/error_tracking/3.json', endpoint: `/project/test/-/error_tracking/${errorsList[0].id}.json`,
redirectUrl: '/error_tracking', redirectUrl: '/error_tracking',
status: 'ignored', status: 'ignored',
}, },
...@@ -267,7 +267,7 @@ describe('ErrorTrackingList', () => { ...@@ -267,7 +267,7 @@ describe('ErrorTrackingList', () => {
expect(actions.updateStatus).toHaveBeenCalledWith( expect(actions.updateStatus).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
{ {
endpoint: '/project/test/-/error_tracking/3.json', endpoint: `/project/test/-/error_tracking/${errorsList[0].id}.json`,
redirectUrl: '/error_tracking', redirectUrl: '/error_tracking',
status: 'resolved', status: 'resolved',
}, },
......
...@@ -26,53 +26,6 @@ describe('Sentry error details store actions', () => { ...@@ -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', () => { describe('startPollingStacktrace', () => {
const endpoint = '123/stacktrace'; const endpoint = '123/stacktrace';
it('should commit SET_ERROR with received response', done => { it('should commit SET_ERROR with received response', done => {
......
...@@ -34,7 +34,7 @@ export const utilsMockData = [ ...@@ -34,7 +34,7 @@ export const utilsMockData = [
content: [ content: [
{ {
text: 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', section: 'prepare-executor',
......
...@@ -83,7 +83,6 @@ describe Projects::ErrorTrackingHelper do ...@@ -83,7 +83,6 @@ describe Projects::ErrorTrackingHelper do
describe '#error_details_data' do describe '#error_details_data' do
let(:issue_id) { 1234 } let(:issue_id) { 1234 }
let(:route_params) { [project.owner, project, issue_id, { format: :json }] } 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(:project_path) { project.full_path }
let(:stack_trace_path) { stack_trace_namespace_project_error_tracking_index_path(*route_params) } let(:stack_trace_path) { stack_trace_namespace_project_error_tracking_index_path(*route_params) }
let(:issues_path) { project_issues_path(project) } let(:issues_path) { project_issues_path(project) }
...@@ -98,10 +97,6 @@ describe Projects::ErrorTrackingHelper do ...@@ -98,10 +97,6 @@ describe Projects::ErrorTrackingHelper do
expect(result['project-path']).to eq project_path expect(result['project-path']).to eq project_path
end 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 it 'returns the correct stack trace path' do
expect(result['issue-stack-trace-path']).to eq stack_trace_path expect(result['issue-stack-trace-path']).to eq stack_trace_path
end end
......
# 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
# 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 ...@@ -17,29 +17,45 @@ describe Gitlab::Utils::DeepSize do
let(:max_size) { 1.kilobyte } let(:max_size) { 1.kilobyte }
let(:max_depth) { 10 } let(:max_depth) { 10 }
let(:deep_size) { described_class.new(data, max_size: max_size, max_depth: max_depth) }
describe '#evaluate' do subject(:deep_size) { described_class.new(data, max_size: max_size, max_depth: max_depth) }
context 'when data within size and depth limits' do
it 'returns true' do it { expect(described_class::DEFAULT_MAX_SIZE).to eq(1.megabyte) }
expect(deep_size).to be_valid 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
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 context 'when data not within size limit' do
let(:max_size) { 200.bytes } let(:max_size) { 200.bytes }
it 'returns false' do it { is_expected.not_to be_valid }
expect(deep_size).not_to be_valid
end
end end
context 'when data not within depth limit' do context 'when data not within depth limit' do
let(:max_depth) { 2 } let(:max_depth) { 2 }
it 'returns false' do it { is_expected.not_to be_valid }
expect(deep_size).not_to be_valid
end
end end
end end
......
...@@ -50,4 +50,12 @@ describe ::Serverless::DomainCluster do ...@@ -50,4 +50,12 @@ describe ::Serverless::DomainCluster do
describe 'domain' do describe 'domain' do
it { is_expected.to respond_to(:domain) } it { is_expected.to respond_to(:domain) }
end end
describe 'certificate' do
it { is_expected.to respond_to(:certificate) }
end
describe 'key' do
it { is_expected.to respond_to(:key) }
end
end end
# 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
# 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| ...@@ -18,3 +18,35 @@ RSpec.shared_examples 'paginated snippets' do |remote: false|
end end
end 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
# 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment