Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1b9a2ce278
commit
48650fe1bf
|
@ -7,11 +7,12 @@
|
|||
*.rake @gitlab-org/maintainers/rails-backend
|
||||
|
||||
# Technical writing team are the default reviewers for all markdown docs
|
||||
*.md @gl-docsteam
|
||||
/doc/ @gl-docsteam
|
||||
# Dev and Doc guidelines
|
||||
/doc/development/ @marcia @mjang1
|
||||
/doc/development/documentation/ @mikelewis
|
||||
/doc/ci @marcel.amirault @sselhorn
|
||||
/doc/.linting @marcel.amirault @eread @aqualls @mikelewis
|
||||
|
||||
# Frontend maintainers should see everything in `app/assets/`
|
||||
*.scss @annabeldunstone @gitlab-org/maintainers/frontend
|
||||
|
|
|
@ -151,12 +151,14 @@ export default {
|
|||
<strong>{{ $options.severityLabels[alert.severity] }}</strong>
|
||||
</div>
|
||||
<span class="mx-2">•</span>
|
||||
<gl-sprintf :message="reportedAtMessage">
|
||||
<template #when>
|
||||
<time-ago-tooltip :time="alert.createdAt" class="gl-ml-3" />
|
||||
</template>
|
||||
<template #tool>{{ alert.monitoringTool }}</template>
|
||||
</gl-sprintf>
|
||||
<span>
|
||||
<gl-sprintf :message="reportedAtMessage">
|
||||
<template #when>
|
||||
<time-ago-tooltip :time="alert.createdAt" />
|
||||
</template>
|
||||
<template #tool>{{ alert.monitoringTool }}</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
</div>
|
||||
<gl-button
|
||||
v-if="glFeatures.createIssueFromAlertEnabled"
|
||||
|
|
|
@ -3,12 +3,19 @@ import { GlLoadingIcon } from '@gitlab/ui';
|
|||
import { RichViewer, SimpleViewer } from '~/vue_shared/components/blob_viewers';
|
||||
import BlobContentError from './blob_content_error.vue';
|
||||
|
||||
import { BLOB_RENDER_EVENT_LOAD, BLOB_RENDER_EVENT_SHOW_SOURCE } from './constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
BlobContentError,
|
||||
},
|
||||
props: {
|
||||
blob: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: '',
|
||||
|
@ -37,6 +44,8 @@ export default {
|
|||
return this.activeViewer.renderError;
|
||||
},
|
||||
},
|
||||
BLOB_RENDER_EVENT_LOAD,
|
||||
BLOB_RENDER_EVENT_SHOW_SOURCE,
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
@ -44,7 +53,13 @@ export default {
|
|||
<gl-loading-icon v-if="loading" size="md" color="dark" class="my-4 mx-auto" />
|
||||
|
||||
<template v-else>
|
||||
<blob-content-error v-if="viewerError" :viewer-error="viewerError" />
|
||||
<blob-content-error
|
||||
v-if="viewerError"
|
||||
:viewer-error="viewerError"
|
||||
:blob="blob"
|
||||
@[$options.BLOB_RENDER_EVENT_LOAD]="$emit($options.BLOB_RENDER_EVENT_LOAD)"
|
||||
@[$options.BLOB_RENDER_EVENT_SHOW_SOURCE]="$emit($options.BLOB_RENDER_EVENT_SHOW_SOURCE)"
|
||||
/>
|
||||
<component
|
||||
:is="viewer"
|
||||
v-else
|
||||
|
|
|
@ -1,15 +1,84 @@
|
|||
<script>
|
||||
import { __ } from '~/locale';
|
||||
import { GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import { BLOB_RENDER_ERRORS } from './constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlSprintf,
|
||||
GlLink,
|
||||
},
|
||||
props: {
|
||||
viewerError: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
blob: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
notStoredExternally() {
|
||||
return this.viewerError !== BLOB_RENDER_ERRORS.REASONS.EXTERNAL.id;
|
||||
},
|
||||
renderErrorReason() {
|
||||
const defaultReasonPath = Object.keys(BLOB_RENDER_ERRORS.REASONS).find(
|
||||
reason => BLOB_RENDER_ERRORS.REASONS[reason].id === this.viewerError,
|
||||
);
|
||||
const defaultReason = BLOB_RENDER_ERRORS.REASONS[defaultReasonPath].text;
|
||||
return this.notStoredExternally
|
||||
? defaultReason
|
||||
: defaultReason[this.blob.externalStorage || 'default'];
|
||||
},
|
||||
renderErrorOptions() {
|
||||
const load = {
|
||||
...BLOB_RENDER_ERRORS.OPTIONS.LOAD,
|
||||
condition: this.shouldShowLoadBtn,
|
||||
};
|
||||
const showSource = {
|
||||
...BLOB_RENDER_ERRORS.OPTIONS.SHOW_SOURCE,
|
||||
condition: this.shouldShowSourceBtn,
|
||||
};
|
||||
const download = {
|
||||
...BLOB_RENDER_ERRORS.OPTIONS.DOWNLOAD,
|
||||
href: this.blob.rawPath,
|
||||
};
|
||||
return [load, showSource, download];
|
||||
},
|
||||
shouldShowLoadBtn() {
|
||||
return this.viewerError === BLOB_RENDER_ERRORS.REASONS.COLLAPSED.id;
|
||||
},
|
||||
shouldShowSourceBtn() {
|
||||
return this.blob.richViewer && this.blob.renderedAsText && this.notStoredExternally;
|
||||
},
|
||||
},
|
||||
errorMessage: __(
|
||||
'This content could not be displayed because %{reason}. You can %{options} instead.',
|
||||
),
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="file-content code">
|
||||
<div class="text-center py-4" v-html="viewerError"></div>
|
||||
<div class="text-center py-4">
|
||||
<gl-sprintf :message="$options.errorMessage">
|
||||
<template #reason>{{ renderErrorReason }}</template>
|
||||
<template #options>
|
||||
<template v-for="option in renderErrorOptions">
|
||||
<span v-if="option.condition" :key="option.text">
|
||||
<gl-link
|
||||
:href="option.href"
|
||||
:target="option.target"
|
||||
:data-test-id="`option-${option.id}`"
|
||||
@click="option.event && $emit(option.event)"
|
||||
>{{ option.text }}</gl-link
|
||||
>
|
||||
{{ option.conjunction }}
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { __ } from '~/locale';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import { numberToHumanSize } from '~/lib/utils/number_utils';
|
||||
|
||||
export const BTN_COPY_CONTENTS_TITLE = __('Copy file contents');
|
||||
export const BTN_RAW_TITLE = __('Open raw');
|
||||
|
@ -9,3 +10,56 @@ export const SIMPLE_BLOB_VIEWER_TITLE = __('Display source');
|
|||
|
||||
export const RICH_BLOB_VIEWER = 'rich';
|
||||
export const RICH_BLOB_VIEWER_TITLE = __('Display rendered file');
|
||||
|
||||
export const BLOB_RENDER_EVENT_LOAD = 'force-content-fetch';
|
||||
export const BLOB_RENDER_EVENT_SHOW_SOURCE = 'force-switch-viewer';
|
||||
|
||||
export const BLOB_RENDER_ERRORS = {
|
||||
REASONS: {
|
||||
COLLAPSED: {
|
||||
id: 'collapsed',
|
||||
text: sprintf(__('it is larger than %{limit}'), {
|
||||
limit: numberToHumanSize(1048576), // 1MB in bytes
|
||||
}),
|
||||
},
|
||||
TOO_LARGE: {
|
||||
id: 'too_large',
|
||||
text: sprintf(__('it is larger than %{limit}'), {
|
||||
limit: numberToHumanSize(104857600), // 100MB in bytes
|
||||
}),
|
||||
},
|
||||
EXTERNAL: {
|
||||
id: 'server_side_but_stored_externally',
|
||||
text: {
|
||||
lfs: __('it is stored in LFS'),
|
||||
build_artifact: __('it is stored as a job artifact'),
|
||||
default: __('it is stored externally'),
|
||||
},
|
||||
},
|
||||
},
|
||||
OPTIONS: {
|
||||
LOAD: {
|
||||
id: 'load',
|
||||
text: __('load it anyway'),
|
||||
conjunction: __('or'),
|
||||
href: '#',
|
||||
target: '',
|
||||
event: BLOB_RENDER_EVENT_LOAD,
|
||||
},
|
||||
SHOW_SOURCE: {
|
||||
id: 'show_source',
|
||||
text: __('view the source'),
|
||||
conjunction: __('or'),
|
||||
href: '#',
|
||||
target: '',
|
||||
event: BLOB_RENDER_EVENT_SHOW_SOURCE,
|
||||
},
|
||||
DOWNLOAD: {
|
||||
id: 'download',
|
||||
text: __('download it'),
|
||||
conjunction: '',
|
||||
target: '_blank',
|
||||
condition: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import { escape } from 'lodash';
|
||||
import { getFirstCharacterCapitalized } from '~/lib/utils/text_utility';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
|
||||
export const DEFAULT_SIZE_CLASS = 's40';
|
||||
export const IDENTICON_BG_COUNT = 7;
|
||||
|
||||
export function getIdenticonBackgroundClass(entityId) {
|
||||
const type = (entityId % IDENTICON_BG_COUNT) + 1;
|
||||
// If a GraphQL string id is passed in, convert it to the entity number
|
||||
const id = typeof entityId === 'string' ? getIdFromGraphQLId(entityId) : entityId;
|
||||
const type = (id % IDENTICON_BG_COUNT) + 1;
|
||||
return `bg${type}`;
|
||||
}
|
||||
|
||||
|
|
|
@ -111,6 +111,9 @@ function deferredInitialisation() {
|
|||
const recoverySettingsCallout = document.querySelector('.js-recovery-settings-callout');
|
||||
PersistentUserCallout.factory(recoverySettingsCallout);
|
||||
|
||||
const usersOverLicenseCallout = document.querySelector('.js-users-over-license-callout');
|
||||
PersistentUserCallout.factory(usersOverLicenseCallout);
|
||||
|
||||
if (document.querySelector('.search')) initSearchAutocomplete();
|
||||
|
||||
addSelectOnFocusBehaviour('.js-select-on-focus');
|
||||
|
|
|
@ -18,6 +18,11 @@ export default class PersistentUserCallout {
|
|||
|
||||
init() {
|
||||
const closeButton = this.container.querySelector('.js-close');
|
||||
|
||||
if (!closeButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
closeButton.addEventListener('click', event => this.dismiss(event));
|
||||
|
||||
if (this.deferLinks) {
|
||||
|
|
|
@ -7,7 +7,12 @@ import CloneDropdownButton from '~/vue_shared/components/clone_dropdown.vue';
|
|||
|
||||
import GetBlobContent from '../queries/snippet.blob.content.query.graphql';
|
||||
|
||||
import { SIMPLE_BLOB_VIEWER, RICH_BLOB_VIEWER } from '~/blob/components/constants';
|
||||
import {
|
||||
SIMPLE_BLOB_VIEWER,
|
||||
RICH_BLOB_VIEWER,
|
||||
BLOB_RENDER_EVENT_LOAD,
|
||||
BLOB_RENDER_EVENT_SHOW_SOURCE,
|
||||
} from '~/blob/components/constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -27,6 +32,16 @@ export default {
|
|||
},
|
||||
update: data =>
|
||||
data.snippets.edges[0].node.blob.richData || data.snippets.edges[0].node.blob.plainData,
|
||||
result() {
|
||||
if (this.activeViewerType === RICH_BLOB_VIEWER) {
|
||||
this.blob.richViewer.renderError = null;
|
||||
} else {
|
||||
this.blob.simpleViewer.renderError = null;
|
||||
}
|
||||
},
|
||||
skip() {
|
||||
return this.viewer.renderError;
|
||||
},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
|
@ -62,9 +77,15 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
switchViewer(newViewer) {
|
||||
this.activeViewerType = newViewer;
|
||||
this.activeViewerType = newViewer || SIMPLE_BLOB_VIEWER;
|
||||
},
|
||||
forceQuery() {
|
||||
this.$apollo.queries.blobContent.skip = false;
|
||||
this.$apollo.queries.blobContent.refetch();
|
||||
},
|
||||
},
|
||||
BLOB_RENDER_EVENT_LOAD,
|
||||
BLOB_RENDER_EVENT_SHOW_SOURCE,
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
@ -81,7 +102,14 @@ export default {
|
|||
/>
|
||||
</template>
|
||||
</blob-header>
|
||||
<blob-content :loading="isContentLoading" :content="blobContent" :active-viewer="viewer" />
|
||||
<blob-content
|
||||
:loading="isContentLoading"
|
||||
:content="blobContent"
|
||||
:active-viewer="viewer"
|
||||
:blob="blob"
|
||||
@[$options.BLOB_RENDER_EVENT_LOAD]="forceQuery"
|
||||
@[$options.BLOB_RENDER_EVENT_SHOW_SOURCE]="switchViewer"
|
||||
/>
|
||||
</article>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -17,6 +17,8 @@ fragment SnippetBase on Snippet {
|
|||
path
|
||||
rawPath
|
||||
size
|
||||
externalStorage
|
||||
renderedAsText
|
||||
simpleViewer {
|
||||
...BlobViewer
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { getIdenticonBackgroundClass, getIdenticonTitle } from '~/helpers/avatar
|
|||
export default {
|
||||
props: {
|
||||
entityId: {
|
||||
type: Number,
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
entityName: {
|
||||
|
|
|
@ -15,6 +15,9 @@ module GoogleApi
|
|||
session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] =
|
||||
expires_at.to_s
|
||||
|
||||
rescue ::Faraday::TimeoutError, ::Faraday::ConnectionFailed
|
||||
flash[:alert] = _('Timeout connecting to the Google API. Please try again.')
|
||||
ensure
|
||||
redirect_to redirect_uri_from_session
|
||||
end
|
||||
|
||||
|
|
|
@ -54,6 +54,10 @@ module ApplicationHelper
|
|||
args.any? { |v| v.to_s.downcase == action_name }
|
||||
end
|
||||
|
||||
def admin_section?
|
||||
controller.class.ancestors.include?(Admin::ApplicationController)
|
||||
end
|
||||
|
||||
def last_commit(project)
|
||||
if project.repo_exists?
|
||||
time_ago_with_tooltip(project.repository.commit.committed_date)
|
||||
|
|
|
@ -223,11 +223,19 @@ module Clusters
|
|||
end
|
||||
|
||||
def applications
|
||||
APPLICATIONS_ASSOCIATIONS.map do |association_name|
|
||||
public_send(association_name) || public_send("build_#{association_name}") # rubocop:disable GitlabSecurity/PublicSend
|
||||
APPLICATIONS.each_value.map do |application_class|
|
||||
find_or_build_application(application_class)
|
||||
end
|
||||
end
|
||||
|
||||
def find_or_build_application(application_class)
|
||||
raise ArgumentError, "#{application_class} is not in APPLICATIONS" unless APPLICATIONS.value?(application_class)
|
||||
|
||||
association_name = application_class.association_name
|
||||
|
||||
public_send(association_name) || public_send("build_#{association_name}") # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
|
||||
def provider
|
||||
if gcp?
|
||||
provider_gcp
|
||||
|
|
|
@ -27,6 +27,7 @@ module Clusters
|
|||
state :update_errored, value: 6
|
||||
state :uninstalling, value: 7
|
||||
state :uninstall_errored, value: 8
|
||||
state :uninstalled, value: 10
|
||||
|
||||
# Used for applications that are pre-installed by the cluster,
|
||||
# e.g. Knative in GCP Cloud Run enabled clusters
|
||||
|
@ -35,6 +36,14 @@ module Clusters
|
|||
# and no exit transitions.
|
||||
state :pre_installed, value: 9
|
||||
|
||||
event :make_externally_installed do
|
||||
transition any => :installed
|
||||
end
|
||||
|
||||
event :make_externally_uninstalled do
|
||||
transition any => :uninstalled
|
||||
end
|
||||
|
||||
event :make_scheduled do
|
||||
transition [:installable, :errored, :installed, :updated, :update_errored, :uninstall_errored] => :scheduled
|
||||
end
|
||||
|
|
|
@ -61,6 +61,7 @@ module Ci
|
|||
|
||||
case artifact.file_type
|
||||
when 'dotenv' then parse_dotenv_artifact(job, artifact)
|
||||
when 'cluster_applications' then parse_cluster_applications_artifact(job, artifact)
|
||||
else success
|
||||
end
|
||||
end
|
||||
|
@ -111,5 +112,9 @@ module Ci
|
|||
def parse_dotenv_artifact(job, artifact)
|
||||
Ci::ParseDotenvArtifactService.new(job.project, current_user).execute(artifact)
|
||||
end
|
||||
|
||||
def parse_cluster_applications_artifact(job, artifact)
|
||||
Clusters::ParseClusterApplicationsArtifactService.new(job, job.user).execute(artifact)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Clusters
|
||||
class ParseClusterApplicationsArtifactService < ::BaseService
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
MAX_ACCEPTABLE_ARTIFACT_SIZE = 5.kilobytes
|
||||
RELEASE_NAMES = %w[prometheus].freeze
|
||||
|
||||
def initialize(job, current_user)
|
||||
@job = job
|
||||
|
||||
super(job.project, current_user)
|
||||
end
|
||||
|
||||
def execute(artifact)
|
||||
return success unless Feature.enabled?(:cluster_applications_artifact, project)
|
||||
|
||||
raise ArgumentError, 'Artifact is not cluster_applications file type' unless artifact&.cluster_applications?
|
||||
|
||||
unless artifact.file.size < MAX_ACCEPTABLE_ARTIFACT_SIZE
|
||||
return error(too_big_error_message, :bad_request)
|
||||
end
|
||||
|
||||
unless cluster
|
||||
return error(s_('ClusterIntegration|No deployment cluster found for this job'))
|
||||
end
|
||||
|
||||
parse!(artifact)
|
||||
|
||||
success
|
||||
rescue Gitlab::Kubernetes::Helm::Parsers::ListV2::ParserError, ActiveRecord::RecordInvalid => error
|
||||
Gitlab::ErrorTracking.track_exception(error, job_id: artifact.job_id)
|
||||
error(error.message, :bad_request)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :job
|
||||
|
||||
def cluster
|
||||
strong_memoize(:cluster) do
|
||||
deployment_cluster = job.deployment&.cluster
|
||||
|
||||
deployment_cluster if Ability.allowed?(current_user, :admin_cluster, deployment_cluster)
|
||||
end
|
||||
end
|
||||
|
||||
def parse!(artifact)
|
||||
releases = []
|
||||
|
||||
artifact.each_blob do |blob|
|
||||
releases.concat(Gitlab::Kubernetes::Helm::Parsers::ListV2.new(blob).releases)
|
||||
end
|
||||
|
||||
update_cluster_application_statuses!(releases)
|
||||
end
|
||||
|
||||
def update_cluster_application_statuses!(releases)
|
||||
release_by_name = releases.index_by { |release| release['Name'] }
|
||||
|
||||
Clusters::Cluster.transaction do
|
||||
RELEASE_NAMES.each do |release_name|
|
||||
application = find_or_build_application(release_name)
|
||||
|
||||
release = release_by_name[release_name]
|
||||
|
||||
if release
|
||||
case release['Status']
|
||||
when 'DEPLOYED'
|
||||
application.make_externally_installed!
|
||||
when 'FAILED'
|
||||
application.make_errored!(s_('ClusterIntegration|Helm release failed to install'))
|
||||
end
|
||||
else
|
||||
# missing, so by definition, we consider this uninstalled
|
||||
application.make_externally_uninstalled! if application.persisted?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def find_or_build_application(application_name)
|
||||
application_class = Clusters::Cluster::APPLICATIONS[application_name]
|
||||
|
||||
cluster.find_or_build_application(application_class)
|
||||
end
|
||||
|
||||
def too_big_error_message
|
||||
human_size = ActiveSupport::NumberHelper.number_to_human_size(MAX_ACCEPTABLE_ARTIFACT_SIZE)
|
||||
|
||||
s_('ClusterIntegration|Cluster_applications artifact too big. Maximum allowable size: %{human_size}') % { human_size: human_size }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,6 +5,7 @@
|
|||
.mobile-overlay
|
||||
.alert-wrapper
|
||||
= render 'shared/outdated_browser'
|
||||
= render_if_exists 'layouts/header/users_over_license_banner'
|
||||
- if Feature.enabled?(:subscribable_banner_license, default_enabled: true)
|
||||
= render_if_exists "layouts/header/ee_subscribable_banner"
|
||||
= render "layouts/broadcast"
|
||||
|
|
|
@ -2,20 +2,19 @@
|
|||
.modal-dialog
|
||||
.modal-content
|
||||
.modal-header
|
||||
%h3.page-title Delete label: #{label.name} ?
|
||||
%h3.page-title= _('Delete label: %{label_name} ?') % { label_name: label.name }
|
||||
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
|
||||
%span{ "aria-hidden": true } ×
|
||||
|
||||
.modal-body
|
||||
%p
|
||||
%strong= label.name
|
||||
%span will be permanently deleted from #{label.subject_name}. This cannot be undone.
|
||||
= _('<strong>%{label_name}</strong> <span>will be permanently deleted from %{subject_name}. This cannot be undone.</span>').html_safe % { label_name: label.name, subject_name: label.subject_name }
|
||||
|
||||
.modal-footer
|
||||
%a{ href: '#', data: { dismiss: 'modal' }, class: 'btn btn-default' } Cancel
|
||||
%a{ href: '#', data: { dismiss: 'modal' }, class: 'btn btn-default' }= _('Cancel')
|
||||
|
||||
= link_to 'Delete label',
|
||||
= link_to _('Delete label'),
|
||||
label.destroy_path,
|
||||
title: 'Delete',
|
||||
title: _('Delete'),
|
||||
method: :delete,
|
||||
class: 'btn btn-remove'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd $(dirname $0)/..
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd $(dirname $0)/..
|
||||
app_root=$(pwd)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd $(dirname $0)/..
|
||||
app_root=$(pwd)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Refactored render errors for blob to Vue
|
||||
merge_request: 32345
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Externalize i18n strings from ./app/views/shared/_delete_label_modal.html.haml
|
||||
merge_request: 32138
|
||||
author: Gilang Gumilar
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Improve responses in the snippet create/update API endpoints
|
||||
merge_request: 32282
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Set timeout for Google OAuth to prevent 503 error
|
||||
merge_request: 30653
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add nested file detection for Dependency Scanning
|
||||
merge_request: 31932
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix missing space character in alert header
|
||||
merge_request: 32395
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class OAuth2
|
||||
alias_method :original_callback_phase, :callback_phase
|
||||
|
||||
# Monkey patch until PR is merged and released upstream
|
||||
# https://github.com/omniauth/omniauth-oauth2/pull/129
|
||||
def callback_phase
|
||||
original_callback_phase
|
||||
rescue ::Faraday::TimeoutError, ::Faraday::ConnectionFailed => e
|
||||
fail!(:timeout, e)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,21 +18,22 @@ You can read more about the Docker Registry at
|
|||
|
||||
**Omnibus GitLab installations**
|
||||
|
||||
If you are using the Omnibus GitLab built-in [Let's Encrypt integration](https://docs.gitlab.com/omnibus/settings/ssl.html#lets-encrypt-integration), as of GitLab 12.5, the Container Registry will be automatically enabled on port 5050 of the default domain.
|
||||
If you installed GitLab by using the Omnibus installation package, the Container Registry
|
||||
may or may not be available by default.
|
||||
|
||||
If you are not using GitLab 12.5 or later, or do not use GitLab's built-in Let's Encrypt
|
||||
integration, the GitLab Container Registry must be enabled and
|
||||
[configured to use an external domain](#container-registry-domain-configuration).
|
||||
The Container Registry is automatically enabled and available on your GitLab domain, port 5050 if:
|
||||
|
||||
To enable the GitLab Container Registry on your *existing* GitLab domain, refer to the section on
|
||||
[configuring Container Registry to use an existing domain](#configure-container-registry-under-an-existing-gitlab-domain).
|
||||
- You're using the built-in [Let's Encrypt integration](https://docs.gitlab.com/omnibus/settings/ssl.html#lets-encrypt-integration), and
|
||||
- You're using GitLab 12.5 or later.
|
||||
|
||||
To use a *separate* domain with your Container Registry, refer to the section on
|
||||
[configuring Container Registry under its own domain](#configure-container-registry-under-its-own-domain).
|
||||
Otherwise, the Container Registry is not enabled. To enable it:
|
||||
|
||||
- You can configure it for your [GitLab domain](#configure-container-registry-under-an-existing-gitlab-domain), or
|
||||
- You can configure it for [a different domain](#configure-container-registry-under-its-own-domain).
|
||||
|
||||
NOTE: **Note:**
|
||||
The container registry works under HTTPS by default. Using HTTP is possible
|
||||
but not recommended and out of the scope of this document.
|
||||
The Container Registry works under HTTPS by default. You can use HTTP
|
||||
but it's not recommended and is out of the scope of this document.
|
||||
Read the [insecure Registry documentation](https://docs.docker.com/registry/insecure/)
|
||||
if you want to implement this.
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ store:
|
|||
|
||||
```yaml
|
||||
before_script:
|
||||
- mkdir -p /kaniko/.docker
|
||||
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
|
||||
- |
|
||||
echo "-----BEGIN CERTIFICATE-----
|
||||
|
|
|
@ -3617,7 +3617,7 @@ Read more about the various [YAML features](https://learnxinyminutes.com/docs/ya
|
|||
|
||||
YAML has a handy feature called 'anchors', which lets you easily duplicate
|
||||
content across your document. Anchors can be used to duplicate/inherit
|
||||
properties, and is a perfect example to be used with [hidden keys](#hide-jobs)
|
||||
properties, and is a perfect example to be used with [hidden jobs](#hide-jobs)
|
||||
to provide templates for your jobs.
|
||||
|
||||
The following example uses anchors and map merging. It will create two jobs,
|
||||
|
@ -3731,7 +3731,7 @@ test:mysql:
|
|||
- ruby
|
||||
```
|
||||
|
||||
You can see that the hidden keys are conveniently used as templates.
|
||||
You can see that the hidden jobs are conveniently used as templates.
|
||||
|
||||
NOTE: **Note:**
|
||||
You can't use YAML anchors across multiple files when leveraging the [`include`](#include)
|
||||
|
@ -3829,7 +3829,7 @@ GitLab CI/CD. In the following example, `.hidden_job` will be ignored:
|
|||
```
|
||||
|
||||
Use this feature to ignore jobs, or use the
|
||||
[special YAML features](#special-yaml-features) and transform the hidden keys
|
||||
[special YAML features](#special-yaml-features) and transform the hidden jobs
|
||||
into templates.
|
||||
|
||||
## Skip Pipeline
|
||||
|
|
|
@ -327,6 +327,23 @@ Before taking the decision to merge:
|
|||
before merging. A comment must to be posted if the MR is merged with any failed job.
|
||||
- If the MR contains both Quality and non-Quality-related changes, the MR should be merged by the relevant maintainer for user-facing changes (backend, frontend, or database) after the Quality related changes are approved by a Software Engineer in Test.
|
||||
|
||||
If a merge request is fundamentally ready, but needs only trivial fixes (such as
|
||||
typos), consider demonstrating a [bias for
|
||||
action](https://about.gitlab.com/handbook/values/#bias-for-action) by making
|
||||
those changes directly without going back to the author. You can do this by
|
||||
using the [suggest changes](../user/discussions/index.md#suggest-changes) feature to apply
|
||||
your own suggestions to the merge request. Note that:
|
||||
|
||||
- If the changes are not straightforward, please prefer assigning the merge request back
|
||||
to the author.
|
||||
- **Before applying suggestions**, edit the merge request to make sure
|
||||
[squash and
|
||||
merge](../user/project/merge_requests/squash_and_merge.md#squash-and-merge)
|
||||
is enabled, otherwise, the pipeline's Danger job will fail.
|
||||
- If a merge request does not have squash and merge enabled, and it
|
||||
has more than one commit, then see the note below about rewriting
|
||||
commit history.
|
||||
|
||||
When ready to merge:
|
||||
|
||||
- Consider using the [Squash and
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# X.509 signatures **(CORE ONLY)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/122159) in GitLab 12.10.
|
||||
|
||||
When [signing commits with X.509](../user/project/repository/x509_signed_commits/index.md),
|
||||
the trust anchor might change and the signatures stored within the database must be updated.
|
||||
|
||||
|
@ -10,13 +12,13 @@ certificate store.
|
|||
|
||||
To update all X.509 signatures, run:
|
||||
|
||||
**Omnibus Installation**
|
||||
**Omnibus Installations:**
|
||||
|
||||
```shell
|
||||
sudo gitlab-rake gitlab:x509:update_signatures
|
||||
```
|
||||
|
||||
**Source Installation**
|
||||
**Source Installations:**
|
||||
|
||||
```shell
|
||||
sudo -u git -H bundle exec rake gitlab:x509:update_signatures RAILS_ENV=production
|
||||
|
|
|
@ -217,9 +217,9 @@ To reorder child epics assigned to an epic:
|
|||
1. Go to the **Epics and Issues** tab.
|
||||
1. Drag and drop epics into the desired order.
|
||||
|
||||
### Move issues between epics
|
||||
### Move issues between epics **(ULTIMATE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33039) in GitLab 13.0.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33039) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.0.
|
||||
|
||||
New issues are added to the top of their list in the **Epics and Issues**
|
||||
tab. You can move issues from one epic to another. Issues and child epics cannot be intermingled.
|
||||
|
@ -270,14 +270,14 @@ To add a child epic to an epic:
|
|||
|
||||
### Move child epics between epics
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33039) in GitLab 13.0.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33039) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.0.
|
||||
|
||||
New child epics are added to the top of their list in the **Epics and Issues** tab.
|
||||
You can move child epics from one epic to another.
|
||||
When you add an epic that's already linked to a parent epic, the link to its current parent is removed.
|
||||
Issues and child epics cannot be intermingled.
|
||||
|
||||
To move child epics **(ULTIMATE)** to another epic:
|
||||
To move child epics to another epic:
|
||||
|
||||
1. Go to the **Epics and Issues** tab.
|
||||
1. Drag and drop epics into the desired parent epic.
|
||||
|
|
|
@ -70,12 +70,12 @@ module API
|
|||
service_response = ::Snippets::CreateService.new(user_project, current_user, snippet_params).execute
|
||||
snippet = service_response.payload[:snippet]
|
||||
|
||||
render_spam_error! if snippet.spam?
|
||||
|
||||
if snippet.persisted?
|
||||
if service_response.success?
|
||||
present snippet, with: Entities::ProjectSnippet
|
||||
else
|
||||
render_validation_error!(snippet)
|
||||
render_spam_error! if snippet.spam?
|
||||
|
||||
render_api_error!({ error: service_response.message }, service_response.http_status)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -106,12 +106,12 @@ module API
|
|||
service_response = ::Snippets::UpdateService.new(user_project, current_user, snippet_params).execute(snippet)
|
||||
snippet = service_response.payload[:snippet]
|
||||
|
||||
render_spam_error! if snippet.spam?
|
||||
|
||||
if snippet.valid?
|
||||
if service_response.success?
|
||||
present snippet, with: Entities::ProjectSnippet
|
||||
else
|
||||
render_validation_error!(snippet)
|
||||
render_spam_error! if snippet.spam?
|
||||
|
||||
render_api_error!({ error: service_response.message }, service_response.http_status)
|
||||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
|
|
@ -81,12 +81,12 @@ module API
|
|||
service_response = ::Snippets::CreateService.new(nil, current_user, attrs).execute
|
||||
snippet = service_response.payload[:snippet]
|
||||
|
||||
render_spam_error! if snippet.spam?
|
||||
|
||||
if snippet.persisted?
|
||||
if service_response.success?
|
||||
present snippet, with: Entities::PersonalSnippet
|
||||
else
|
||||
render_validation_error!(snippet)
|
||||
render_spam_error! if snippet.spam?
|
||||
|
||||
render_api_error!({ error: service_response.message }, service_response.http_status)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -115,12 +115,12 @@ module API
|
|||
service_response = ::Snippets::UpdateService.new(nil, current_user, attrs).execute(snippet)
|
||||
snippet = service_response.payload[:snippet]
|
||||
|
||||
render_spam_error! if snippet.spam?
|
||||
|
||||
if snippet.persisted?
|
||||
if service_response.success?
|
||||
present snippet, with: Entities::PersonalSnippet
|
||||
else
|
||||
render_validation_error!(snippet)
|
||||
render_spam_error! if snippet.spam?
|
||||
|
||||
render_api_error!({ error: service_response.message }, service_response.http_status)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -66,7 +66,10 @@ module Gitlab
|
|||
nil
|
||||
end
|
||||
else
|
||||
Gitlab.config.omniauth.providers.find { |provider| provider.name == name }
|
||||
provider = Gitlab.config.omniauth.providers.find { |provider| provider.name == name }
|
||||
merge_provider_args_with_defaults!(provider)
|
||||
|
||||
provider
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -81,6 +84,15 @@ module Gitlab
|
|||
config = config_for(name)
|
||||
config && config['icon']
|
||||
end
|
||||
|
||||
def self.merge_provider_args_with_defaults!(provider)
|
||||
return unless provider
|
||||
|
||||
provider['args'] ||= {}
|
||||
|
||||
defaults = Gitlab::OmniauthInitializer.default_arguments_for(provider['name'])
|
||||
provider['args'].deep_merge!(defaults.deep_stringify_keys)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -105,13 +105,13 @@ gemnasium-dependency_scanning:
|
|||
$GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
|
||||
$DS_DEFAULT_ANALYZERS =~ /gemnasium([^-]|$)/
|
||||
exists:
|
||||
- 'Gemfile.lock'
|
||||
- 'composer.lock'
|
||||
- 'gems.locked'
|
||||
- 'go.sum'
|
||||
- 'npm-shrinkwrap.json'
|
||||
- 'package-lock.json'
|
||||
- 'yarn.lock'
|
||||
- '{Gemfile.lock,*/Gemfile.lock,*/*/Gemfile.lock}'
|
||||
- '{composer.lock,*/composer.lock,*/*/composer.lock}'
|
||||
- '{gems.locked,*/gems.locked,*/*/gems.locked}'
|
||||
- '{go.sum,*/go.sum,*/*/go.sum}'
|
||||
- '{npm-shrinkwrap.json,*/npm-shrinkwrap.json,*/*/npm-shrinkwrap.json}'
|
||||
- '{package-lock.json,*/package-lock.json,*/*/package-lock.json}'
|
||||
- '{yarn.lock,*/yarn.lock,*/*/yarn.lock}'
|
||||
|
||||
gemnasium-maven-dependency_scanning:
|
||||
extends: .ds-analyzer
|
||||
|
@ -124,9 +124,9 @@ gemnasium-maven-dependency_scanning:
|
|||
$GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
|
||||
$DS_DEFAULT_ANALYZERS =~ /gemnasium-maven/
|
||||
exists:
|
||||
- 'build.gradle'
|
||||
- 'build.sbt'
|
||||
- 'pom.xml'
|
||||
- '{build.gradle,*/build.gradle,*/*/build.gradle}'
|
||||
- '{build.sbt,*/build.sbt,*/*/build.sbt}'
|
||||
- '{pom.xml,*/pom.xml,*/*/pom.xml}'
|
||||
|
||||
gemnasium-python-dependency_scanning:
|
||||
extends: .ds-analyzer
|
||||
|
@ -139,11 +139,11 @@ gemnasium-python-dependency_scanning:
|
|||
$GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
|
||||
$DS_DEFAULT_ANALYZERS =~ /gemnasium-python/
|
||||
exists:
|
||||
- 'requirements.txt'
|
||||
- 'requirements.pip'
|
||||
- 'Pipfile'
|
||||
- 'requires.txt'
|
||||
- 'setup.py'
|
||||
- '{requirements.txt,*/requirements.txt,*/*/requirements.txt}'
|
||||
- '{requirements.pip,*/requirements.pip,*/*/requirements.pip}'
|
||||
- '{Pipfile,*/Pipfile,*/*/Pipfile}'
|
||||
- '{requires.txt,*/requires.txt,*/*/requires.txt}'
|
||||
- '{setup.py,*/setup.py,*/*/setup.py}'
|
||||
# Support passing of $PIP_REQUIREMENTS_FILE
|
||||
# See https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#configuring-specific-analyzers-used-by-dependency-scanning
|
||||
- if: $CI_COMMIT_BRANCH &&
|
||||
|
@ -162,7 +162,7 @@ bundler-audit-dependency_scanning:
|
|||
$GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
|
||||
$DS_DEFAULT_ANALYZERS =~ /bundler-audit/
|
||||
exists:
|
||||
- 'Gemfile.lock'
|
||||
- '{Gemfile.lock,*/Gemfile.lock,*/*/Gemfile.lock}'
|
||||
|
||||
retire-js-dependency_scanning:
|
||||
extends: .ds-analyzer
|
||||
|
@ -175,4 +175,4 @@ retire-js-dependency_scanning:
|
|||
$GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
|
||||
$DS_DEFAULT_ANALYZERS =~ /retire.js/
|
||||
exists:
|
||||
- 'package.json'
|
||||
- '{package.json,*/package.json,*/*/package.json}'
|
||||
|
|
|
@ -18,7 +18,7 @@ module Gitlab
|
|||
PROBLEMS = {
|
||||
subject_too_short: "The %s must contain at least #{MIN_SUBJECT_WORDS_COUNT} words",
|
||||
subject_too_long: "The %s may not be longer than #{MAX_LINE_LENGTH} characters",
|
||||
subject_above_warning: "The %s length is acceptable, but please try to [reduce it to #{WARN_SUBJECT_LENGTH} characters](#{URL_LIMIT_SUBJECT}).",
|
||||
subject_above_warning: "The %s length is acceptable, but please try to [reduce it to #{WARN_SUBJECT_LENGTH} characters](#{URL_LIMIT_SUBJECT})",
|
||||
subject_starts_with_lowercase: "The %s must start with a capital letter",
|
||||
subject_ends_with_a_period: "The %s must not end with a period",
|
||||
separator_missing: "The commit subject and body must be separated by a blank line",
|
||||
|
|
|
@ -18,7 +18,17 @@ module Gitlab
|
|||
end
|
||||
|
||||
def releases
|
||||
@releases ||= json["Releases"] || []
|
||||
@releases = helm_releases
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def helm_releases
|
||||
helm_releases = json['Releases'] || []
|
||||
|
||||
raise ParserError, 'Invalid format for Releases' unless helm_releases.all? { |item| item.is_a?(Hash) }
|
||||
|
||||
helm_releases
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
module Gitlab
|
||||
class OmniauthInitializer
|
||||
OAUTH2_TIMEOUT_SECONDS = 10
|
||||
|
||||
def initialize(devise_config)
|
||||
@devise_config = devise_config
|
||||
end
|
||||
|
@ -15,6 +17,47 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
def default_arguments_for(provider_name)
|
||||
case provider_name
|
||||
when 'cas3'
|
||||
{ on_single_sign_out: cas3_signout_handler }
|
||||
when 'authentiq'
|
||||
{ remote_sign_out_handler: authentiq_signout_handler }
|
||||
when 'shibboleth'
|
||||
{ fail_with_empty_uid: true }
|
||||
when 'google_oauth2'
|
||||
{ client_options: { connection_opts: { request: { timeout: OAUTH2_TIMEOUT_SECONDS } } } }
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cas3_signout_handler
|
||||
lambda do |request|
|
||||
ticket = request.params[:session_index]
|
||||
raise "Service Ticket not found." unless Gitlab::Auth::OAuth::Session.valid?(:cas3, ticket)
|
||||
|
||||
Gitlab::Auth::OAuth::Session.destroy(:cas3, ticket)
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def authentiq_signout_handler
|
||||
lambda do |request|
|
||||
authentiq_session = request.params['sid']
|
||||
if Gitlab::Auth::OAuth::Session.valid?(:authentiq, authentiq_session)
|
||||
Gitlab::Auth::OAuth::Session.destroy(:authentiq, authentiq_session)
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_provider_to_devise(*args)
|
||||
|
@ -33,7 +76,8 @@ module Gitlab
|
|||
# An Array from the configuration will be expanded.
|
||||
provider_arguments.concat provider['args']
|
||||
when Hash
|
||||
hash_arguments = provider['args'].merge(provider_defaults(provider))
|
||||
defaults = provider_defaults(provider)
|
||||
hash_arguments = provider['args'].deep_symbolize_keys.deep_merge(defaults)
|
||||
|
||||
# A Hash from the configuration will be passed as is.
|
||||
provider_arguments << normalize_hash_arguments(hash_arguments)
|
||||
|
@ -43,7 +87,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def normalize_hash_arguments(args)
|
||||
args.symbolize_keys!
|
||||
args.deep_symbolize_keys!
|
||||
|
||||
# Rails 5.1 deprecated the use of string names in the middleware
|
||||
# (https://github.com/rails/rails/commit/83b767ce), so we need to
|
||||
|
@ -66,38 +110,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def provider_defaults(provider)
|
||||
case provider['name']
|
||||
when 'cas3'
|
||||
{ on_single_sign_out: cas3_signout_handler }
|
||||
when 'authentiq'
|
||||
{ remote_sign_out_handler: authentiq_signout_handler }
|
||||
when 'shibboleth'
|
||||
{ fail_with_empty_uid: true }
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def cas3_signout_handler
|
||||
lambda do |request|
|
||||
ticket = request.params[:session_index]
|
||||
raise "Service Ticket not found." unless Gitlab::Auth::OAuth::Session.valid?(:cas3, ticket)
|
||||
|
||||
Gitlab::Auth::OAuth::Session.destroy(:cas3, ticket)
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def authentiq_signout_handler
|
||||
lambda do |request|
|
||||
authentiq_session = request.params['sid']
|
||||
if Gitlab::Auth::OAuth::Session.valid?(:authentiq, authentiq_session)
|
||||
Gitlab::Auth::OAuth::Session.destroy(:authentiq, authentiq_session)
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
self.class.default_arguments_for(provider['name'])
|
||||
end
|
||||
|
||||
def omniauth_customized_providers
|
||||
|
|
|
@ -37,6 +37,10 @@ module GoogleApi
|
|||
Gitlab::Auth::OAuth::Provider.config_for('google_oauth2')
|
||||
end
|
||||
|
||||
def client_options
|
||||
config.args.client_options.deep_symbolize_keys
|
||||
end
|
||||
|
||||
def client
|
||||
return @client if defined?(@client)
|
||||
|
||||
|
@ -49,7 +53,8 @@ module GoogleApi
|
|||
config.app_secret,
|
||||
site: 'https://accounts.google.com',
|
||||
token_url: '/o/oauth2/token',
|
||||
authorize_url: '/o/oauth2/auth'
|
||||
authorize_url: '/o/oauth2/auth',
|
||||
**client_options
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -829,6 +829,9 @@ msgstr ""
|
|||
msgid "<strong>%{group_name}</strong> group members"
|
||||
msgstr ""
|
||||
|
||||
msgid "<strong>%{label_name}</strong> <span>will be permanently deleted from %{subject_name}. This cannot be undone.</span>"
|
||||
msgstr ""
|
||||
|
||||
msgid "<strong>Deletes</strong> source branch"
|
||||
msgstr ""
|
||||
|
||||
|
@ -877,7 +880,7 @@ msgstr ""
|
|||
msgid "A group is a collection of several projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "A group represents your organization in GitLab."
|
||||
msgid "A group represents your organization in GitLab. Groups allow you to manage users and collaborate across multiple projects."
|
||||
msgstr ""
|
||||
|
||||
msgid "A member of the abuse team will review your report as soon as possible."
|
||||
|
@ -4577,6 +4580,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Cluster name is required."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Cluster_applications artifact too big. Maximum allowable size: %{human_size}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
|
||||
msgstr ""
|
||||
|
||||
|
@ -4757,6 +4763,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Helm Tiller"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Helm release failed to install"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Helm streamlines installing and managing Kubernetes applications. Tiller runs inside of your Kubernetes Cluster, and manages releases of your charts."
|
||||
msgstr ""
|
||||
|
||||
|
@ -4919,6 +4928,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|No VPCs found"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|No deployment cluster found for this job"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|No instance type found"
|
||||
msgstr ""
|
||||
|
||||
|
@ -5602,6 +5614,9 @@ msgstr ""
|
|||
msgid "ComplianceFramework|SOX - Sarbanes-Oxley"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceFramework|This project is regulated by %{framework}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Confidence: %{confidence}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -5716,6 +5731,9 @@ msgstr ""
|
|||
msgid "Contact sales to upgrade"
|
||||
msgstr ""
|
||||
|
||||
msgid "Contact support"
|
||||
msgstr ""
|
||||
|
||||
msgid "Container Registry"
|
||||
msgstr ""
|
||||
|
||||
|
@ -6243,9 +6261,6 @@ msgstr ""
|
|||
msgid "Create a Mattermost team for this group"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create a group for your organization"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create a local proxy for storing frequently used upstream images. %{link_start}Learn more%{link_end} about dependency proxies."
|
||||
msgstr ""
|
||||
|
||||
|
@ -6369,6 +6384,9 @@ msgstr ""
|
|||
msgid "Create your first page"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create your group"
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateGroup|You don’t have permission to create a subgroup in this group."
|
||||
msgstr ""
|
||||
|
||||
|
@ -6920,6 +6938,12 @@ msgstr ""
|
|||
msgid "Delete domain"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete label"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete label: %{label_name} ?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete license"
|
||||
msgstr ""
|
||||
|
||||
|
@ -10667,7 +10691,7 @@ msgstr ""
|
|||
msgid "Group name"
|
||||
msgstr ""
|
||||
|
||||
msgid "Group name (Your organization)"
|
||||
msgid "Group name (your organization)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Group overview"
|
||||
|
@ -12729,6 +12753,9 @@ msgstr ""
|
|||
msgid "License|License"
|
||||
msgstr ""
|
||||
|
||||
msgid "License|Licensed user count exceeded"
|
||||
msgstr ""
|
||||
|
||||
msgid "License|You can restore access to the Gold features at any time by upgrading."
|
||||
msgstr ""
|
||||
|
||||
|
@ -12744,6 +12771,9 @@ msgstr ""
|
|||
msgid "License|Your free trial of GitLab Ultimate expired on %{trial_ends_on}."
|
||||
msgstr ""
|
||||
|
||||
msgid "License|Your instance has exceeded your subscription's number of licensed users by %{extra_users_count}. You can continue to add more users and we'll include the overage in your next bill."
|
||||
msgstr ""
|
||||
|
||||
msgid "Limit display of time tracking units to hours."
|
||||
msgstr ""
|
||||
|
||||
|
@ -21806,6 +21836,9 @@ msgstr ""
|
|||
msgid "This commit was signed with an <strong>unverified</strong> signature."
|
||||
msgstr ""
|
||||
|
||||
msgid "This content could not be displayed because %{reason}. You can %{options} instead."
|
||||
msgstr ""
|
||||
|
||||
msgid "This date is after the due date, so this epic won't appear in the roadmap."
|
||||
msgstr ""
|
||||
|
||||
|
@ -22439,6 +22472,9 @@ msgstr ""
|
|||
msgid "Timeout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Timeout connecting to the Google API. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Time|hr"
|
||||
msgid_plural "Time|hrs"
|
||||
msgstr[0] ""
|
||||
|
@ -25564,6 +25600,9 @@ msgstr ""
|
|||
msgid "done"
|
||||
msgstr ""
|
||||
|
||||
msgid "download it"
|
||||
msgstr ""
|
||||
|
||||
msgid "draft"
|
||||
msgid_plural "drafts"
|
||||
msgstr[0] ""
|
||||
|
@ -25768,6 +25807,12 @@ msgstr ""
|
|||
msgid "issues on track"
|
||||
msgstr ""
|
||||
|
||||
msgid "it is larger than %{limit}"
|
||||
msgstr ""
|
||||
|
||||
msgid "it is stored as a job artifact"
|
||||
msgstr ""
|
||||
|
||||
msgid "it is stored externally"
|
||||
msgstr ""
|
||||
|
||||
|
@ -25807,6 +25852,9 @@ msgstr ""
|
|||
msgid "limit of %{project_limit} reached"
|
||||
msgstr ""
|
||||
|
||||
msgid "load it anyway"
|
||||
msgstr ""
|
||||
|
||||
msgid "locked by %{path_lock_user_name} %{created_at}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -26484,6 +26532,9 @@ msgstr ""
|
|||
msgid "view the blob"
|
||||
msgstr ""
|
||||
|
||||
msgid "view the source"
|
||||
msgstr ""
|
||||
|
||||
msgid "vulnerability|Add a comment"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
"@gitlab/svgs": "1.127.0",
|
||||
"@gitlab/ui": "14.10.0",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "^6.0.2-2",
|
||||
"@rails/actioncable": "^6.0.3",
|
||||
"@sentry/browser": "^5.10.2",
|
||||
"@sourcegraph/code-host-integration": "0.0.46",
|
||||
"@toast-ui/editor": "^2.0.1",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Clean up cached files that are older than 4 days
|
||||
find tmp/cache/assets/sprockets/ -type f -mtime +4 -execdir rm -- "{}" \;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
psql -h postgres -U postgres postgres <<EOF
|
||||
CREATE USER gitlab;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
psql -h postgres -U postgres gitlabhq_geo_test <<EOF
|
||||
CREATE EXTENSION postgres_fdw;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source scripts/utils.sh
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
function retrieve_tests_metadata() {
|
||||
mkdir -p knapsack/ rspec_flaky/ rspec_profiling/
|
||||
|
|
|
@ -19,7 +19,7 @@ end
|
|||
|
||||
HOOK_PATH = File.expand_path("../.git/hooks/pre-push", __dir__)
|
||||
HOOK_DATA = <<~HOOK
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
|
|
|
@ -12,10 +12,6 @@ describe GoogleApi::AuthorizationsController do
|
|||
|
||||
before do
|
||||
sign_in(user)
|
||||
|
||||
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |instance|
|
||||
allow(instance).to receive(:get_token).and_return([token, expires_at])
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'access denied' do
|
||||
|
@ -38,6 +34,12 @@ describe GoogleApi::AuthorizationsController do
|
|||
context 'session key matches state param' do
|
||||
let(:state) { session_key }
|
||||
|
||||
before do
|
||||
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |instance|
|
||||
allow(instance).to receive(:get_token).and_return([token, expires_at])
|
||||
end
|
||||
end
|
||||
|
||||
it 'sets token and expires_at in session' do
|
||||
subject
|
||||
|
||||
|
@ -63,6 +65,22 @@ describe GoogleApi::AuthorizationsController do
|
|||
|
||||
it_behaves_like 'access denied'
|
||||
end
|
||||
|
||||
context 'when a Faraday exception occurs' do
|
||||
let(:state) { session_key }
|
||||
|
||||
[::Faraday::TimeoutError, ::Faraday::ConnectionFailed].each do |error|
|
||||
it "sets a flash alert on #{error}" do
|
||||
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |instance|
|
||||
allow(instance).to receive(:get_token).and_raise(error.new(nil))
|
||||
end
|
||||
|
||||
subject
|
||||
|
||||
expect(flash[:alert]).to eq('Timeout connecting to the Google API. Please try again.')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'state param is present, but session key is blank' do
|
||||
|
|
|
@ -252,6 +252,21 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
trait :cluster_applications do
|
||||
file_type { :cluster_applications }
|
||||
file_format { :gzip }
|
||||
|
||||
transient do
|
||||
file do
|
||||
fixture_file_upload(Rails.root.join('spec/fixtures/helm/helm_list_v2_prometheus_missing.json.gz'), 'application/x-gzip')
|
||||
end
|
||||
end
|
||||
|
||||
after(:build) do |artifact, evaluator|
|
||||
artifact.file = evaluator.file
|
||||
end
|
||||
end
|
||||
|
||||
trait :correct_checksum do
|
||||
after(:build) do |artifact, evaluator|
|
||||
artifact.file_sha256 = Digest::SHA256.file(artifact.file.path).hexdigest
|
||||
|
|
|
@ -65,6 +65,10 @@ FactoryBot.define do
|
|||
status_reason { 'something went wrong' }
|
||||
end
|
||||
|
||||
trait :uninstalled do
|
||||
status { 10 }
|
||||
end
|
||||
|
||||
trait :timed_out do
|
||||
installing
|
||||
updated_at { ClusterWaitForAppInstallationWorker::TIMEOUT.ago }
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -15,10 +15,22 @@ function matchAll(str) {
|
|||
|
||||
describe('avatar_helper', () => {
|
||||
describe('getIdenticonBackgroundClass', () => {
|
||||
it('returns identicon bg class from id', () => {
|
||||
it('returns identicon bg class from id that is a number', () => {
|
||||
expect(getIdenticonBackgroundClass(1)).toEqual('bg2');
|
||||
});
|
||||
|
||||
it('returns identicon bg class from id that is a string', () => {
|
||||
expect(getIdenticonBackgroundClass('1')).toEqual('bg2');
|
||||
});
|
||||
|
||||
it('returns identicon bg class from id that is a GraphQL string id', () => {
|
||||
expect(getIdenticonBackgroundClass('gid://gitlab/Project/1')).toEqual('bg2');
|
||||
});
|
||||
|
||||
it('returns identicon bg class from unparsable string', () => {
|
||||
expect(getIdenticonBackgroundClass('gid://gitlab/')).toEqual('bg1');
|
||||
});
|
||||
|
||||
it(`wraps around if id is bigger than ${IDENTICON_BG_COUNT}`, () => {
|
||||
expect(getIdenticonBackgroundClass(IDENTICON_BG_COUNT + 4)).toEqual('bg5');
|
||||
expect(getIdenticonBackgroundClass(IDENTICON_BG_COUNT * 5 + 6)).toEqual('bg7');
|
||||
|
|
|
@ -1,27 +1,60 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import BlobContentError from '~/blob/components/blob_content_error.vue';
|
||||
import { GlSprintf } from '@gitlab/ui';
|
||||
|
||||
import { BLOB_RENDER_ERRORS } from '~/blob/components/constants';
|
||||
|
||||
describe('Blob Content Error component', () => {
|
||||
let wrapper;
|
||||
const viewerError = '<h1 id="error">Foo Error</h1>';
|
||||
|
||||
function createComponent() {
|
||||
function createComponent(props = {}) {
|
||||
wrapper = shallowMount(BlobContentError, {
|
||||
propsData: {
|
||||
viewerError,
|
||||
...props,
|
||||
},
|
||||
stubs: {
|
||||
GlSprintf,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders the passed error without transformations', () => {
|
||||
expect(wrapper.html()).toContain(viewerError);
|
||||
describe('collapsed and too large blobs', () => {
|
||||
it.each`
|
||||
error | reason | options
|
||||
${BLOB_RENDER_ERRORS.REASONS.COLLAPSED} | ${'it is larger than 1.00 MiB'} | ${[BLOB_RENDER_ERRORS.OPTIONS.LOAD.text, BLOB_RENDER_ERRORS.OPTIONS.DOWNLOAD.text]}
|
||||
${BLOB_RENDER_ERRORS.REASONS.TOO_LARGE} | ${'it is larger than 100.00 MiB'} | ${[BLOB_RENDER_ERRORS.OPTIONS.DOWNLOAD.text]}
|
||||
`('renders correct reason for $error.id', ({ error, reason, options }) => {
|
||||
createComponent({
|
||||
viewerError: error.id,
|
||||
});
|
||||
expect(wrapper.text()).toContain(reason);
|
||||
options.forEach(option => {
|
||||
expect(wrapper.text()).toContain(option);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('external blob', () => {
|
||||
it.each`
|
||||
storageType | reason | options
|
||||
${'lfs'} | ${BLOB_RENDER_ERRORS.REASONS.EXTERNAL.text.lfs} | ${[BLOB_RENDER_ERRORS.OPTIONS.DOWNLOAD.text]}
|
||||
${'build_artifact'} | ${BLOB_RENDER_ERRORS.REASONS.EXTERNAL.text.build_artifact} | ${[BLOB_RENDER_ERRORS.OPTIONS.DOWNLOAD.text]}
|
||||
${'default'} | ${BLOB_RENDER_ERRORS.REASONS.EXTERNAL.text.default} | ${[BLOB_RENDER_ERRORS.OPTIONS.DOWNLOAD.text]}
|
||||
`('renders correct reason for $storageType blob', ({ storageType, reason, options }) => {
|
||||
createComponent({
|
||||
viewerError: BLOB_RENDER_ERRORS.REASONS.EXTERNAL.id,
|
||||
blob: {
|
||||
externalStorage: storageType,
|
||||
},
|
||||
});
|
||||
expect(wrapper.text()).toContain(reason);
|
||||
options.forEach(option => {
|
||||
expect(wrapper.text()).toContain(option);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,6 +2,12 @@ import { shallowMount } from '@vue/test-utils';
|
|||
import BlobContent from '~/blob/components/blob_content.vue';
|
||||
import BlobContentError from '~/blob/components/blob_content_error.vue';
|
||||
import {
|
||||
BLOB_RENDER_EVENT_LOAD,
|
||||
BLOB_RENDER_EVENT_SHOW_SOURCE,
|
||||
BLOB_RENDER_ERRORS,
|
||||
} from '~/blob/components/constants';
|
||||
import {
|
||||
Blob,
|
||||
RichViewerMock,
|
||||
SimpleViewerMock,
|
||||
RichBlobContentMock,
|
||||
|
@ -67,4 +73,32 @@ describe('Blob Content component', () => {
|
|||
expect(wrapper.find(viewer).html()).toContain(content);
|
||||
});
|
||||
});
|
||||
|
||||
describe('functionality', () => {
|
||||
describe('render error', () => {
|
||||
const findErrorEl = () => wrapper.find(BlobContentError);
|
||||
const renderError = BLOB_RENDER_ERRORS.REASONS.COLLAPSED.id;
|
||||
const viewer = { ...SimpleViewerMock, renderError };
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent({ blob: Blob }, viewer);
|
||||
});
|
||||
|
||||
it('correctly sets blob on the blob-content-error component', () => {
|
||||
expect(findErrorEl().props('blob')).toEqual(Blob);
|
||||
});
|
||||
|
||||
it(`properly proxies ${BLOB_RENDER_EVENT_LOAD} event`, () => {
|
||||
expect(wrapper.emitted(BLOB_RENDER_EVENT_LOAD)).toBeUndefined();
|
||||
findErrorEl().vm.$emit(BLOB_RENDER_EVENT_LOAD);
|
||||
expect(wrapper.emitted(BLOB_RENDER_EVENT_LOAD)).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`properly proxies ${BLOB_RENDER_EVENT_SHOW_SOURCE} event`, () => {
|
||||
expect(wrapper.emitted(BLOB_RENDER_EVENT_SHOW_SOURCE)).toBeUndefined();
|
||||
findErrorEl().vm.$emit(BLOB_RENDER_EVENT_SHOW_SOURCE);
|
||||
expect(wrapper.emitted(BLOB_RENDER_EVENT_SHOW_SOURCE)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,6 +3,7 @@ import SnippetBlobView from '~/snippets/components/snippet_blob_view.vue';
|
|||
import BlobHeader from '~/blob/components/blob_header.vue';
|
||||
import BlobEmbeddable from '~/blob/components/blob_embeddable.vue';
|
||||
import BlobContent from '~/blob/components/blob_content.vue';
|
||||
import { BLOB_RENDER_EVENT_LOAD, BLOB_RENDER_EVENT_SHOW_SOURCE } from '~/blob/components/constants';
|
||||
import { RichViewer, SimpleViewer } from '~/vue_shared/components/blob_viewers';
|
||||
import {
|
||||
SNIPPET_VISIBILITY_PRIVATE,
|
||||
|
@ -29,6 +30,8 @@ describe('Blob Embeddable', () => {
|
|||
queries: {
|
||||
blobContent: {
|
||||
loading: contentLoading,
|
||||
refetch: jest.fn(),
|
||||
skip: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -143,4 +146,35 @@ describe('Blob Embeddable', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('functionality', () => {
|
||||
describe('render error', () => {
|
||||
const findContentEl = () => wrapper.find(BlobContent);
|
||||
|
||||
it('correctly sets blob on the blob-content-error component', () => {
|
||||
createComponent();
|
||||
expect(findContentEl().props('blob')).toEqual(BlobMock);
|
||||
});
|
||||
|
||||
it(`refetches blob content on ${BLOB_RENDER_EVENT_LOAD} event`, () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.vm.$apollo.queries.blobContent.refetch).not.toHaveBeenCalled();
|
||||
findContentEl().vm.$emit(BLOB_RENDER_EVENT_LOAD);
|
||||
expect(wrapper.vm.$apollo.queries.blobContent.refetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it(`sets '${SimpleViewerMock.type}' as active on ${BLOB_RENDER_EVENT_SHOW_SOURCE} event`, () => {
|
||||
createComponent(
|
||||
{},
|
||||
{
|
||||
activeViewerType: RichViewerMock.type,
|
||||
},
|
||||
);
|
||||
|
||||
findContentEl().vm.$emit(BLOB_RENDER_EVENT_SHOW_SOURCE);
|
||||
expect(wrapper.vm.activeViewerType).toEqual(SimpleViewerMock.type);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Identicon matches snapshot 1`] = `
|
||||
exports[`Identicon entity id is a GraphQL id matches snapshot 1`] = `
|
||||
<div
|
||||
class="avatar identicon s40 bg2"
|
||||
>
|
||||
|
||||
E
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Identicon entity id is a number matches snapshot 1`] = `
|
||||
<div
|
||||
class="avatar identicon s40 bg2"
|
||||
>
|
||||
|
|
|
@ -4,12 +4,17 @@ import IdenticonComponent from '~/vue_shared/components/identicon.vue';
|
|||
describe('Identicon', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = () => {
|
||||
const defaultProps = {
|
||||
entityId: 1,
|
||||
entityName: 'entity-name',
|
||||
sizeClass: 's40',
|
||||
};
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = shallowMount(IdenticonComponent, {
|
||||
propsData: {
|
||||
entityId: 1,
|
||||
entityName: 'entity-name',
|
||||
sizeClass: 's40',
|
||||
...defaultProps,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -19,15 +24,27 @@ describe('Identicon', () => {
|
|||
wrapper = null;
|
||||
});
|
||||
|
||||
it('matches snapshot', () => {
|
||||
createComponent();
|
||||
describe('entity id is a number', () => {
|
||||
beforeEach(createComponent);
|
||||
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
it('matches snapshot', () => {
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('adds a correct class to identicon', () => {
|
||||
expect(wrapper.find({ ref: 'identicon' }).classes()).toContain('bg2');
|
||||
});
|
||||
});
|
||||
|
||||
it('adds a correct class to identicon', () => {
|
||||
createComponent();
|
||||
describe('entity id is a GraphQL id', () => {
|
||||
beforeEach(() => createComponent({ entityId: 'gid://gitlab/Project/8' }));
|
||||
|
||||
expect(wrapper.find({ ref: 'identicon' }).classes()).toContain('bg2');
|
||||
it('matches snapshot', () => {
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('adds a correct class to identicon', () => {
|
||||
expect(wrapper.find({ ref: 'identicon' }).classes()).toContain('bg2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -71,6 +71,28 @@ describe ApplicationHelper do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#admin_section?' do
|
||||
context 'when controller is under the admin namespace' do
|
||||
before do
|
||||
allow(helper).to receive(:controller).and_return(Admin::UsersController.new)
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
expect(helper.admin_section?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when controller is not under the admin namespace' do
|
||||
before do
|
||||
allow(helper).to receive(:controller).and_return(UsersController.new)
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
expect(helper.admin_section?).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'simple_sanitize' do
|
||||
let(:a_tag) { '<a href="#">Foo</a>' }
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ describe Gitlab::Auth::OAuth::Provider do
|
|||
context 'for an OmniAuth provider' do
|
||||
before do
|
||||
provider = OpenStruct.new(
|
||||
name: 'google',
|
||||
name: 'google_oauth2',
|
||||
app_id: 'asd123',
|
||||
app_secret: 'asd123'
|
||||
)
|
||||
|
@ -71,8 +71,16 @@ describe Gitlab::Auth::OAuth::Provider do
|
|||
end
|
||||
|
||||
context 'when the provider exists' do
|
||||
subject { described_class.config_for('google_oauth2') }
|
||||
|
||||
it 'returns the config' do
|
||||
expect(described_class.config_for('google')).to be_a(OpenStruct)
|
||||
expect(subject).to be_a(OpenStruct)
|
||||
end
|
||||
|
||||
it 'merges defaults with the given configuration' do
|
||||
defaults = Gitlab::OmniauthInitializer.default_arguments_for('google_oauth2').deep_stringify_keys
|
||||
|
||||
expect(subject['args']).to include(defaults)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -82,5 +82,19 @@ describe Gitlab::Kubernetes::Helm::Parsers::ListV2 do
|
|||
expect(list_v2.releases).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid Releases' do
|
||||
let(:invalid_file_contents) do
|
||||
'{ "Releases" : ["a", "b"] }'
|
||||
end
|
||||
|
||||
subject(:list_v2) { described_class.new(invalid_file_contents) }
|
||||
|
||||
it 'raises an error' do
|
||||
expect do
|
||||
list_v2.releases
|
||||
end.to raise_error(described_class::ParserError, 'Invalid format for Releases')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -86,6 +86,22 @@ describe Gitlab::OmniauthInitializer do
|
|||
subject.execute([cas3_config])
|
||||
end
|
||||
|
||||
it 'configures defaults for google_oauth2' do
|
||||
google_config = {
|
||||
'name' => 'google_oauth2',
|
||||
"args" => { "access_type" => "offline", "approval_prompt" => '' }
|
||||
}
|
||||
|
||||
expect(devise_config).to receive(:omniauth).with(
|
||||
:google_oauth2,
|
||||
access_type: "offline",
|
||||
approval_prompt: "",
|
||||
client_options: { connection_opts: { request: { timeout: Gitlab::OmniauthInitializer::OAUTH2_TIMEOUT_SECONDS } } }
|
||||
)
|
||||
|
||||
subject.execute([google_config])
|
||||
end
|
||||
|
||||
it 'converts client_auth_method to a Symbol for openid_connect' do
|
||||
openid_connect_config = {
|
||||
'name' => 'openid_connect',
|
||||
|
|
|
@ -40,5 +40,19 @@ describe GoogleApi::Auth do
|
|||
expect(token).to eq('token')
|
||||
expect(expires_at).to eq('expires_at')
|
||||
end
|
||||
|
||||
it 'expects the client to receive default options' do
|
||||
config = Gitlab::Auth::OAuth::Provider.config_for('google_oauth2')
|
||||
|
||||
expect(OAuth2::Client).to receive(:new).with(
|
||||
config.app_id,
|
||||
config.app_secret,
|
||||
hash_including(
|
||||
**config.args.client_options.deep_symbolize_keys
|
||||
)
|
||||
).and_call_original
|
||||
|
||||
client.get_token('xxx')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -590,6 +590,60 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#find_or_build_application' do
|
||||
let_it_be(:cluster, reload: true) { create(:cluster) }
|
||||
|
||||
it 'rejects classes that are not applications' do
|
||||
expect do
|
||||
cluster.find_or_build_application(Project)
|
||||
end.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
context 'when none of applications are created' do
|
||||
it 'returns the new application', :aggregate_failures do
|
||||
described_class::APPLICATIONS.values.each do |application_class|
|
||||
application = cluster.find_or_build_application(application_class)
|
||||
|
||||
expect(application).to be_a(application_class)
|
||||
expect(application).not_to be_persisted
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when application is persisted' do
|
||||
let!(:helm) { create(:clusters_applications_helm, cluster: cluster) }
|
||||
let!(:ingress) { create(:clusters_applications_ingress, cluster: cluster) }
|
||||
let!(:cert_manager) { create(:clusters_applications_cert_manager, cluster: cluster) }
|
||||
let!(:crossplane) { create(:clusters_applications_crossplane, cluster: cluster) }
|
||||
let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) }
|
||||
let!(:runner) { create(:clusters_applications_runner, cluster: cluster) }
|
||||
let!(:jupyter) { create(:clusters_applications_jupyter, cluster: cluster) }
|
||||
let!(:knative) { create(:clusters_applications_knative, cluster: cluster) }
|
||||
let!(:elastic_stack) { create(:clusters_applications_elastic_stack, cluster: cluster) }
|
||||
let!(:fluentd) { create(:clusters_applications_fluentd, cluster: cluster) }
|
||||
|
||||
it 'returns the persisted application', :aggregate_failures do
|
||||
{
|
||||
Clusters::Applications::Helm => helm,
|
||||
Clusters::Applications::Ingress => ingress,
|
||||
Clusters::Applications::CertManager => cert_manager,
|
||||
Clusters::Applications::Crossplane => crossplane,
|
||||
Clusters::Applications::Prometheus => prometheus,
|
||||
Clusters::Applications::Runner => runner,
|
||||
Clusters::Applications::Jupyter => jupyter,
|
||||
Clusters::Applications::Knative => knative,
|
||||
Clusters::Applications::ElasticStack => elastic_stack,
|
||||
Clusters::Applications::Fluentd => fluentd
|
||||
}.each do |application_class, expected_object|
|
||||
application = cluster.find_or_build_application(application_class)
|
||||
|
||||
expect(application).to eq(expected_object)
|
||||
expect(application).to be_persisted
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#allow_user_defined_namespace?' do
|
||||
subject { cluster.allow_user_defined_namespace? }
|
||||
|
||||
|
|
|
@ -224,6 +224,20 @@ describe API::ProjectSnippets do
|
|||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
|
||||
context 'when save fails because the repository could not be created' do
|
||||
before do
|
||||
allow_next_instance_of(Snippets::CreateService) do |instance|
|
||||
allow(instance).to receive(:create_repository).and_raise(Snippets::CreateService::CreateRepositoryError)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns 400' do
|
||||
post api("/projects/#{project.id}/snippets", admin), params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the snippet is spam' do
|
||||
def create_snippet(project, snippet_params = {})
|
||||
project.add_developer(user)
|
||||
|
|
|
@ -267,6 +267,28 @@ describe API::Snippets do
|
|||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
|
||||
it 'returns 400 for validation errors' do
|
||||
params[:title] = ''
|
||||
|
||||
post api("/snippets/", user), params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
|
||||
context 'when save fails because the repository could not be created' do
|
||||
before do
|
||||
allow_next_instance_of(Snippets::CreateService) do |instance|
|
||||
allow(instance).to receive(:create_repository).and_raise(Snippets::CreateService::CreateRepositoryError)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns 400' do
|
||||
post api("/snippets/", user), params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the snippet is spam' do
|
||||
def create_snippet(snippet_params = {})
|
||||
post api('/snippets', user), params: params.merge(snippet_params)
|
||||
|
@ -356,6 +378,12 @@ describe API::Snippets do
|
|||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
|
||||
it 'returns 400 for validation errors' do
|
||||
update_snippet(params: { title: '' })
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
|
||||
it_behaves_like 'update with repository actions' do
|
||||
let(:snippet_without_repo) { create(:personal_snippet, author: user, visibility_level: visibility_level) }
|
||||
end
|
||||
|
|
|
@ -177,6 +177,53 @@ describe Ci::CreateJobArtifactsService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when artifact type is cluster_applications' do
|
||||
let(:artifacts_file) do
|
||||
file_to_upload('spec/fixtures/helm/helm_list_v2_prometheus_missing.json.gz', sha256: artifacts_sha256)
|
||||
end
|
||||
|
||||
let(:params) do
|
||||
{
|
||||
'artifact_type' => 'cluster_applications',
|
||||
'artifact_format' => 'gzip'
|
||||
}
|
||||
end
|
||||
|
||||
it 'calls cluster applications parse service' do
|
||||
expect_next_instance_of(Clusters::ParseClusterApplicationsArtifactService) do |service|
|
||||
expect(service).to receive(:execute).once.and_call_original
|
||||
end
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
context 'when there is a deployment cluster' do
|
||||
let(:user) { project.owner }
|
||||
|
||||
before do
|
||||
job.update!(user: user)
|
||||
end
|
||||
|
||||
it 'calls cluster applications parse service with job and job user', :aggregate_failures do
|
||||
expect(Clusters::ParseClusterApplicationsArtifactService).to receive(:new).with(job, user).and_call_original
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ci_synchronous_artifact_parsing feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_synchronous_artifact_parsing: false)
|
||||
end
|
||||
|
||||
it 'does not call parse service' do
|
||||
expect(Clusters::ParseClusterApplicationsArtifactService).not_to receive(:new)
|
||||
|
||||
expect(subject[:status]).to eq(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'rescues object storage error' do |klass, message, expected_message|
|
||||
it "handles #{klass}" do
|
||||
allow_next_instance_of(JobArtifactUploader) do |uploader|
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Clusters::ParseClusterApplicationsArtifactService do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
describe 'RELEASE_NAMES' do
|
||||
it 'is included in Cluster application names', :aggregate_failures do
|
||||
described_class::RELEASE_NAMES.each do |release_name|
|
||||
expect(Clusters::Cluster::APPLICATIONS).to include(release_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.new' do
|
||||
let(:job) { build(:ci_build) }
|
||||
|
||||
it 'sets the project and current user', :aggregate_failures do
|
||||
service = described_class.new(job, user)
|
||||
|
||||
expect(service.project).to eq(job.project)
|
||||
expect(service.current_user).to eq(user)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
let_it_be(:cluster, reload: true) { create(:cluster, projects: [project]) }
|
||||
let_it_be(:deployment, reload: true) { create(:deployment, cluster: cluster) }
|
||||
|
||||
let(:job) { deployment.deployable }
|
||||
let(:artifact) { create(:ci_job_artifact, :cluster_applications, job: job) }
|
||||
|
||||
context 'when cluster_applications_artifact feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(cluster_applications_artifact: false)
|
||||
end
|
||||
|
||||
it 'does not call Gitlab::Kubernetes::Helm::Parsers::ListV2 and returns success immediately' do
|
||||
expect(Gitlab::Kubernetes::Helm::Parsers::ListV2).not_to receive(:new)
|
||||
|
||||
result = described_class.new(job, user).execute(artifact)
|
||||
|
||||
expect(result[:status]).to eq(:success)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when cluster_applications_artifact feature flag is enabled for project' do
|
||||
before do
|
||||
stub_feature_flags(cluster_applications_artifact: job.project)
|
||||
end
|
||||
|
||||
it 'calls Gitlab::Kubernetes::Helm::Parsers::ListV2' do
|
||||
expect(Gitlab::Kubernetes::Helm::Parsers::ListV2).to receive(:new).and_call_original
|
||||
|
||||
result = described_class.new(job, user).execute(artifact)
|
||||
|
||||
expect(result[:status]).to eq(:success)
|
||||
end
|
||||
|
||||
context 'artifact is not of cluster_applications type' do
|
||||
let(:artifact) { create(:ci_job_artifact, :archive) }
|
||||
let(:job) { artifact.job }
|
||||
|
||||
it 'raise ArgumentError' do
|
||||
expect do
|
||||
described_class.new(job, user).execute(artifact)
|
||||
end.to raise_error(ArgumentError, 'Artifact is not cluster_applications file type')
|
||||
end
|
||||
end
|
||||
|
||||
context 'artifact exceeds acceptable size' do
|
||||
it 'returns an error' do
|
||||
stub_const("#{described_class}::MAX_ACCEPTABLE_ARTIFACT_SIZE", 1.byte)
|
||||
|
||||
result = described_class.new(job, user).execute(artifact)
|
||||
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect(result[:message]).to eq('Cluster_applications artifact too big. Maximum allowable size: 1 Byte')
|
||||
end
|
||||
end
|
||||
|
||||
context 'job has no deployment cluster' do
|
||||
let(:job) { build(:ci_build) }
|
||||
|
||||
it 'returns an error' do
|
||||
result = described_class.new(job, user).execute(artifact)
|
||||
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect(result[:message]).to eq('No deployment cluster found for this job')
|
||||
end
|
||||
end
|
||||
|
||||
context 'job has deployment cluster' do
|
||||
context 'current user does not have access to deployment cluster' do
|
||||
let(:other_user) { create(:user) }
|
||||
|
||||
it 'returns an error' do
|
||||
result = described_class.new(job, other_user).execute(artifact)
|
||||
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect(result[:message]).to eq('No deployment cluster found for this job')
|
||||
end
|
||||
end
|
||||
|
||||
context 'release is missing' do
|
||||
let(:fixture) { 'spec/fixtures/helm/helm_list_v2_prometheus_missing.json.gz' }
|
||||
let(:file) { fixture_file_upload(Rails.root.join(fixture)) }
|
||||
let(:artifact) { create(:ci_job_artifact, :cluster_applications, job: job, file: file) }
|
||||
|
||||
context 'application does not exist' do
|
||||
it 'does not create or destroy an application' do
|
||||
expect do
|
||||
described_class.new(job, user).execute(artifact)
|
||||
end.not_to change(Clusters::Applications::Prometheus, :count)
|
||||
end
|
||||
end
|
||||
|
||||
context 'application exists' do
|
||||
before do
|
||||
create(:clusters_applications_prometheus, :installed, cluster: cluster)
|
||||
end
|
||||
|
||||
it 'marks the application as uninstalled' do
|
||||
described_class.new(job, user).execute(artifact)
|
||||
|
||||
cluster.application_prometheus.reload
|
||||
expect(cluster.application_prometheus).to be_uninstalled
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'release is deployed' do
|
||||
let(:fixture) { 'spec/fixtures/helm/helm_list_v2_prometheus_deployed.json.gz' }
|
||||
let(:file) { fixture_file_upload(Rails.root.join(fixture)) }
|
||||
let(:artifact) { create(:ci_job_artifact, :cluster_applications, job: job, file: file) }
|
||||
|
||||
context 'application does not exist' do
|
||||
it 'creates an application and marks it as installed' do
|
||||
expect do
|
||||
described_class.new(job, user).execute(artifact)
|
||||
end.to change(Clusters::Applications::Prometheus, :count)
|
||||
|
||||
expect(cluster.application_prometheus).to be_persisted
|
||||
expect(cluster.application_prometheus).to be_installed
|
||||
end
|
||||
end
|
||||
|
||||
context 'application exists' do
|
||||
before do
|
||||
create(:clusters_applications_prometheus, :errored, cluster: cluster)
|
||||
end
|
||||
|
||||
it 'marks the application as installed' do
|
||||
described_class.new(job, user).execute(artifact)
|
||||
|
||||
expect(cluster.application_prometheus).to be_installed
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'release is failed' do
|
||||
let(:fixture) { 'spec/fixtures/helm/helm_list_v2_prometheus_failed.json.gz' }
|
||||
let(:file) { fixture_file_upload(Rails.root.join(fixture)) }
|
||||
let(:artifact) { create(:ci_job_artifact, :cluster_applications, job: job, file: file) }
|
||||
|
||||
context 'application does not exist' do
|
||||
it 'creates an application and marks it as errored' do
|
||||
expect do
|
||||
described_class.new(job, user).execute(artifact)
|
||||
end.to change(Clusters::Applications::Prometheus, :count)
|
||||
|
||||
expect(cluster.application_prometheus).to be_persisted
|
||||
expect(cluster.application_prometheus).to be_errored
|
||||
expect(cluster.application_prometheus.status_reason).to eq('Helm release failed to install')
|
||||
end
|
||||
end
|
||||
|
||||
context 'application exists' do
|
||||
before do
|
||||
create(:clusters_applications_prometheus, :installed, cluster: cluster)
|
||||
end
|
||||
|
||||
it 'marks the application as errored' do
|
||||
described_class.new(job, user).execute(artifact)
|
||||
|
||||
expect(cluster.application_prometheus).to be_errored
|
||||
expect(cluster.application_prometheus.status_reason).to eq('Helm release failed to install')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -194,6 +194,66 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
|
|||
end
|
||||
end
|
||||
|
||||
describe '#make_externally_installed' do
|
||||
subject { create(application_name, :installing) }
|
||||
|
||||
it 'is installed' do
|
||||
subject.make_externally_installed
|
||||
|
||||
expect(subject).to be_installed
|
||||
end
|
||||
|
||||
context 'application is updated' do
|
||||
subject { create(application_name, :updated) }
|
||||
|
||||
it 'is installed' do
|
||||
subject.make_externally_installed
|
||||
|
||||
expect(subject).to be_installed
|
||||
end
|
||||
end
|
||||
|
||||
context 'application is errored' do
|
||||
subject { create(application_name, :errored) }
|
||||
|
||||
it 'is installed' do
|
||||
subject.make_externally_installed
|
||||
|
||||
expect(subject).to be_installed
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#make_externally_uninstalled' do
|
||||
subject { create(application_name, :installed) }
|
||||
|
||||
it 'is uninstalled' do
|
||||
subject.make_externally_uninstalled
|
||||
|
||||
expect(subject).to be_uninstalled
|
||||
end
|
||||
|
||||
context 'application is updated' do
|
||||
subject { create(application_name, :updated) }
|
||||
|
||||
it 'is uninstalled' do
|
||||
subject.make_externally_uninstalled
|
||||
|
||||
expect(subject).to be_uninstalled
|
||||
end
|
||||
end
|
||||
|
||||
context 'application is errored' do
|
||||
subject { create(application_name, :errored) }
|
||||
|
||||
it 'is uninstalled' do
|
||||
subject.make_externally_uninstalled
|
||||
|
||||
expect(subject).to be_uninstalled
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#make_scheduled' do
|
||||
subject { create(application_name, :installable) }
|
||||
|
||||
|
@ -278,6 +338,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
|
|||
:update_errored | false
|
||||
:uninstalling | false
|
||||
:uninstall_errored | false
|
||||
:uninstalled | false
|
||||
:timed_out | false
|
||||
end
|
||||
|
||||
|
|
|
@ -48,6 +48,28 @@ RSpec.shared_examples 'update with repository actions' do
|
|||
expect(blob).not_to be_nil
|
||||
expect(blob.data).to eq content
|
||||
end
|
||||
|
||||
context 'when save fails due to a repository creation error' do
|
||||
let(:content) { 'File content' }
|
||||
let(:file_name) { 'test.md' }
|
||||
|
||||
before do
|
||||
allow_next_instance_of(Snippets::UpdateService) do |instance|
|
||||
allow(instance).to receive(:create_repository_for).with(snippet).and_raise(Snippets::UpdateService::CreateRepositoryError)
|
||||
end
|
||||
|
||||
update_snippet(snippet_id: snippet.id, params: { content: content, file_name: file_name })
|
||||
end
|
||||
|
||||
it 'returns 400' do
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
|
||||
it 'does not save the changes to the snippet object' do
|
||||
expect(snippet.content).not_to eq(content)
|
||||
expect(snippet.file_name).not_to eq(file_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
|
||||
IFS=$'\n\t'
|
||||
set -euo pipefail
|
||||
|
|
|
@ -983,10 +983,10 @@
|
|||
consola "^2.10.1"
|
||||
node-fetch "^2.6.0"
|
||||
|
||||
"@rails/actioncable@^6.0.2-2":
|
||||
version "6.0.2-2"
|
||||
resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.0.2-2.tgz#237907f8111707950381387c273b19ac25958408"
|
||||
integrity sha512-0sKStf8hnberH1TKup10PJ92JT2dVqf3gf+OT4lJ7DiYSBEuDcvICHxWsyML2oWTpjUhC4kLvUJ3pXL2JJrJuQ==
|
||||
"@rails/actioncable@^6.0.3":
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.0.3.tgz#722b4b639936129307ddbab3a390f6bcacf3e7bc"
|
||||
integrity sha512-I01hgqxxnOgOtJTGlq0ZsGJYiTEEiSGVEGQn3vimZSqEP1HqzyFNbzGTq14Xdyeow2yGJjygjoFF1pmtE+SQaw==
|
||||
|
||||
"@sentry/browser@^5.10.2":
|
||||
version "5.10.2"
|
||||
|
|
Loading…
Reference in New Issue