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