Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5982b74e32
commit
14245e7755
63 changed files with 693 additions and 551 deletions
|
@ -1,9 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlFormInput } from '@gitlab/ui';
|
import { GlFormInput, GlButton } from '@gitlab/ui';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
GlFormInput,
|
GlFormInput,
|
||||||
|
GlButton,
|
||||||
},
|
},
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: {
|
props: {
|
||||||
|
@ -11,6 +12,16 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
canDelete: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
showDelete: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -21,17 +32,27 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="js-file-title file-title-flex-parent">
|
<div class="js-file-title file-title-flex-parent">
|
||||||
<gl-form-input
|
<div class="gl-display-flex gl-align-items-center gl-w-full">
|
||||||
id="snippet_file_name"
|
<gl-form-input
|
||||||
v-model="name"
|
v-model="name"
|
||||||
:placeholder="
|
:placeholder="
|
||||||
s__('Snippets|Give your file a name to add code highlighting, e.g. example.rb for Ruby')
|
s__('Snippets|Give your file a name to add code highlighting, e.g. example.rb for Ruby')
|
||||||
"
|
"
|
||||||
name="snippet_file_name"
|
name="snippet_file_name"
|
||||||
class="form-control js-snippet-file-name"
|
class="form-control js-snippet-file-name"
|
||||||
type="text"
|
type="text"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
@change="$emit('input', name)"
|
@change="$emit('input', name)"
|
||||||
/>
|
/>
|
||||||
|
<gl-button
|
||||||
|
v-if="showDelete"
|
||||||
|
class="gl-ml-4"
|
||||||
|
variant="danger"
|
||||||
|
category="secondary"
|
||||||
|
:disabled="!canDelete"
|
||||||
|
@click="$emit('delete')"
|
||||||
|
>{{ s__('Snippets|Delete file') }}</gl-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -24,9 +24,9 @@ export default {
|
||||||
class="settings no-animate qa-incident-management-settings"
|
class="settings no-animate qa-incident-management-settings"
|
||||||
>
|
>
|
||||||
<div class="settings-header">
|
<div class="settings-header">
|
||||||
<h3 ref="sectionHeader" class="h4">
|
<h4 ref="sectionHeader" class="gl-my-3! gl-py-1">
|
||||||
{{ $options.i18n.headerText }}
|
{{ $options.i18n.headerText }}
|
||||||
</h3>
|
</h4>
|
||||||
<gl-button ref="toggleBtn" class="js-settings-toggle">{{
|
<gl-button ref="toggleBtn" class="js-settings-toggle">{{
|
||||||
$options.i18n.expandBtnLabel
|
$options.i18n.expandBtnLabel
|
||||||
}}</gl-button>
|
}}</gl-button>
|
||||||
|
|
|
@ -54,7 +54,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
strings: {
|
strings: {
|
||||||
deleteProject: __('Remove project'),
|
deleteProject: __('Delete project'),
|
||||||
title: __('Delete project. Are you ABSOLUTELY SURE?'),
|
title: __('Delete project. Are you ABSOLUTELY SURE?'),
|
||||||
confirmText: __('Please type the following to confirm:'),
|
confirmText: __('Please type the following to confirm:'),
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {
|
||||||
SNIPPET_BLOB_ACTION_UPDATE,
|
SNIPPET_BLOB_ACTION_UPDATE,
|
||||||
SNIPPET_BLOB_ACTION_MOVE,
|
SNIPPET_BLOB_ACTION_MOVE,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
import SnippetBlobEdit from './snippet_blob_edit.vue';
|
import SnippetBlobActionsEdit from './snippet_blob_actions_edit.vue';
|
||||||
import SnippetVisibilityEdit from './snippet_visibility_edit.vue';
|
import SnippetVisibilityEdit from './snippet_visibility_edit.vue';
|
||||||
import SnippetDescriptionEdit from './snippet_description_edit.vue';
|
import SnippetDescriptionEdit from './snippet_description_edit.vue';
|
||||||
import { SNIPPET_MARK_EDIT_APP_START } from '~/performance_constants';
|
import { SNIPPET_MARK_EDIT_APP_START } from '~/performance_constants';
|
||||||
|
@ -27,7 +27,7 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
SnippetDescriptionEdit,
|
SnippetDescriptionEdit,
|
||||||
SnippetVisibilityEdit,
|
SnippetVisibilityEdit,
|
||||||
SnippetBlobEdit,
|
SnippetBlobActionsEdit,
|
||||||
TitleField,
|
TitleField,
|
||||||
FormFooterActions,
|
FormFooterActions,
|
||||||
GlButton,
|
GlButton,
|
||||||
|
@ -261,15 +261,7 @@ export default {
|
||||||
:markdown-preview-path="markdownPreviewPath"
|
:markdown-preview-path="markdownPreviewPath"
|
||||||
:markdown-docs-path="markdownDocsPath"
|
:markdown-docs-path="markdownDocsPath"
|
||||||
/>
|
/>
|
||||||
<template v-if="blobs.length">
|
<snippet-blob-actions-edit :blobs="blobs" @blob-updated="updateBlobActions" />
|
||||||
<snippet-blob-edit
|
|
||||||
v-for="blob in blobs"
|
|
||||||
:key="blob.name"
|
|
||||||
:blob="blob"
|
|
||||||
@blob-updated="updateBlobActions"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<snippet-blob-edit v-else @blob-updated="updateBlobActions" />
|
|
||||||
|
|
||||||
<snippet-visibility-edit
|
<snippet-visibility-edit
|
||||||
v-model="snippet.visibilityLevel"
|
v-model="snippet.visibilityLevel"
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
<script>
|
||||||
|
import SnippetBlobEdit from './snippet_blob_edit.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
SnippetBlobEdit,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
blobs: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="form-group file-editor">
|
||||||
|
<label for="snippet_file_path">{{ s__('Snippets|File') }}</label>
|
||||||
|
<template v-if="blobs.length">
|
||||||
|
<snippet-blob-edit v-for="blob in blobs" :key="blob.name" :blob="blob" v-on="$listeners" />
|
||||||
|
</template>
|
||||||
|
<snippet-blob-edit v-else v-on="$listeners" />
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -91,17 +91,18 @@ export default {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="form-group file-editor">
|
<div class="file-holder snippet">
|
||||||
<label>{{ s__('Snippets|File') }}</label>
|
<blob-header-edit
|
||||||
<div class="file-holder snippet">
|
id="snippet_file_path"
|
||||||
<blob-header-edit v-model="filePath" data-qa-selector="file_name_field" />
|
v-model="filePath"
|
||||||
<gl-loading-icon
|
data-qa-selector="file_name_field"
|
||||||
v-if="isContentLoading"
|
/>
|
||||||
:label="__('Loading snippet')"
|
<gl-loading-icon
|
||||||
size="lg"
|
v-if="isContentLoading"
|
||||||
class="loading-animation prepend-top-20 append-bottom-20"
|
:label="__('Loading snippet')"
|
||||||
/>
|
size="lg"
|
||||||
<blob-content-edit v-else v-model="content" :file-global-id="id" :file-name="filePath" />
|
class="loading-animation prepend-top-20 append-bottom-20"
|
||||||
</div>
|
/>
|
||||||
|
<blob-content-edit v-else v-model="content" :file-global-id="id" :file-name="filePath" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -30,3 +30,4 @@ export const SNIPPET_BLOB_CONTENT_FETCH_ERROR = __("Can't fetch content for the
|
||||||
export const SNIPPET_BLOB_ACTION_CREATE = 'create';
|
export const SNIPPET_BLOB_ACTION_CREATE = 'create';
|
||||||
export const SNIPPET_BLOB_ACTION_UPDATE = 'update';
|
export const SNIPPET_BLOB_ACTION_UPDATE = 'update';
|
||||||
export const SNIPPET_BLOB_ACTION_MOVE = 'move';
|
export const SNIPPET_BLOB_ACTION_MOVE = 'move';
|
||||||
|
export const SNIPPET_BLOB_ACTION_DELETE = 'delete';
|
||||||
|
|
66
app/assets/javascripts/snippets/utils/blob.js
Normal file
66
app/assets/javascripts/snippets/utils/blob.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import { uniqueId } from 'lodash';
|
||||||
|
import {
|
||||||
|
SNIPPET_BLOB_ACTION_CREATE,
|
||||||
|
SNIPPET_BLOB_ACTION_UPDATE,
|
||||||
|
SNIPPET_BLOB_ACTION_MOVE,
|
||||||
|
SNIPPET_BLOB_ACTION_DELETE,
|
||||||
|
} from '../constants';
|
||||||
|
|
||||||
|
const createLocalId = () => uniqueId('blob_local_');
|
||||||
|
|
||||||
|
export const decorateBlob = blob => ({
|
||||||
|
...blob,
|
||||||
|
id: createLocalId(),
|
||||||
|
isLoaded: false,
|
||||||
|
content: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createBlob = () => ({
|
||||||
|
id: createLocalId(),
|
||||||
|
content: '',
|
||||||
|
path: '',
|
||||||
|
isLoaded: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const diff = ({ content, path }, origBlob) => {
|
||||||
|
if (!origBlob) {
|
||||||
|
return {
|
||||||
|
action: SNIPPET_BLOB_ACTION_CREATE,
|
||||||
|
previousPath: path,
|
||||||
|
content,
|
||||||
|
filePath: path,
|
||||||
|
};
|
||||||
|
} else if (origBlob.path !== path || origBlob.content !== content) {
|
||||||
|
return {
|
||||||
|
action: origBlob.path === path ? SNIPPET_BLOB_ACTION_UPDATE : SNIPPET_BLOB_ACTION_MOVE,
|
||||||
|
previousPath: origBlob.path,
|
||||||
|
content,
|
||||||
|
filePath: path,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function returns an array of diff actions (to be sent to the BE) based on the current vs. original blobs
|
||||||
|
*
|
||||||
|
* @param {Object} blobs
|
||||||
|
* @param {Object} origBlobs
|
||||||
|
*/
|
||||||
|
export const diffAll = (blobs, origBlobs) => {
|
||||||
|
const deletedEntries = Object.values(origBlobs)
|
||||||
|
.filter(x => !blobs[x.id])
|
||||||
|
.map(({ path, content }) => ({
|
||||||
|
action: SNIPPET_BLOB_ACTION_DELETE,
|
||||||
|
previousPath: path,
|
||||||
|
filePath: path,
|
||||||
|
content,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const newEntries = Object.values(blobs)
|
||||||
|
.map(blob => diff(blob, origBlobs[blob.id]))
|
||||||
|
.filter(x => x);
|
||||||
|
|
||||||
|
return [...deletedEntries, ...newEntries];
|
||||||
|
};
|
|
@ -38,6 +38,9 @@ class ProjectsController < Projects::ApplicationController
|
||||||
before_action only: [:new, :create] do
|
before_action only: [:new, :create] do
|
||||||
frontend_experimentation_tracking_data(:new_create_project_ui, 'click_tab')
|
frontend_experimentation_tracking_data(:new_create_project_ui, 'click_tab')
|
||||||
push_frontend_feature_flag(:new_create_project_ui) if experiment_enabled?(:new_create_project_ui)
|
push_frontend_feature_flag(:new_create_project_ui) if experiment_enabled?(:new_create_project_ui)
|
||||||
|
end
|
||||||
|
|
||||||
|
before_action only: [:edit] do
|
||||||
push_frontend_feature_flag(:service_desk_custom_address, @project)
|
push_frontend_feature_flag(:service_desk_custom_address, @project)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -130,7 +130,7 @@ module GroupsHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_group_message(group)
|
def remove_group_message(group)
|
||||||
_("You are going to remove %{group_name}, this will also remove all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") %
|
_("You are going to remove %{group_name}, this will also delete all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") %
|
||||||
{ group_name: group.name }
|
{ group_name: group.name }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,7 @@ module ProjectsHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_project_message(project)
|
def remove_project_message(project)
|
||||||
_("You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?") %
|
_("You are going to delete %{project_full_name}. Deleted projects CANNOT be restored! Are you ABSOLUTELY sure?") %
|
||||||
{ project_full_name: project.full_name }
|
{ project_full_name: project.full_name }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
class PagesDomain < ApplicationRecord
|
class PagesDomain < ApplicationRecord
|
||||||
include Presentable
|
include Presentable
|
||||||
include FromUnion
|
include FromUnion
|
||||||
|
include AfterCommitQueue
|
||||||
|
|
||||||
VERIFICATION_KEY = 'gitlab-pages-verification-code'
|
VERIFICATION_KEY = 'gitlab-pages-verification-code'
|
||||||
VERIFICATION_THRESHOLD = 3.days.freeze
|
VERIFICATION_THRESHOLD = 3.days.freeze
|
||||||
|
@ -222,6 +223,8 @@ class PagesDomain < ApplicationRecord
|
||||||
private
|
private
|
||||||
|
|
||||||
def pages_deployed?
|
def pages_deployed?
|
||||||
|
return false unless project
|
||||||
|
|
||||||
# TODO: remove once `pages_metadatum` is migrated
|
# TODO: remove once `pages_metadatum` is migrated
|
||||||
# https://gitlab.com/gitlab-org/gitlab/issues/33106
|
# https://gitlab.com/gitlab-org/gitlab/issues/33106
|
||||||
unless project.pages_metadatum
|
unless project.pages_metadatum
|
||||||
|
@ -244,8 +247,13 @@ class PagesDomain < ApplicationRecord
|
||||||
# rubocop: disable CodeReuse/ServiceClass
|
# rubocop: disable CodeReuse/ServiceClass
|
||||||
def update_daemon
|
def update_daemon
|
||||||
return if usage_serverless?
|
return if usage_serverless?
|
||||||
|
return unless pages_deployed?
|
||||||
|
|
||||||
::Projects::UpdatePagesConfigurationService.new(project).execute
|
if Feature.enabled?(:async_update_pages_config, project)
|
||||||
|
run_after_commit { PagesUpdateConfigurationWorker.perform_async(project_id) }
|
||||||
|
else
|
||||||
|
Projects::UpdatePagesConfigurationService.new(project).execute
|
||||||
|
end
|
||||||
end
|
end
|
||||||
# rubocop: enable CodeReuse/ServiceClass
|
# rubocop: enable CodeReuse/ServiceClass
|
||||||
|
|
||||||
|
|
|
@ -2468,6 +2468,10 @@ class Project < ApplicationRecord
|
||||||
alias_method :service_desk_enabled?, :service_desk_enabled
|
alias_method :service_desk_enabled?, :service_desk_enabled
|
||||||
|
|
||||||
def service_desk_address
|
def service_desk_address
|
||||||
|
service_desk_custom_address || service_desk_incoming_address
|
||||||
|
end
|
||||||
|
|
||||||
|
def service_desk_incoming_address
|
||||||
return unless service_desk_enabled?
|
return unless service_desk_enabled?
|
||||||
|
|
||||||
config = Gitlab.config.incoming_email
|
config = Gitlab.config.incoming_email
|
||||||
|
@ -2476,6 +2480,16 @@ class Project < ApplicationRecord
|
||||||
config.address&.gsub(wildcard, "#{full_path_slug}-#{id}-issue-")
|
config.address&.gsub(wildcard, "#{full_path_slug}-#{id}-issue-")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def service_desk_custom_address
|
||||||
|
return unless ::Gitlab::ServiceDeskEmail.enabled?
|
||||||
|
return unless ::Feature.enabled?(:service_desk_custom_address, self)
|
||||||
|
|
||||||
|
key = service_desk_setting&.project_key
|
||||||
|
return unless key.present?
|
||||||
|
|
||||||
|
::Gitlab::ServiceDeskEmail.address_for_key("#{full_path_slug}-#{key}")
|
||||||
|
end
|
||||||
|
|
||||||
def root_namespace
|
def root_namespace
|
||||||
if namespace.has_parent?
|
if namespace.has_parent?
|
||||||
namespace.root_ancestor
|
namespace.root_ancestor
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module IncidentManagement
|
|
||||||
class CreateIssueService < BaseService
|
|
||||||
include Gitlab::Utils::StrongMemoize
|
|
||||||
include IncidentManagement::Settings
|
|
||||||
|
|
||||||
attr_reader :alert
|
|
||||||
|
|
||||||
def initialize(project, alert)
|
|
||||||
super(project, User.alert_bot)
|
|
||||||
@alert = alert
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
return error('setting disabled') unless incident_management_setting.create_issue?
|
|
||||||
return error('invalid alert') unless alert_presenter.valid?
|
|
||||||
|
|
||||||
result = create_incident
|
|
||||||
return error(result.message, result.payload[:issue]) unless result.success?
|
|
||||||
|
|
||||||
result
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def create_incident
|
|
||||||
::IncidentManagement::Incidents::CreateService.new(
|
|
||||||
project,
|
|
||||||
current_user,
|
|
||||||
title: issue_title,
|
|
||||||
description: issue_description
|
|
||||||
).execute
|
|
||||||
end
|
|
||||||
|
|
||||||
def issue_title
|
|
||||||
alert_presenter.full_title
|
|
||||||
end
|
|
||||||
|
|
||||||
def issue_description
|
|
||||||
horizontal_line = "\n\n---\n\n"
|
|
||||||
|
|
||||||
[
|
|
||||||
alert_summary,
|
|
||||||
alert_markdown,
|
|
||||||
issue_template_content
|
|
||||||
].compact.join(horizontal_line)
|
|
||||||
end
|
|
||||||
|
|
||||||
def alert_summary
|
|
||||||
alert_presenter.issue_summary_markdown
|
|
||||||
end
|
|
||||||
|
|
||||||
def alert_markdown
|
|
||||||
alert_presenter.alert_markdown
|
|
||||||
end
|
|
||||||
|
|
||||||
def alert_presenter
|
|
||||||
strong_memoize(:alert_presenter) do
|
|
||||||
Gitlab::Alerting::Alert.for_alert_management_alert(project: project, alert: alert).present
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def issue_template_content
|
|
||||||
incident_management_setting.issue_template_content
|
|
||||||
end
|
|
||||||
|
|
||||||
def error(message, issue = nil)
|
|
||||||
log_error(%{Cannot create incident issue for "#{project.full_name}": #{message}})
|
|
||||||
|
|
||||||
ServiceResponse.error(payload: { issue: issue }, message: message)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -31,7 +31,7 @@ module Projects
|
||||||
attempt_destroy_transaction(project)
|
attempt_destroy_transaction(project)
|
||||||
|
|
||||||
system_hook_service.execute_hooks_for(project, :destroy)
|
system_hook_service.execute_hooks_for(project, :destroy)
|
||||||
log_info("Project \"#{project.full_path}\" was removed")
|
log_info("Project \"#{project.full_path}\" was deleted")
|
||||||
|
|
||||||
current_user.invalidate_personal_projects_count
|
current_user.invalidate_personal_projects_count
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ module ServiceDeskSettings
|
||||||
params.delete(:project_key)
|
params.delete(:project_key)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
params[:project_key] = nil if params[:project_key].blank?
|
||||||
|
|
||||||
if settings.update(params)
|
if settings.update(params)
|
||||||
success
|
success
|
||||||
else
|
else
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
.controls
|
.controls
|
||||||
= link_to _('Members'), project_project_members_path(project), id: "edit_#{dom_id(project)}", class: "btn"
|
= link_to _('Members'), project_project_members_path(project), id: "edit_#{dom_id(project)}", class: "btn"
|
||||||
= link_to _('Edit'), edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn"
|
= link_to _('Edit'), edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn"
|
||||||
= link_to _('Remove'), project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-remove"
|
= link_to _('Delete'), project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-remove"
|
||||||
|
|
||||||
.stats
|
.stats
|
||||||
%span.badge.badge-pill
|
%span.badge.badge-pill
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
- confirm_phrase = s_('DeleteProject|Delete %{name}') % { name: project.full_name }
|
- confirm_phrase = s_('DeleteProject|Delete %{name}') % { name: project.full_name }
|
||||||
|
|
||||||
.sub-section
|
.sub-section
|
||||||
%h4.danger-title= _('Remove project')
|
%h4.danger-title= _('Delete project')
|
||||||
%p
|
%p
|
||||||
%strong= _('Removing the project will delete its repository and all related resources including issues, merge requests etc.')
|
%strong= _('Deleting the project will delete its repository and all related resources including issues, merge requests etc.')
|
||||||
%p
|
%p
|
||||||
%strong= _('Removed projects cannot be restored!')
|
%strong= _('Deleted projects cannot be restored!')
|
||||||
#js-project-delete-button{ data: { form_path: project_path(project), confirm_phrase: confirm_phrase } }
|
#js-project-delete-button{ data: { form_path: project_path(project), confirm_phrase: confirm_phrase } }
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
- if ::Gitlab::ServiceDesk.supported?
|
- if ::Gitlab::ServiceDesk.supported?
|
||||||
.js-service-desk-setting-root{ data: { endpoint: project_service_desk_path(@project),
|
.js-service-desk-setting-root{ data: { endpoint: project_service_desk_path(@project),
|
||||||
enabled: "#{@project.service_desk_enabled}",
|
enabled: "#{@project.service_desk_enabled}",
|
||||||
incoming_email: (@project.service_desk_address if @project.service_desk_enabled),
|
incoming_email: (@project.service_desk_incoming_address if @project.service_desk_enabled),
|
||||||
|
custom_email: (@project.service_desk_custom_address if @project.service_desk_enabled),
|
||||||
selected_template: "#{@project.service_desk_setting&.issue_template_key}",
|
selected_template: "#{@project.service_desk_setting&.issue_template_key}",
|
||||||
outgoing_name: "#{@project.service_desk_setting&.outgoing_name}",
|
outgoing_name: "#{@project.service_desk_setting&.outgoing_name}",
|
||||||
project_key: "#{@project.service_desk_setting&.project_key}",
|
project_key: "#{@project.service_desk_setting&.project_key}",
|
||||||
|
|
|
@ -22,4 +22,4 @@
|
||||||
|
|
||||||
- if can? current_user, :remove_project, @project
|
- if can? current_user, :remove_project, @project
|
||||||
.prepend-top-20
|
.prepend-top-20
|
||||||
= link_to _('Remove project'), project_path(@project), data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-inverted btn-remove float-right"
|
= link_to _('Delete project'), project_path(@project), data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-inverted btn-remove float-right"
|
||||||
|
|
|
@ -17,10 +17,9 @@ module IncidentManagement
|
||||||
return unless alert
|
return unless alert
|
||||||
|
|
||||||
result = create_issue_for(alert)
|
result = create_issue_for(alert)
|
||||||
return unless result.success?
|
return if result.success?
|
||||||
|
|
||||||
new_issue = result.payload[:issue]
|
log_warning(alert, result)
|
||||||
link_issue_with_alert(alert, new_issue.id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -30,19 +29,19 @@ module IncidentManagement
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_issue_for(alert)
|
def create_issue_for(alert)
|
||||||
IncidentManagement::CreateIssueService
|
AlertManagement::CreateAlertIssueService
|
||||||
.new(alert.project, alert)
|
.new(alert, User.alert_bot)
|
||||||
.execute
|
.execute
|
||||||
end
|
end
|
||||||
|
|
||||||
def link_issue_with_alert(alert, issue_id)
|
def log_warning(alert, result)
|
||||||
return if alert.update(issue_id: issue_id)
|
issue_id = result.payload[:issue]&.id
|
||||||
|
|
||||||
Gitlab::AppLogger.warn(
|
Gitlab::AppLogger.warn(
|
||||||
message: 'Cannot link an Issue with Alert',
|
message: 'Cannot process an Incident',
|
||||||
issue_id: issue_id,
|
issue_id: issue_id,
|
||||||
alert_id: alert.id,
|
alert_id: alert.id,
|
||||||
alert_errors: alert.errors.messages
|
errors: result.message
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Re-name project remove as project delete
|
||||||
|
merge_request: 38489
|
||||||
|
author:
|
||||||
|
type: changed
|
5
changelogs/unreleased/227838-h3-h4-class-fix.yml
Normal file
5
changelogs/unreleased/227838-h3-h4-class-fix.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Replace mis-used CSS class in operations settings
|
||||||
|
merge_request: 39338
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -167,7 +167,7 @@ user = User.find_by_username('root')
|
||||||
# Find the project, update the xxx-changeme values from above
|
# Find the project, update the xxx-changeme values from above
|
||||||
project = Project.find_by_full_path('group-changeme/project-changeme')
|
project = Project.find_by_full_path('group-changeme/project-changeme')
|
||||||
|
|
||||||
# Delete the project
|
# Immediately delete the project
|
||||||
::Projects::DestroyService.new(project, user, {}).execute
|
::Projects::DestroyService.new(project, user, {}).execute
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -1832,11 +1832,11 @@ Example response:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Remove project
|
## Delete project
|
||||||
|
|
||||||
This endpoint:
|
This endpoint:
|
||||||
|
|
||||||
- Removes a project including all associated resources (issues, merge requests etc).
|
- Deletes a project including all associated resources (issues, merge requests etc).
|
||||||
- From [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) on [Premium or Silver](https://about.gitlab.com/pricing/) or higher tiers,
|
- From [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) on [Premium or Silver](https://about.gitlab.com/pricing/) or higher tiers,
|
||||||
group admins can [configure](../user/group/index.md#enabling-delayed-project-removal-premium) projects within a group
|
group admins can [configure](../user/group/index.md#enabling-delayed-project-removal-premium) projects within a group
|
||||||
to be deleted after a delayed period.
|
to be deleted after a delayed period.
|
||||||
|
|
|
@ -221,7 +221,7 @@ are listed in the descriptions of the relevant settings.
|
||||||
| `default_project_visibility` | string | no | What visibility level new projects receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
|
| `default_project_visibility` | string | no | What visibility level new projects receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
|
||||||
| `default_projects_limit` | integer | no | Project limit per user. Default is `100000`. |
|
| `default_projects_limit` | integer | no | Project limit per user. Default is `100000`. |
|
||||||
| `default_snippet_visibility` | string | no | What visibility level new snippets receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
|
| `default_snippet_visibility` | string | no | What visibility level new snippets receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
|
||||||
| `deletion_adjourned_period` | integer | no | **(PREMIUM ONLY)** The number of days to wait before removing a project or group that is marked for deletion. Value must be between 0 and 90.
|
| `deletion_adjourned_period` | integer | no | **(PREMIUM ONLY)** The number of days to wait before deleting a project or group that is marked for deletion. Value must be between 0 and 90.
|
||||||
| `diff_max_patch_bytes` | integer | no | Maximum diff patch size (Bytes). |
|
| `diff_max_patch_bytes` | integer | no | Maximum diff patch size (Bytes). |
|
||||||
| `disabled_oauth_sign_in_sources` | array of strings | no | Disabled OAuth sign-in sources. |
|
| `disabled_oauth_sign_in_sources` | array of strings | no | Disabled OAuth sign-in sources. |
|
||||||
| `dns_rebinding_protection_enabled` | boolean | no | Enforce DNS rebinding attack protection. |
|
| `dns_rebinding_protection_enabled` | boolean | no | Enforce DNS rebinding attack protection. |
|
||||||
|
|
|
@ -24,7 +24,8 @@ should be leveraged:
|
||||||
- When development of a feature will be spread across multiple merge
|
- When development of a feature will be spread across multiple merge
|
||||||
requests, you can use the following workflow:
|
requests, you can use the following workflow:
|
||||||
|
|
||||||
1. Introduce a feature flag which is **off** by default, in the first merge request.
|
1. [Create a new feature flag](development.md#create-a-new-feature-flag)
|
||||||
|
which is **off** by default, in the first merge request.
|
||||||
1. Submit incremental changes via one or more merge requests, ensuring that any
|
1. Submit incremental changes via one or more merge requests, ensuring that any
|
||||||
new code added can only be reached if the feature flag is **on**.
|
new code added can only be reached if the feature flag is **on**.
|
||||||
You can keep the feature flag enabled on your local GDK during development.
|
You can keep the feature flag enabled on your local GDK during development.
|
||||||
|
|
|
@ -636,7 +636,7 @@ Errors in `locale/zh_HK/gitlab.po`:
|
||||||
Syntax error in msgstr
|
Syntax error in msgstr
|
||||||
Syntax error in message_line
|
Syntax error in message_line
|
||||||
There should be only whitespace until the end of line after the double quote character of a message text.
|
There should be only whitespace until the end of line after the double quote character of a message text.
|
||||||
Parsing result before error: '{:msgid=>["", "You are going to remove %{project_name_with_namespace}.\\n", "Removed project CANNOT be restored!\\n", "Are you ABSOLUTELY sure?"]}'
|
Parsing result before error: '{:msgid=>["", "You are going to delete %{project_name_with_namespace}.\\n", "Deleted projects CANNOT be restored!\\n", "Are you ABSOLUTELY sure?"]}'
|
||||||
SimplePoParser filtered backtrace: SimplePoParser::ParserError
|
SimplePoParser filtered backtrace: SimplePoParser::ParserError
|
||||||
Errors in `locale/zh_TW/gitlab.po`:
|
Errors in `locale/zh_TW/gitlab.po`:
|
||||||
1 pipeline
|
1 pipeline
|
||||||
|
|
36
doc/operations/metrics/dashboards/default.md
Normal file
36
doc/operations/metrics/dashboards/default.md
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
stage: Monitor
|
||||||
|
group: APM
|
||||||
|
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||||
|
---
|
||||||
|
|
||||||
|
# GitLab-defined metrics dashboards **(CORE)**
|
||||||
|
|
||||||
|
GitLab provides some dashboards out-of-the-box for any project with
|
||||||
|
[Prometheus available](../../../user/project/integrations/prometheus.md). You can
|
||||||
|
[duplicate these GitLab-defined dashboards](index.md#duplicate-a-gitlab-defined-dashboard):
|
||||||
|
|
||||||
|
- [Overview dashboard](#overview-dashboard).
|
||||||
|
- [Kubernetes pod health dashboard](#kubernetes-pod-health-dashboard).
|
||||||
|
|
||||||
|
To learn about the components of a dashboard, read
|
||||||
|
[Metrics dashboard for your CI/CD environment](../index.md).
|
||||||
|
|
||||||
|
## Overview dashboard
|
||||||
|
|
||||||
|
This dashboard is the default metrics dashboard. It displays a large number of
|
||||||
|
metrics about the [deployed application](../index.md#configure-prometheus-to-gather-metrics).
|
||||||
|
|
||||||
|
![Example of metrics dashboard](../img/example-dashboard_v13_3.png)
|
||||||
|
|
||||||
|
## Kubernetes pod health dashboard
|
||||||
|
|
||||||
|
NOTE: **Note:**
|
||||||
|
This dashboard requires Kubernetes v1.14 or higher, due to the
|
||||||
|
[change in metric labels](https://github.com/kubernetes/kubernetes/pull/69099)
|
||||||
|
in Kubernetes 1.14.
|
||||||
|
|
||||||
|
This dashboard displays CPU, memory, network and disk metrics for the pods in your
|
||||||
|
[connected K8s cluster](../../../user/project/clusters/index.md). It provides a
|
||||||
|
[variable selector](templating_variables.md#metric_label_values-variable-type)
|
||||||
|
at the top of the dashboard to select which pod's metrics to display.
|
|
@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/59974) in GitLab 12.1.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/59974) in GitLab 12.1.
|
||||||
|
|
||||||
By default, all projects include a GitLab-defined Prometheus dashboard, which
|
By default, all projects include a [GitLab-defined Prometheus dashboard](default.md), which
|
||||||
includes a few key metrics, but you can also define your own custom dashboards.
|
includes a few key metrics, but you can also define your own custom dashboards.
|
||||||
|
|
||||||
You may create a [new dashboard from scratch](#add-a-new-dashboard-to-your-project)
|
You may create a [new dashboard from scratch](#add-a-new-dashboard-to-your-project)
|
||||||
|
@ -23,7 +23,8 @@ The metrics as defined below do not support alerts, unlike
|
||||||
> UI option [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/228856) in GitLab 13.3.
|
> UI option [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/228856) in GitLab 13.3.
|
||||||
|
|
||||||
You can configure a custom dashboard by adding a new YAML file into your project's
|
You can configure a custom dashboard by adding a new YAML file into your project's
|
||||||
`.gitlab/dashboards/` directory. For the dashboard to display on your project's **Operations > Metrics** page, the files must have a `.yml`
|
`.gitlab/dashboards/` directory. For the dashboard to display on your project's
|
||||||
|
**Operations > Metrics** page, the files must have a `.yml`
|
||||||
extension and be present in your project's **default** branch.
|
extension and be present in your project's **default** branch.
|
||||||
|
|
||||||
To create a new dashboard from the GitLab user interface:
|
To create a new dashboard from the GitLab user interface:
|
||||||
|
@ -145,7 +146,7 @@ Your custom dashboard is available at `https://example.com/project/-/metrics/cus
|
||||||
|
|
||||||
To manage the settings for your metrics dashboard:
|
To manage the settings for your metrics dashboard:
|
||||||
|
|
||||||
1. Sign in as a user with project Maintainer or Admin
|
1. Sign in as a user with project Maintainer or Administrator
|
||||||
[permissions](../../../user/permissions.md#project-members-permissions).
|
[permissions](../../../user/permissions.md#project-members-permissions).
|
||||||
1. Navigate to your dashboard at **Operations > Metrics**.
|
1. Navigate to your dashboard at **Operations > Metrics**.
|
||||||
1. In the top-right corner of your dashboard, click **Metrics Settings**:
|
1. In the top-right corner of your dashboard, click **Metrics Settings**:
|
||||||
|
|
|
@ -72,16 +72,15 @@ and NGINX, and attempts to identify individual environments. To learn more about
|
||||||
the supported metrics and scan processes, see the
|
the supported metrics and scan processes, see the
|
||||||
[Prometheus Metrics Library documentation](../../user/project/integrations/prometheus_library/index.md).
|
[Prometheus Metrics Library documentation](../../user/project/integrations/prometheus_library/index.md).
|
||||||
|
|
||||||
To view the metrics dashboard for an environment that has
|
To view the [default metrics dashboard](dashboards/default.md) for an environment that is
|
||||||
To view the metrics dashboard for an environment that is
|
|
||||||
[configured to gather metrics](#configure-prometheus-to-gather-metrics):
|
[configured to gather metrics](#configure-prometheus-to-gather-metrics):
|
||||||
|
|
||||||
1. *If the metrics dashboard is only visible to project members,* sign in to
|
1. *If the metrics dashboard is only visible to project members,* sign in to
|
||||||
GitLab as a member of a project. Learn more about [metrics dashboard visibility](#metrics-dashboard-visibility).
|
GitLab as a member of a project. Learn more about [metrics dashboard visibility](#metrics-dashboard-visibility).
|
||||||
1. In your project, navigate to **Operations > Metrics**.
|
1. In your project, navigate to **Operations > Metrics**.
|
||||||
|
|
||||||
GitLab displays the default metrics dashboard for the environment, like the
|
GitLab displays the [default metrics dashboard](dashboards/default.md) for the environment,
|
||||||
following example:
|
like the following example:
|
||||||
|
|
||||||
![Example of metrics dashboard](img/example-dashboard_v13_3.png)
|
![Example of metrics dashboard](img/example-dashboard_v13_3.png)
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,6 @@ type: reference, howto
|
||||||
|
|
||||||
# Coverage Guided Fuzz Testing **(ULTIMATE)**
|
# Coverage Guided Fuzz Testing **(ULTIMATE)**
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3226) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.2 as an [Alpha feature](https://about.gitlab.com/handbook/product/gitlab-the-product/#alpha).
|
|
||||||
|
|
||||||
GitLab allows you to add coverage-guided fuzz testing to your pipelines. This helps you discover
|
GitLab allows you to add coverage-guided fuzz testing to your pipelines. This helps you discover
|
||||||
bugs and potential security issues that other QA processes may miss. Coverage-guided fuzzing sends
|
bugs and potential security issues that other QA processes may miss. Coverage-guided fuzzing sends
|
||||||
random inputs to an instrumented version of your application in an effort to cause unexpected
|
random inputs to an instrumented version of your application in an effort to cause unexpected
|
||||||
|
|
|
@ -156,7 +156,7 @@ The following table depicts the various user permission levels in a project.
|
||||||
| Transfer project to another namespace | | | | | ✓ |
|
| Transfer project to another namespace | | | | | ✓ |
|
||||||
| Rename project | | | | | ✓ |
|
| Rename project | | | | | ✓ |
|
||||||
| Remove fork relationship | | | | | ✓ |
|
| Remove fork relationship | | | | | ✓ |
|
||||||
| Remove project | | | | | ✓ |
|
| Delete project | | | | | ✓ |
|
||||||
| Archive project | | | | | ✓ |
|
| Archive project | | | | | ✓ |
|
||||||
| Delete issues | | | | | ✓ |
|
| Delete issues | | | | | ✓ |
|
||||||
| Delete pipelines | | | | | ✓ |
|
| Delete pipelines | | | | | ✓ |
|
||||||
|
@ -416,7 +416,7 @@ instance and project. In addition, all admins can use the admin interface under
|
||||||
| See commits and jobs | ✓ | ✓ | ✓ | ✓ |
|
| See commits and jobs | ✓ | ✓ | ✓ | ✓ |
|
||||||
| Retry or cancel job | | ✓ | ✓ | ✓ |
|
| Retry or cancel job | | ✓ | ✓ | ✓ |
|
||||||
| Erase job artifacts and trace | | ✓ (*1*) | ✓ | ✓ |
|
| Erase job artifacts and trace | | ✓ (*1*) | ✓ | ✓ |
|
||||||
| Remove project | | | ✓ | ✓ |
|
| Delete project | | | ✓ | ✓ |
|
||||||
| Create project | | | ✓ | ✓ |
|
| Create project | | | ✓ | ✓ |
|
||||||
| Change project configuration | | | ✓ | ✓ |
|
| Change project configuration | | | ✓ | ✓ |
|
||||||
| Add specific runners | | | ✓ | ✓ |
|
| Add specific runners | | | ✓ | ✓ |
|
||||||
|
|
|
@ -180,22 +180,22 @@ Read through the documentation on [project settings](settings/index.md).
|
||||||
- [Export a project from GitLab](settings/import_export.md#exporting-a-project-and-its-data)
|
- [Export a project from GitLab](settings/import_export.md#exporting-a-project-and-its-data)
|
||||||
- [Importing and exporting projects between GitLab instances](settings/import_export.md)
|
- [Importing and exporting projects between GitLab instances](settings/import_export.md)
|
||||||
|
|
||||||
## Remove a project
|
## Delete a project
|
||||||
|
|
||||||
To remove a project, first navigate to the home page for that project.
|
To delete a project, first navigate to the home page for that project.
|
||||||
|
|
||||||
1. Navigate to **Settings > General**.
|
1. Navigate to **Settings > General**.
|
||||||
1. Expand the **Advanced** section.
|
1. Expand the **Advanced** section.
|
||||||
1. Scroll down to the **Remove project** section.
|
1. Scroll down to the **Delete project** section.
|
||||||
1. Click **Remove project**
|
1. Click **Delete project**
|
||||||
1. Confirm this action by typing in the expected text.
|
1. Confirm this action by typing in the expected text.
|
||||||
|
|
||||||
### Delayed removal **(PREMIUM)**
|
### Delayed deletion **(PREMIUM)**
|
||||||
|
|
||||||
By default, clicking to remove a project is followed by a seven day delay. Admins can restore the project during this period of time.
|
By default, clicking to delete a project is followed by a seven day delay. Admins can restore the project during this period of time.
|
||||||
This delay [may be changed by an admin](../admin_area/settings/visibility_and_access_controls.md#default-deletion-delay-premium-only).
|
This delay [may be changed by an admin](../admin_area/settings/visibility_and_access_controls.md#default-deletion-delay-premium-only).
|
||||||
|
|
||||||
Admins can view all projects pending deletion. If you're an administrator, go to the top navigation bar, click **Projects > Your projects**, and then select the **Removed projects** tab.
|
Admins can view all projects pending deletion. If you're an administrator, go to the top navigation bar, click **Projects > Your projects**, and then select the **Deleted projects** tab.
|
||||||
From this tab an admin can restore any project.
|
From this tab an admin can restore any project.
|
||||||
|
|
||||||
## CI/CD for external repositories **(PREMIUM)**
|
## CI/CD for external repositories **(PREMIUM)**
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 49 KiB |
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
|
@ -187,7 +187,10 @@ a rule is already defined.
|
||||||
When an [eligible approver](#eligible-approvers) approves a merge request, it will
|
When an [eligible approver](#eligible-approvers) approves a merge request, it will
|
||||||
reduce the number of approvals left for all rules that the approver belongs to.
|
reduce the number of approvals left for all rules that the approver belongs to.
|
||||||
|
|
||||||
![Approvals premium merge request widget](img/approvals_premium_mr_widget_v12_7.png)
|
When an [eligible approver](#eligible-approvers) comments on a merge request, it
|
||||||
|
appears in the **Commented by** column. This feature was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10294) in GitLab 13.3.
|
||||||
|
|
||||||
|
![Approvals premium merge request widget](img/approvals_premium_mr_widget_v13_3.png)
|
||||||
|
|
||||||
#### Scoped to Protected Branch **(PREMIUM)**
|
#### Scoped to Protected Branch **(PREMIUM)**
|
||||||
|
|
||||||
|
|
|
@ -219,20 +219,20 @@ NOTE: **Note:**
|
||||||
GitLab administrators can use the admin interface to move any project to any
|
GitLab administrators can use the admin interface to move any project to any
|
||||||
namespace if needed.
|
namespace if needed.
|
||||||
|
|
||||||
#### Remove a project
|
#### Delete a project
|
||||||
|
|
||||||
NOTE: **Note:**
|
NOTE: **Note:**
|
||||||
Only project owners and admins have [permissions](../../permissions.md#project-members-permissions) to remove a project.
|
Only project owners and admins have [permissions](../../permissions.md#project-members-permissions) to delete a project.
|
||||||
|
|
||||||
To remove a project:
|
To delete a project:
|
||||||
|
|
||||||
1. Navigate to your project, and select **Settings > General > Advanced**.
|
1. Navigate to your project, and select **Settings > General > Advanced**.
|
||||||
1. In the Remove project section, click the **Remove project** button.
|
1. In the "Delete project" section, click the **Delete project** button.
|
||||||
1. Confirm the action when asked to.
|
1. Confirm the action when asked to.
|
||||||
|
|
||||||
This action:
|
This action:
|
||||||
|
|
||||||
- Removes a project including all associated resources (issues, merge requests etc).
|
- Deletes a project including all associated resources (issues, merge requests etc).
|
||||||
- From [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) on [Premium or Silver](https://about.gitlab.com/pricing/) or higher tiers,
|
- From [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) on [Premium or Silver](https://about.gitlab.com/pricing/) or higher tiers,
|
||||||
group admins can [configure](../../group/index.md#enabling-delayed-project-removal-premium) projects within a group
|
group admins can [configure](../../group/index.md#enabling-delayed-project-removal-premium) projects within a group
|
||||||
to be deleted after a delayed period.
|
to be deleted after a delayed period.
|
||||||
|
|
|
@ -448,7 +448,7 @@ module API
|
||||||
.execute.map { |lang| [lang.name, lang.share] }.to_h
|
.execute.map { |lang| [lang.name, lang.share] }.to_h
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Remove a project'
|
desc 'Delete a project'
|
||||||
delete ":id" do
|
delete ":id" do
|
||||||
authorize! :remove_project, user_project
|
authorize! :remove_project, user_project
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,12 @@ module Gitlab
|
||||||
def config
|
def config
|
||||||
Gitlab.config.service_desk_email
|
Gitlab.config.service_desk_email
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def address_for_key(key)
|
||||||
|
return if config.address.blank?
|
||||||
|
|
||||||
|
config.address.sub(Gitlab::IncomingEmail::WILDCARD_PLACEHOLDER, key)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7884,15 +7884,27 @@ msgstr ""
|
||||||
msgid "Deleted"
|
msgid "Deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Deleted Projects"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Deleted chat nickname: %{chat_name}!"
|
msgid "Deleted chat nickname: %{chat_name}!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Deleted in this version"
|
msgid "Deleted in this version"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Deleted projects"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Deleted projects cannot be restored!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Deleting"
|
msgid "Deleting"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Deleting a project places it into a read-only state until %{date}, at which point the project will be permanently deleted. Are you ABSOLUTELY sure?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Deleting the license failed."
|
msgid "Deleting the license failed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -7902,6 +7914,9 @@ msgstr ""
|
||||||
msgid "Deleting the license failed. You are not permitted to perform this action."
|
msgid "Deleting the license failed. You are not permitted to perform this action."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Deleting the project will delete its repository and all related resources including issues, merge requests etc."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Deletion pending. This project will be removed on %{date}. Repository and other project resources are read-only."
|
msgid "Deletion pending. This project will be removed on %{date}. Repository and other project resources are read-only."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -20240,9 +20255,6 @@ msgstr ""
|
||||||
msgid "Remove priority"
|
msgid "Remove priority"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Remove project"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Remove secondary node"
|
msgid "Remove secondary node"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -20276,9 +20288,6 @@ msgstr ""
|
||||||
msgid "Removed %{type} with id %{id}"
|
msgid "Removed %{type} with id %{id}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Removed Projects"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Removed all labels."
|
msgid "Removed all labels."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -20291,12 +20300,6 @@ msgstr ""
|
||||||
msgid "Removed parent epic %{epic_ref}."
|
msgid "Removed parent epic %{epic_ref}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Removed projects"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Removed projects cannot be restored!"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Removed spent time."
|
msgid "Removed spent time."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -20345,15 +20348,9 @@ msgstr ""
|
||||||
msgid "Removes time estimate."
|
msgid "Removes time estimate."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Removing a project places it into a read-only state until %{date}, at which point the project will be permanently removed. Are you ABSOLUTELY sure?"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Removing license…"
|
msgid "Removing license…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Removing this group also removes all child projects, including archived projects, and their resources."
|
msgid "Removing this group also removes all child projects, including archived projects, and their resources."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -22552,6 +22549,9 @@ msgstr ""
|
||||||
msgid "SnippetsEmptyState|There are no snippets to show."
|
msgid "SnippetsEmptyState|There are no snippets to show."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Snippets|Delete file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Snippets|Description (optional)"
|
msgid "Snippets|Description (optional)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -22744,10 +22744,10 @@ msgstr ""
|
||||||
msgid "Something went wrong, unable to add projects to dashboard"
|
msgid "Something went wrong, unable to add projects to dashboard"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Something went wrong, unable to get projects"
|
msgid "Something went wrong, unable to delete project"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Something went wrong, unable to remove project"
|
msgid "Something went wrong, unable to get projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Something went wrong, unable to search projects"
|
msgid "Something went wrong, unable to search projects"
|
||||||
|
@ -27657,10 +27657,10 @@ msgstr ""
|
||||||
msgid "You are connected to the Prometheus server, but there is currently no data to display."
|
msgid "You are connected to the Prometheus server, but there is currently no data to display."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "You are going to remove %{group_name}, this will also remove all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
|
msgid "You are going to delete %{project_full_name}. Deleted projects CANNOT be restored! Are you ABSOLUTELY sure?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
|
msgid "You are going to remove %{group_name}, this will also delete all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "You are going to remove the fork relationship from %{project_full_name}. Are you ABSOLUTELY sure?"
|
msgid "You are going to remove the fork relationship from %{project_full_name}. Are you ABSOLUTELY sure?"
|
||||||
|
|
|
@ -35,7 +35,7 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders form for incident management' do
|
it 'renders form for incident management' do
|
||||||
expect(page).to have_selector('h3', text: 'Incidents')
|
expect(page).to have_selector('h4', text: 'Incidents')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets correct default values' do
|
it 'sets correct default values' do
|
||||||
|
|
|
@ -254,13 +254,13 @@ RSpec.describe 'Project' do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'focuses on the confirmation field' do
|
it 'focuses on the confirmation field' do
|
||||||
click_button 'Remove project'
|
click_button 'Delete project'
|
||||||
|
|
||||||
expect(page).to have_selector '#confirm_name_input:focus'
|
expect(page).to have_selector '#confirm_name_input:focus'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'removes a project', :sidekiq_might_not_need_inline do
|
it 'deletes a project', :sidekiq_might_not_need_inline do
|
||||||
expect { remove_with_confirm('Remove project', "Delete #{project.full_name}", 'Yes, delete project') }.to change { Project.count }.by(-1)
|
expect { remove_with_confirm('Delete project', "Delete #{project.full_name}", 'Yes, delete project') }.to change { Project.count }.by(-1)
|
||||||
expect(page).to have_content "Project '#{project.full_name}' is in the process of being deleted."
|
expect(page).to have_content "Project '#{project.full_name}' is in the process of being deleted."
|
||||||
expect(Project.all.count).to be_zero
|
expect(Project.all.count).to be_zero
|
||||||
expect(project.issues).to be_empty
|
expect(project.issues).to be_empty
|
||||||
|
|
|
@ -4,13 +4,18 @@ exports[`Blob Header Editing rendering matches the snapshot 1`] = `
|
||||||
<div
|
<div
|
||||||
class="js-file-title file-title-flex-parent"
|
class="js-file-title file-title-flex-parent"
|
||||||
>
|
>
|
||||||
<gl-form-input-stub
|
<div
|
||||||
class="form-control js-snippet-file-name"
|
class="gl-display-flex gl-align-items-center gl-w-full"
|
||||||
id="snippet_file_name"
|
>
|
||||||
name="snippet_file_name"
|
<gl-form-input-stub
|
||||||
placeholder="Give your file a name to add code highlighting, e.g. example.rb for Ruby"
|
class="form-control js-snippet-file-name"
|
||||||
type="text"
|
name="snippet_file_name"
|
||||||
value="foo.md"
|
placeholder="Give your file a name to add code highlighting, e.g. example.rb for Ruby"
|
||||||
/>
|
type="text"
|
||||||
|
value="foo.md"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!---->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import BlobEditHeader from '~/blob/components/blob_edit_header.vue';
|
import BlobEditHeader from '~/blob/components/blob_edit_header.vue';
|
||||||
import { GlFormInput } from '@gitlab/ui';
|
import { GlFormInput, GlButton } from '@gitlab/ui';
|
||||||
|
|
||||||
describe('Blob Header Editing', () => {
|
describe('Blob Header Editing', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
const value = 'foo.md';
|
const value = 'foo.md';
|
||||||
|
|
||||||
function createComponent() {
|
const createComponent = (props = {}) => {
|
||||||
wrapper = shallowMount(BlobEditHeader, {
|
wrapper = shallowMount(BlobEditHeader, {
|
||||||
propsData: {
|
propsData: {
|
||||||
value,
|
value,
|
||||||
|
...props,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
const findDeleteButton = () =>
|
||||||
|
wrapper.findAll(GlButton).wrappers.find(x => x.text() === 'Delete file');
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createComponent();
|
createComponent();
|
||||||
|
@ -30,6 +33,10 @@ describe('Blob Header Editing', () => {
|
||||||
it('contains a form input field', () => {
|
it('contains a form input field', () => {
|
||||||
expect(wrapper.contains(GlFormInput)).toBe(true);
|
expect(wrapper.contains(GlFormInput)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not show delete button', () => {
|
||||||
|
expect(findDeleteButton()).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('functionality', () => {
|
describe('functionality', () => {
|
||||||
|
@ -47,4 +54,35 @@ describe('Blob Header Editing', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe.each`
|
||||||
|
props | expectedDisabled
|
||||||
|
${{ showDelete: true }} | ${true}
|
||||||
|
${{ showDelete: true, canDelete: true }} | ${false}
|
||||||
|
`('with $props', ({ props, expectedDisabled }) => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createComponent(props);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`shows delete button (disabled=${expectedDisabled})`, () => {
|
||||||
|
const deleteButton = findDeleteButton();
|
||||||
|
|
||||||
|
expect(deleteButton.exists()).toBe(true);
|
||||||
|
expect(deleteButton.props('disabled')).toBe(expectedDisabled);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with delete button', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createComponent({ showDelete: true, canDelete: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits delete when clicked', () => {
|
||||||
|
expect(wrapper.emitted().delete).toBeUndefined();
|
||||||
|
|
||||||
|
findDeleteButton().vm.$emit('click');
|
||||||
|
|
||||||
|
expect(wrapper.emitted().delete).toEqual([[]]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,13 +9,13 @@ exports[`IncidentsSettingTabs should render the component 1`] = `
|
||||||
<div
|
<div
|
||||||
class="settings-header"
|
class="settings-header"
|
||||||
>
|
>
|
||||||
<h3
|
<h4
|
||||||
class="h4"
|
class="gl-my-3! gl-py-1"
|
||||||
>
|
>
|
||||||
|
|
||||||
Incidents
|
Incidents
|
||||||
|
|
||||||
</h3>
|
</h4>
|
||||||
|
|
||||||
<gl-button-stub
|
<gl-button-stub
|
||||||
category="tertiary"
|
category="tertiary"
|
||||||
|
|
|
@ -24,7 +24,7 @@ exports[`Project remove modal initialized matches the snapshot 1`] = `
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
variant="danger"
|
variant="danger"
|
||||||
>
|
>
|
||||||
Remove project
|
Delete project
|
||||||
</gl-button-stub>
|
</gl-button-stub>
|
||||||
|
|
||||||
<gl-modal-stub
|
<gl-modal-stub
|
||||||
|
|
|
@ -25,7 +25,7 @@ exports[`Project remove modal intialized matches the snapshot 1`] = `
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
variant="danger"
|
variant="danger"
|
||||||
>
|
>
|
||||||
Remove project
|
Delete project
|
||||||
</gl-button-stub>
|
</gl-button-stub>
|
||||||
|
|
||||||
<b-modal-stub
|
<b-modal-stub
|
||||||
|
|
|
@ -2,25 +2,18 @@
|
||||||
|
|
||||||
exports[`Snippet Blob Edit component rendering matches the snapshot 1`] = `
|
exports[`Snippet Blob Edit component rendering matches the snapshot 1`] = `
|
||||||
<div
|
<div
|
||||||
class="form-group file-editor"
|
class="file-holder snippet"
|
||||||
>
|
>
|
||||||
<label>
|
<blob-header-edit-stub
|
||||||
File
|
data-qa-selector="file_name_field"
|
||||||
</label>
|
id="snippet_file_path"
|
||||||
|
value="lorem.txt"
|
||||||
|
/>
|
||||||
|
|
||||||
<div
|
<blob-content-edit-stub
|
||||||
class="file-holder snippet"
|
fileglobalid="0a3d"
|
||||||
>
|
filename="lorem.txt"
|
||||||
<blob-header-edit-stub
|
value="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||||
data-qa-selector="file_name_field"
|
/>
|
||||||
value="lorem.txt"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<blob-content-edit-stub
|
|
||||||
fileglobalid="0a3d"
|
|
||||||
filename="lorem.txt"
|
|
||||||
value="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { redirectTo } from '~/lib/utils/url_utility';
|
||||||
import SnippetEditApp from '~/snippets/components/edit.vue';
|
import SnippetEditApp from '~/snippets/components/edit.vue';
|
||||||
import SnippetDescriptionEdit from '~/snippets/components/snippet_description_edit.vue';
|
import SnippetDescriptionEdit from '~/snippets/components/snippet_description_edit.vue';
|
||||||
import SnippetVisibilityEdit from '~/snippets/components/snippet_visibility_edit.vue';
|
import SnippetVisibilityEdit from '~/snippets/components/snippet_visibility_edit.vue';
|
||||||
import SnippetBlobEdit from '~/snippets/components/snippet_blob_edit.vue';
|
import SnippetBlobActionsEdit from '~/snippets/components/snippet_blob_actions_edit.vue';
|
||||||
import TitleField from '~/vue_shared/components/form/title.vue';
|
import TitleField from '~/vue_shared/components/form/title.vue';
|
||||||
import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue';
|
import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue';
|
||||||
import { SNIPPET_CREATE_MUTATION_ERROR, SNIPPET_UPDATE_MUTATION_ERROR } from '~/snippets/constants';
|
import { SNIPPET_CREATE_MUTATION_ERROR, SNIPPET_UPDATE_MUTATION_ERROR } from '~/snippets/constants';
|
||||||
|
@ -141,7 +141,7 @@ describe('Snippet Edit app', () => {
|
||||||
|
|
||||||
expect(wrapper.contains(TitleField)).toBe(true);
|
expect(wrapper.contains(TitleField)).toBe(true);
|
||||||
expect(wrapper.contains(SnippetDescriptionEdit)).toBe(true);
|
expect(wrapper.contains(SnippetDescriptionEdit)).toBe(true);
|
||||||
expect(wrapper.contains(SnippetBlobEdit)).toBe(true);
|
expect(wrapper.contains(SnippetBlobActionsEdit)).toBe(true);
|
||||||
expect(wrapper.contains(SnippetVisibilityEdit)).toBe(true);
|
expect(wrapper.contains(SnippetVisibilityEdit)).toBe(true);
|
||||||
expect(wrapper.contains(FormFooterActions)).toBe(true);
|
expect(wrapper.contains(FormFooterActions)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import SnippetBlobActionsEdit from '~/snippets/components/snippet_blob_actions_edit.vue';
|
||||||
|
import SnippetBlobEdit from '~/snippets/components/snippet_blob_edit.vue';
|
||||||
|
|
||||||
|
const TEST_BLOBS = [
|
||||||
|
{ name: 'foo', content: 'abc', rawPath: 'test/raw' },
|
||||||
|
{ name: 'bar', content: 'def', rawPath: 'test/raw' },
|
||||||
|
];
|
||||||
|
const TEST_EVENT = 'blob-update';
|
||||||
|
|
||||||
|
describe('snippets/components/snippet_blob_actions_edit', () => {
|
||||||
|
let onEvent;
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
const createComponent = (props = {}) => {
|
||||||
|
wrapper = shallowMount(SnippetBlobActionsEdit, {
|
||||||
|
propsData: {
|
||||||
|
blobs: [],
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
listeners: {
|
||||||
|
[TEST_EVENT]: onEvent,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const findBlobEdit = () => wrapper.find(SnippetBlobEdit);
|
||||||
|
const findBlobEditData = () => wrapper.findAll(SnippetBlobEdit).wrappers.map(x => x.props());
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
onEvent = jest.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.destroy();
|
||||||
|
wrapper = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each`
|
||||||
|
props | expectedData
|
||||||
|
${{}} | ${[{ blob: null }]}
|
||||||
|
${{ blobs: TEST_BLOBS }} | ${TEST_BLOBS.map(blob => ({ blob }))}
|
||||||
|
`('with $props', ({ props, expectedData }) => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createComponent(props);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders blob edit', () => {
|
||||||
|
expect(findBlobEditData()).toEqual(expectedData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits event', () => {
|
||||||
|
expect(onEvent).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
findBlobEdit().vm.$emit('blob-update', TEST_BLOBS[0]);
|
||||||
|
|
||||||
|
expect(onEvent).toHaveBeenCalledWith(TEST_BLOBS[0]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
134
spec/frontend/snippets/utils/blob_spec.js
Normal file
134
spec/frontend/snippets/utils/blob_spec.js
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
import {
|
||||||
|
SNIPPET_BLOB_ACTION_CREATE,
|
||||||
|
SNIPPET_BLOB_ACTION_UPDATE,
|
||||||
|
SNIPPET_BLOB_ACTION_MOVE,
|
||||||
|
SNIPPET_BLOB_ACTION_DELETE,
|
||||||
|
} from '~/snippets/constants';
|
||||||
|
import { decorateBlob, createBlob, diffAll } from '~/snippets/utils/blob';
|
||||||
|
|
||||||
|
jest.mock('lodash/uniqueId', () => arg => `${arg}fakeUniqueId`);
|
||||||
|
|
||||||
|
const TEST_RAW_BLOB = {
|
||||||
|
rawPath: '/test/blob/7/raw',
|
||||||
|
};
|
||||||
|
const CONTENT_1 = 'Lorem ipsum dolar\nSit amit\n\nGoodbye!\n';
|
||||||
|
const CONTENT_2 = 'Lorem ipsum dolar sit amit.\n\nGoodbye!\n';
|
||||||
|
|
||||||
|
describe('~/snippets/utils/blob', () => {
|
||||||
|
describe('decorateBlob', () => {
|
||||||
|
it('should decorate the given object with local blob properties', () => {
|
||||||
|
const orig = cloneDeep(TEST_RAW_BLOB);
|
||||||
|
|
||||||
|
expect(decorateBlob(orig)).toEqual({
|
||||||
|
...TEST_RAW_BLOB,
|
||||||
|
id: 'blob_local_fakeUniqueId',
|
||||||
|
isLoaded: false,
|
||||||
|
content: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createBlob', () => {
|
||||||
|
it('should create an empty local blob', () => {
|
||||||
|
expect(createBlob()).toEqual({
|
||||||
|
id: 'blob_local_fakeUniqueId',
|
||||||
|
isLoaded: true,
|
||||||
|
content: '',
|
||||||
|
path: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('diffAll', () => {
|
||||||
|
// This object contains entries that contain an expected "diff" and the `id`
|
||||||
|
// or `origContent` that should be used to generate the expected diff.
|
||||||
|
const testEntries = {
|
||||||
|
created: {
|
||||||
|
id: 'blob_1',
|
||||||
|
diff: {
|
||||||
|
action: SNIPPET_BLOB_ACTION_CREATE,
|
||||||
|
filePath: '/new/file',
|
||||||
|
previousPath: '/new/file',
|
||||||
|
content: CONTENT_1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
deleted: {
|
||||||
|
id: 'blob_2',
|
||||||
|
diff: {
|
||||||
|
action: SNIPPET_BLOB_ACTION_DELETE,
|
||||||
|
filePath: '/src/delete/me',
|
||||||
|
previousPath: '/src/delete/me',
|
||||||
|
content: CONTENT_1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
updated: {
|
||||||
|
id: 'blob_3',
|
||||||
|
origContent: CONTENT_1,
|
||||||
|
diff: {
|
||||||
|
action: SNIPPET_BLOB_ACTION_UPDATE,
|
||||||
|
filePath: '/lorem.md',
|
||||||
|
previousPath: '/lorem.md',
|
||||||
|
content: CONTENT_2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
renamed: {
|
||||||
|
id: 'blob_4',
|
||||||
|
diff: {
|
||||||
|
action: SNIPPET_BLOB_ACTION_MOVE,
|
||||||
|
filePath: '/dolar.md',
|
||||||
|
previousPath: '/ipsum.md',
|
||||||
|
content: CONTENT_1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
renamedAndUpdated: {
|
||||||
|
id: 'blob_5',
|
||||||
|
origContent: CONTENT_1,
|
||||||
|
diff: {
|
||||||
|
action: SNIPPET_BLOB_ACTION_MOVE,
|
||||||
|
filePath: '/sit.md',
|
||||||
|
previousPath: '/sit/amit.md',
|
||||||
|
content: CONTENT_2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const createBlobsFromTestEntries = (entries, isOrig = false) =>
|
||||||
|
entries.reduce(
|
||||||
|
(acc, { id, diff, origContent }) =>
|
||||||
|
Object.assign(acc, {
|
||||||
|
[id]: {
|
||||||
|
id,
|
||||||
|
content: isOrig && origContent ? origContent : diff.content,
|
||||||
|
path: isOrig ? diff.previousPath : diff.filePath,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should create diff from original files', () => {
|
||||||
|
const origBlobs = createBlobsFromTestEntries(
|
||||||
|
[
|
||||||
|
testEntries.deleted,
|
||||||
|
testEntries.updated,
|
||||||
|
testEntries.renamed,
|
||||||
|
testEntries.renamedAndUpdated,
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
const blobs = createBlobsFromTestEntries([
|
||||||
|
testEntries.created,
|
||||||
|
testEntries.updated,
|
||||||
|
testEntries.renamed,
|
||||||
|
testEntries.renamedAndUpdated,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(diffAll(blobs, origBlobs)).toEqual([
|
||||||
|
testEntries.deleted.diff,
|
||||||
|
testEntries.created.diff,
|
||||||
|
testEntries.updated.diff,
|
||||||
|
testEntries.renamed.diff,
|
||||||
|
testEntries.renamedAndUpdated.diff,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -56,4 +56,26 @@ RSpec.describe Gitlab::ServiceDeskEmail do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.address_for_key' do
|
||||||
|
context 'when service desk address is set' do
|
||||||
|
before do
|
||||||
|
stub_service_desk_email_setting(address: 'address+%{key}@example.com')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns address' do
|
||||||
|
expect(described_class.address_for_key('foo')).to eq('address+foo@example.com')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when service desk address is not set' do
|
||||||
|
before do
|
||||||
|
stub_service_desk_email_setting(address: nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns nil' do
|
||||||
|
expect(described_class.key_from_address('foo')).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -328,9 +328,11 @@ RSpec.describe PagesDomain do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#update_daemon' do
|
describe '#update_daemon' do
|
||||||
|
let_it_be(:project) { create(:project).tap(&:mark_pages_as_deployed) }
|
||||||
|
|
||||||
context 'when usage is serverless' do
|
context 'when usage is serverless' do
|
||||||
it 'does not call the UpdatePagesConfigurationService' do
|
it 'does not call the UpdatePagesConfigurationService' do
|
||||||
expect(Projects::UpdatePagesConfigurationService).not_to receive(:new)
|
expect(PagesUpdateConfigurationWorker).not_to receive(:perform_async)
|
||||||
|
|
||||||
create(:pages_domain, usage: :serverless)
|
create(:pages_domain, usage: :serverless)
|
||||||
end
|
end
|
||||||
|
@ -352,12 +354,30 @@ RSpec.describe PagesDomain do
|
||||||
domain.destroy!
|
domain.destroy!
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'delegates to Projects::UpdatePagesConfigurationService' do
|
it 'delegates to Projects::UpdatePagesConfigurationService when not running async' do
|
||||||
|
stub_feature_flags(async_update_pages_config: false)
|
||||||
|
|
||||||
service = instance_double('Projects::UpdatePagesConfigurationService')
|
service = instance_double('Projects::UpdatePagesConfigurationService')
|
||||||
expect(Projects::UpdatePagesConfigurationService).to receive(:new) { service }
|
expect(Projects::UpdatePagesConfigurationService).to receive(:new) { service }
|
||||||
expect(service).to receive(:execute)
|
expect(service).to receive(:execute)
|
||||||
|
|
||||||
create(:pages_domain)
|
create(:pages_domain, project: project)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "schedules a PagesUpdateConfigurationWorker" do
|
||||||
|
expect(PagesUpdateConfigurationWorker).to receive(:perform_async).with(project.id)
|
||||||
|
|
||||||
|
create(:pages_domain, project: project)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the pages aren't deployed" do
|
||||||
|
let_it_be(:project) { create(:project).tap(&:mark_pages_as_not_deployed) }
|
||||||
|
|
||||||
|
it "does not schedule a PagesUpdateConfigurationWorker" do
|
||||||
|
expect(PagesUpdateConfigurationWorker).not_to receive(:perform_async).with(project.id)
|
||||||
|
|
||||||
|
create(:pages_domain, project: project)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'configuration updates when attributes change' do
|
context 'configuration updates when attributes change' do
|
||||||
|
|
|
@ -1450,16 +1450,69 @@ RSpec.describe Project do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#service_desk_address' do
|
describe '#service_desk_address' do
|
||||||
let_it_be(:project) { create(:project, service_desk_enabled: true) }
|
let_it_be(:project, reload: true) { create(:project, service_desk_enabled: true) }
|
||||||
|
|
||||||
before do
|
subject { project.service_desk_address }
|
||||||
allow(Gitlab::ServiceDesk).to receive(:enabled?).and_return(true)
|
|
||||||
allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)
|
shared_examples 'with incoming email address' do
|
||||||
allow(Gitlab.config.incoming_email).to receive(:address).and_return("test+%{key}@mail.com")
|
context 'when incoming email is enabled' do
|
||||||
|
before do
|
||||||
|
config = double(enabled: true, address: 'test+%{key}@mail.com')
|
||||||
|
allow(::Gitlab.config).to receive(:incoming_email).and_return(config)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'uses project full path as service desk address key' do
|
||||||
|
expect(project.service_desk_address).to eq("test+#{project.full_path_slug}-#{project.project_id}-issue-@mail.com")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when incoming email is disabled' do
|
||||||
|
before do
|
||||||
|
config = double(enabled: false)
|
||||||
|
allow(::Gitlab.config).to receive(:incoming_email).and_return(config)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'uses project full path as service desk address key' do
|
||||||
|
expect(project.service_desk_address).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'uses project full path as service desk address key' do
|
context 'when service_desk_email is disabled' do
|
||||||
expect(project.service_desk_address).to eq("test+#{project.full_path_slug}-#{project.project_id}-issue-@mail.com")
|
before do
|
||||||
|
allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'with incoming email address'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when service_desk_email is enabled' do
|
||||||
|
before do
|
||||||
|
config = double(enabled: true, address: 'foo+%{key}@bar.com')
|
||||||
|
allow(::Gitlab::ServiceDeskEmail).to receive(:config).and_return(config)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when service_desk_custom_address flag is enabled' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(service_desk_custom_address: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns custom address when project_key is set' do
|
||||||
|
create(:service_desk_setting, project: project, project_key: 'key1')
|
||||||
|
|
||||||
|
expect(subject).to eq("foo+#{project.full_path_slug}-key1@bar.com")
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'with incoming email address'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when service_desk_custom_address flag is disabled' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(service_desk_custom_address: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'with incoming email address'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,240 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
RSpec.describe IncidentManagement::CreateIssueService do
|
|
||||||
let(:project) { create(:project, :repository, :private) }
|
|
||||||
let_it_be(:user) { User.alert_bot }
|
|
||||||
let(:alert_starts_at) { Time.current }
|
|
||||||
let(:alert_title) { 'TITLE' }
|
|
||||||
let(:alert_annotations) { { title: alert_title } }
|
|
||||||
|
|
||||||
let(:alert_payload) do
|
|
||||||
build_alert_payload(
|
|
||||||
annotations: alert_annotations,
|
|
||||||
starts_at: alert_starts_at
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:alert) { create(:alert_management_alert, :prometheus, project: project, payload: alert_payload) }
|
|
||||||
let(:service) { described_class.new(project, alert) }
|
|
||||||
|
|
||||||
let(:alert_presenter) do
|
|
||||||
Gitlab::Alerting::Alert.for_alert_management_alert(project: project, alert: alert).present
|
|
||||||
end
|
|
||||||
|
|
||||||
let!(:setting) do
|
|
||||||
create(:project_incident_management_setting, project: project)
|
|
||||||
end
|
|
||||||
|
|
||||||
subject(:execute) { service.execute }
|
|
||||||
|
|
||||||
context 'when create_issue enabled' do
|
|
||||||
let(:issue) { execute.payload[:issue] }
|
|
||||||
|
|
||||||
before do
|
|
||||||
setting.update!(create_issue: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'without issue_template_content' do
|
|
||||||
it 'creates an issue with alert summary only' do
|
|
||||||
expect(execute).to be_success
|
|
||||||
|
|
||||||
expect(issue.author).to eq(user)
|
|
||||||
expect(issue.title).to eq(alert_title)
|
|
||||||
expect(issue.description).to include(alert_presenter.issue_summary_markdown.strip)
|
|
||||||
expect(separator_count(issue.description)).to eq(0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with erroneous issue service' do
|
|
||||||
let(:invalid_issue) do
|
|
||||||
build(:issue, project: project, title: nil).tap(&:valid?)
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:issue_error) { invalid_issue.errors.full_messages.to_sentence }
|
|
||||||
|
|
||||||
it 'returns and logs the issue error' do
|
|
||||||
expect_next_instance_of(Issues::CreateService) do |issue_service|
|
|
||||||
expect(issue_service).to receive(:execute).and_return(invalid_issue)
|
|
||||||
end
|
|
||||||
|
|
||||||
expect(service)
|
|
||||||
.to receive(:log_error)
|
|
||||||
.with(error_message(issue_error))
|
|
||||||
|
|
||||||
expect(execute).to be_error
|
|
||||||
expect(execute.message).to eq(issue_error)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples 'GFM template' do
|
|
||||||
context 'plain content' do
|
|
||||||
let(:template_content) { 'some content' }
|
|
||||||
|
|
||||||
it 'creates an issue appending issue template' do
|
|
||||||
expect(execute).to be_success
|
|
||||||
|
|
||||||
expect(issue.description).to include(alert_presenter.issue_summary_markdown)
|
|
||||||
expect(separator_count(issue.description)).to eq(1)
|
|
||||||
expect(issue.description).to include(template_content)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'quick actions' do
|
|
||||||
let(:user) { create(:user) }
|
|
||||||
let(:plain_text) { 'some content' }
|
|
||||||
|
|
||||||
let(:template_content) do
|
|
||||||
<<~CONTENT
|
|
||||||
#{plain_text}
|
|
||||||
/due tomorrow
|
|
||||||
/assign @#{user.username}
|
|
||||||
CONTENT
|
|
||||||
end
|
|
||||||
|
|
||||||
before do
|
|
||||||
project.add_maintainer(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates an issue interpreting quick actions' do
|
|
||||||
expect(execute).to be_success
|
|
||||||
|
|
||||||
expect(issue.description).to include(plain_text)
|
|
||||||
expect(issue.due_date).to be_present
|
|
||||||
expect(issue.assignees).to eq([user])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with gitlab_incident_markdown' do
|
|
||||||
let(:alert_annotations) do
|
|
||||||
{ title: alert_title, gitlab_incident_markdown: template_content }
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'GFM template'
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with issue_template_content' do
|
|
||||||
before do
|
|
||||||
create_issue_template('bug', template_content)
|
|
||||||
setting.update!(issue_template_key: 'bug')
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'GFM template'
|
|
||||||
|
|
||||||
context 'and gitlab_incident_markdown' do
|
|
||||||
let(:template_content) { 'plain text'}
|
|
||||||
let(:alt_template) { 'alternate text' }
|
|
||||||
let(:alert_annotations) do
|
|
||||||
{ title: alert_title, gitlab_incident_markdown: alt_template }
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'includes both templates' do
|
|
||||||
expect(execute).to be_success
|
|
||||||
|
|
||||||
expect(issue.description).to include(alert_presenter.issue_summary_markdown)
|
|
||||||
expect(issue.description).to include(template_content)
|
|
||||||
expect(issue.description).to include(alt_template)
|
|
||||||
expect(separator_count(issue.description)).to eq(2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def create_issue_template(name, content)
|
|
||||||
project.repository.create_file(
|
|
||||||
project.creator,
|
|
||||||
".gitlab/issue_templates/#{name}.md",
|
|
||||||
content,
|
|
||||||
message: 'message',
|
|
||||||
branch_name: 'master'
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with gitlab alert' do
|
|
||||||
let(:gitlab_alert) { create(:prometheus_alert, project: project) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
alert_payload['labels'] = {
|
|
||||||
'gitlab_alert_id' => gitlab_alert.prometheus_metric_id.to_s
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates an issue' do
|
|
||||||
query_title = "#{gitlab_alert.title} #{gitlab_alert.computed_operator} #{gitlab_alert.threshold}"
|
|
||||||
|
|
||||||
expect(execute).to be_success
|
|
||||||
|
|
||||||
expect(issue.author).to eq(user)
|
|
||||||
expect(issue.title).to eq(alert_presenter.full_title)
|
|
||||||
expect(issue.title).to include(gitlab_alert.environment.name)
|
|
||||||
expect(issue.title).to include(query_title)
|
|
||||||
expect(issue.title).to include('for 5 minutes')
|
|
||||||
expect(issue.description).to include(alert_presenter.issue_summary_markdown.strip)
|
|
||||||
expect(separator_count(issue.description)).to eq(0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'with invalid alert payload' do
|
|
||||||
shared_examples 'invalid alert' do
|
|
||||||
it 'does not create an issue' do
|
|
||||||
expect(service)
|
|
||||||
.to receive(:log_error)
|
|
||||||
.with(error_message('invalid alert'))
|
|
||||||
|
|
||||||
expect(execute).to be_error
|
|
||||||
expect(execute.message).to eq('invalid alert')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'without title' do
|
|
||||||
let(:alert_annotations) { {} }
|
|
||||||
|
|
||||||
it_behaves_like 'invalid alert'
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'without startsAt' do
|
|
||||||
let(:alert_starts_at) { nil }
|
|
||||||
|
|
||||||
it_behaves_like 'invalid alert'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when create_issue disabled' do
|
|
||||||
before do
|
|
||||||
setting.update!(create_issue: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns an error' do
|
|
||||||
expect(service)
|
|
||||||
.to receive(:log_error)
|
|
||||||
.with(error_message('setting disabled'))
|
|
||||||
|
|
||||||
expect(execute).to be_error
|
|
||||||
expect(execute.message).to eq('setting disabled')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def build_alert_payload(annotations: {}, starts_at: Time.current)
|
|
||||||
{
|
|
||||||
'annotations' => annotations.stringify_keys
|
|
||||||
}.tap do |payload|
|
|
||||||
payload['startsAt'] = starts_at.rfc3339 if starts_at
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def error_message(message)
|
|
||||||
%{Cannot create incident issue for "#{project.full_name}": #{message}}
|
|
||||||
end
|
|
||||||
|
|
||||||
def separator_count(text)
|
|
||||||
summary_separator = "\n\n---\n\n"
|
|
||||||
|
|
||||||
text.scan(summary_separator).size
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -31,6 +31,17 @@ RSpec.describe ServiceDeskSettings::UpdateService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when project_key is an empty string' do
|
||||||
|
let(:params) { { project_key: '' } }
|
||||||
|
|
||||||
|
it 'sets nil project_key' do
|
||||||
|
result = described_class.new(settings.project, user, params).execute
|
||||||
|
|
||||||
|
expect(result[:status]).to eq :success
|
||||||
|
expect(settings.reload.project_key).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with invalid params' do
|
context 'with invalid params' do
|
||||||
let(:params) { { outgoing_name: 'x' * 256 } }
|
let(:params) { { outgoing_name: 'x' * 256 } }
|
||||||
|
|
||||||
|
|
|
@ -18,15 +18,15 @@ RSpec.describe IncidentManagement::ProcessAlertWorker do
|
||||||
before do
|
before do
|
||||||
allow(Gitlab::AppLogger).to receive(:warn).and_call_original
|
allow(Gitlab::AppLogger).to receive(:warn).and_call_original
|
||||||
|
|
||||||
allow(IncidentManagement::CreateIssueService)
|
allow(AlertManagement::CreateAlertIssueService)
|
||||||
.to receive(:new).with(alert.project, alert)
|
.to receive(:new).with(alert, User.alert_bot)
|
||||||
.and_call_original
|
.and_call_original
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'creates issue successfully' do
|
shared_examples 'creates issue successfully' do
|
||||||
it 'creates an issue' do
|
it 'creates an issue' do
|
||||||
expect(IncidentManagement::CreateIssueService)
|
expect(AlertManagement::CreateAlertIssueService)
|
||||||
.to receive(:new).with(alert.project, alert)
|
.to receive(:new).with(alert, User.alert_bot)
|
||||||
|
|
||||||
expect { subject }.to change { Issue.count }.by(1)
|
expect { subject }.to change { Issue.count }.by(1)
|
||||||
end
|
end
|
||||||
|
@ -58,10 +58,10 @@ RSpec.describe IncidentManagement::ProcessAlertWorker do
|
||||||
subject
|
subject
|
||||||
|
|
||||||
expect(Gitlab::AppLogger).to have_received(:warn).with(
|
expect(Gitlab::AppLogger).to have_received(:warn).with(
|
||||||
message: 'Cannot link an Issue with Alert',
|
message: 'Cannot process an Incident',
|
||||||
issue_id: created_issue.id,
|
issue_id: created_issue.id,
|
||||||
alert_id: alert.id,
|
alert_id: alert.id,
|
||||||
alert_errors: { hosts: ['hosts array is over 255 chars'] }
|
errors: 'Hosts hosts array is over 255 chars'
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -80,7 +80,7 @@ RSpec.describe IncidentManagement::ProcessAlertWorker do
|
||||||
subject { described_class.new.perform(nil, nil, invalid_alert_id) }
|
subject { described_class.new.perform(nil, nil, invalid_alert_id) }
|
||||||
|
|
||||||
it 'does not create issues' do
|
it 'does not create issues' do
|
||||||
expect(IncidentManagement::CreateIssueService).not_to receive(:new)
|
expect(AlertManagement::CreateAlertIssueService).not_to receive(:new)
|
||||||
|
|
||||||
expect { subject }.not_to change { Issue.count }
|
expect { subject }.not_to change { Issue.count }
|
||||||
end
|
end
|
||||||
|
|
0
vendor/gitignore/C++.gitignore
vendored
Normal file → Executable file
0
vendor/gitignore/C++.gitignore
vendored
Normal file → Executable file
14
vendor/gitignore/Clojure.gitignore
vendored
14
vendor/gitignore/Clojure.gitignore
vendored
|
@ -1,14 +0,0 @@
|
||||||
pom.xml
|
|
||||||
pom.xml.asc
|
|
||||||
*.jar
|
|
||||||
*.class
|
|
||||||
/lib/
|
|
||||||
/classes/
|
|
||||||
/target/
|
|
||||||
/checkouts/
|
|
||||||
.lein-deps-sum
|
|
||||||
.lein-repl-history
|
|
||||||
.lein-plugins/
|
|
||||||
.lein-failures
|
|
||||||
.nrepl-port
|
|
||||||
.cpcache/
|
|
1
vendor/gitignore/Clojure.gitignore
vendored
Symbolic link
1
vendor/gitignore/Clojure.gitignore
vendored
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
Leiningen.gitignore
|
32
vendor/gitignore/Fortran.gitignore
vendored
32
vendor/gitignore/Fortran.gitignore
vendored
|
@ -1,32 +0,0 @@
|
||||||
# Prerequisites
|
|
||||||
*.d
|
|
||||||
|
|
||||||
# Compiled Object files
|
|
||||||
*.slo
|
|
||||||
*.lo
|
|
||||||
*.o
|
|
||||||
*.obj
|
|
||||||
|
|
||||||
# Precompiled Headers
|
|
||||||
*.gch
|
|
||||||
*.pch
|
|
||||||
|
|
||||||
# Compiled Dynamic libraries
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
*.dll
|
|
||||||
|
|
||||||
# Fortran module files
|
|
||||||
*.mod
|
|
||||||
*.smod
|
|
||||||
|
|
||||||
# Compiled Static libraries
|
|
||||||
*.lai
|
|
||||||
*.la
|
|
||||||
*.a
|
|
||||||
*.lib
|
|
||||||
|
|
||||||
# Executables
|
|
||||||
*.exe
|
|
||||||
*.out
|
|
||||||
*.app
|
|
1
vendor/gitignore/Fortran.gitignore
vendored
Symbolic link
1
vendor/gitignore/Fortran.gitignore
vendored
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
C++.gitignore
|
0
vendor/gitignore/Java.gitignore
vendored
Normal file → Executable file
0
vendor/gitignore/Java.gitignore
vendored
Normal file → Executable file
23
vendor/gitignore/Kotlin.gitignore
vendored
23
vendor/gitignore/Kotlin.gitignore
vendored
|
@ -1,23 +0,0 @@
|
||||||
# Compiled class file
|
|
||||||
*.class
|
|
||||||
|
|
||||||
# Log file
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# BlueJ files
|
|
||||||
*.ctxt
|
|
||||||
|
|
||||||
# Mobile Tools for Java (J2ME)
|
|
||||||
.mtj.tmp/
|
|
||||||
|
|
||||||
# Package Files #
|
|
||||||
*.jar
|
|
||||||
*.war
|
|
||||||
*.nar
|
|
||||||
*.ear
|
|
||||||
*.zip
|
|
||||||
*.tar.gz
|
|
||||||
*.rar
|
|
||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
|
||||||
hs_err_pid*
|
|
1
vendor/gitignore/Kotlin.gitignore
vendored
Symbolic link
1
vendor/gitignore/Kotlin.gitignore
vendored
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
Java.gitignore
|
Loading…
Reference in a new issue