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>
|
||||
import { GlFormInput } from '@gitlab/ui';
|
||||
import { GlFormInput, GlButton } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlFormInput,
|
||||
GlButton,
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
|
@ -11,6 +12,16 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
canDelete: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
showDelete: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -21,17 +32,27 @@ export default {
|
|||
</script>
|
||||
<template>
|
||||
<div class="js-file-title file-title-flex-parent">
|
||||
<gl-form-input
|
||||
id="snippet_file_name"
|
||||
v-model="name"
|
||||
:placeholder="
|
||||
s__('Snippets|Give your file a name to add code highlighting, e.g. example.rb for Ruby')
|
||||
"
|
||||
name="snippet_file_name"
|
||||
class="form-control js-snippet-file-name"
|
||||
type="text"
|
||||
v-bind="$attrs"
|
||||
@change="$emit('input', name)"
|
||||
/>
|
||||
<div class="gl-display-flex gl-align-items-center gl-w-full">
|
||||
<gl-form-input
|
||||
v-model="name"
|
||||
:placeholder="
|
||||
s__('Snippets|Give your file a name to add code highlighting, e.g. example.rb for Ruby')
|
||||
"
|
||||
name="snippet_file_name"
|
||||
class="form-control js-snippet-file-name"
|
||||
type="text"
|
||||
v-bind="$attrs"
|
||||
@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>
|
||||
</template>
|
||||
|
|
|
@ -24,9 +24,9 @@ export default {
|
|||
class="settings no-animate qa-incident-management-settings"
|
||||
>
|
||||
<div class="settings-header">
|
||||
<h3 ref="sectionHeader" class="h4">
|
||||
<h4 ref="sectionHeader" class="gl-my-3! gl-py-1">
|
||||
{{ $options.i18n.headerText }}
|
||||
</h3>
|
||||
</h4>
|
||||
<gl-button ref="toggleBtn" class="js-settings-toggle">{{
|
||||
$options.i18n.expandBtnLabel
|
||||
}}</gl-button>
|
||||
|
|
|
@ -54,7 +54,7 @@ export default {
|
|||
},
|
||||
},
|
||||
strings: {
|
||||
deleteProject: __('Remove project'),
|
||||
deleteProject: __('Delete project'),
|
||||
title: __('Delete project. Are you ABSOLUTELY SURE?'),
|
||||
confirmText: __('Please type the following to confirm:'),
|
||||
},
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
SNIPPET_BLOB_ACTION_UPDATE,
|
||||
SNIPPET_BLOB_ACTION_MOVE,
|
||||
} 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 SnippetDescriptionEdit from './snippet_description_edit.vue';
|
||||
import { SNIPPET_MARK_EDIT_APP_START } from '~/performance_constants';
|
||||
|
@ -27,7 +27,7 @@ export default {
|
|||
components: {
|
||||
SnippetDescriptionEdit,
|
||||
SnippetVisibilityEdit,
|
||||
SnippetBlobEdit,
|
||||
SnippetBlobActionsEdit,
|
||||
TitleField,
|
||||
FormFooterActions,
|
||||
GlButton,
|
||||
|
@ -261,15 +261,7 @@ export default {
|
|||
:markdown-preview-path="markdownPreviewPath"
|
||||
:markdown-docs-path="markdownDocsPath"
|
||||
/>
|
||||
<template v-if="blobs.length">
|
||||
<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-blob-actions-edit :blobs="blobs" @blob-updated="updateBlobActions" />
|
||||
|
||||
<snippet-visibility-edit
|
||||
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>
|
||||
<template>
|
||||
<div class="form-group file-editor">
|
||||
<label>{{ s__('Snippets|File') }}</label>
|
||||
<div class="file-holder snippet">
|
||||
<blob-header-edit v-model="filePath" data-qa-selector="file_name_field" />
|
||||
<gl-loading-icon
|
||||
v-if="isContentLoading"
|
||||
:label="__('Loading snippet')"
|
||||
size="lg"
|
||||
class="loading-animation prepend-top-20 append-bottom-20"
|
||||
/>
|
||||
<blob-content-edit v-else v-model="content" :file-global-id="id" :file-name="filePath" />
|
||||
</div>
|
||||
<div class="file-holder snippet">
|
||||
<blob-header-edit
|
||||
id="snippet_file_path"
|
||||
v-model="filePath"
|
||||
data-qa-selector="file_name_field"
|
||||
/>
|
||||
<gl-loading-icon
|
||||
v-if="isContentLoading"
|
||||
:label="__('Loading snippet')"
|
||||
size="lg"
|
||||
class="loading-animation prepend-top-20 append-bottom-20"
|
||||
/>
|
||||
<blob-content-edit v-else v-model="content" :file-global-id="id" :file-name="filePath" />
|
||||
</div>
|
||||
</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_UPDATE = 'update';
|
||||
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
|
||||
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)
|
||||
end
|
||||
|
||||
before_action only: [:edit] do
|
||||
push_frontend_feature_flag(:service_desk_custom_address, @project)
|
||||
end
|
||||
|
||||
|
|
|
@ -130,7 +130,7 @@ module GroupsHelper
|
|||
end
|
||||
|
||||
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 }
|
||||
end
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ module ProjectsHelper
|
|||
end
|
||||
|
||||
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 }
|
||||
end
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
class PagesDomain < ApplicationRecord
|
||||
include Presentable
|
||||
include FromUnion
|
||||
include AfterCommitQueue
|
||||
|
||||
VERIFICATION_KEY = 'gitlab-pages-verification-code'
|
||||
VERIFICATION_THRESHOLD = 3.days.freeze
|
||||
|
@ -222,6 +223,8 @@ class PagesDomain < ApplicationRecord
|
|||
private
|
||||
|
||||
def pages_deployed?
|
||||
return false unless project
|
||||
|
||||
# TODO: remove once `pages_metadatum` is migrated
|
||||
# https://gitlab.com/gitlab-org/gitlab/issues/33106
|
||||
unless project.pages_metadatum
|
||||
|
@ -244,8 +247,13 @@ class PagesDomain < ApplicationRecord
|
|||
# rubocop: disable CodeReuse/ServiceClass
|
||||
def update_daemon
|
||||
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
|
||||
# rubocop: enable CodeReuse/ServiceClass
|
||||
|
||||
|
|
|
@ -2468,6 +2468,10 @@ class Project < ApplicationRecord
|
|||
alias_method :service_desk_enabled?, :service_desk_enabled
|
||||
|
||||
def service_desk_address
|
||||
service_desk_custom_address || service_desk_incoming_address
|
||||
end
|
||||
|
||||
def service_desk_incoming_address
|
||||
return unless service_desk_enabled?
|
||||
|
||||
config = Gitlab.config.incoming_email
|
||||
|
@ -2476,6 +2480,16 @@ class Project < ApplicationRecord
|
|||
config.address&.gsub(wildcard, "#{full_path_slug}-#{id}-issue-")
|
||||
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
|
||||
if namespace.has_parent?
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ module ServiceDeskSettings
|
|||
params.delete(:project_key)
|
||||
end
|
||||
|
||||
params[:project_key] = nil if params[:project_key].blank?
|
||||
|
||||
if settings.update(params)
|
||||
success
|
||||
else
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
.controls
|
||||
= 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 _('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
|
||||
%span.badge.badge-pill
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
- confirm_phrase = s_('DeleteProject|Delete %{name}') % { name: project.full_name }
|
||||
|
||||
.sub-section
|
||||
%h4.danger-title= _('Remove project')
|
||||
%h4.danger-title= _('Delete project')
|
||||
%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
|
||||
%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 } }
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
- if ::Gitlab::ServiceDesk.supported?
|
||||
.js-service-desk-setting-root{ data: { endpoint: project_service_desk_path(@project),
|
||||
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}",
|
||||
outgoing_name: "#{@project.service_desk_setting&.outgoing_name}",
|
||||
project_key: "#{@project.service_desk_setting&.project_key}",
|
||||
|
|
|
@ -22,4 +22,4 @@
|
|||
|
||||
- if can? current_user, :remove_project, @project
|
||||
.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
|
||||
|
||||
result = create_issue_for(alert)
|
||||
return unless result.success?
|
||||
return if result.success?
|
||||
|
||||
new_issue = result.payload[:issue]
|
||||
link_issue_with_alert(alert, new_issue.id)
|
||||
log_warning(alert, result)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -30,19 +29,19 @@ module IncidentManagement
|
|||
end
|
||||
|
||||
def create_issue_for(alert)
|
||||
IncidentManagement::CreateIssueService
|
||||
.new(alert.project, alert)
|
||||
AlertManagement::CreateAlertIssueService
|
||||
.new(alert, User.alert_bot)
|
||||
.execute
|
||||
end
|
||||
|
||||
def link_issue_with_alert(alert, issue_id)
|
||||
return if alert.update(issue_id: issue_id)
|
||||
def log_warning(alert, result)
|
||||
issue_id = result.payload[:issue]&.id
|
||||
|
||||
Gitlab::AppLogger.warn(
|
||||
message: 'Cannot link an Issue with Alert',
|
||||
message: 'Cannot process an Incident',
|
||||
issue_id: issue_id,
|
||||
alert_id: alert.id,
|
||||
alert_errors: alert.errors.messages
|
||||
errors: result.message
|
||||
)
|
||||
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
|
||||
project = Project.find_by_full_path('group-changeme/project-changeme')
|
||||
|
||||
# Delete the project
|
||||
# Immediately delete the project
|
||||
::Projects::DestroyService.new(project, user, {}).execute
|
||||
```
|
||||
|
||||
|
|
|
@ -1832,11 +1832,11 @@ Example response:
|
|||
}
|
||||
```
|
||||
|
||||
## Remove project
|
||||
## Delete project
|
||||
|
||||
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,
|
||||
group admins can [configure](../user/group/index.md#enabling-delayed-project-removal-premium) projects within a group
|
||||
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_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`. |
|
||||
| `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). |
|
||||
| `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. |
|
||||
|
|
|
@ -24,7 +24,8 @@ should be leveraged:
|
|||
- When development of a feature will be spread across multiple merge
|
||||
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
|
||||
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.
|
||||
|
|
|
@ -636,7 +636,7 @@ Errors in `locale/zh_HK/gitlab.po`:
|
|||
Syntax error in msgstr
|
||||
Syntax error in message_line
|
||||
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
|
||||
Errors in `locale/zh_TW/gitlab.po`:
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
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).
|
||||
1. Navigate to your dashboard at **Operations > Metrics**.
|
||||
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
|
||||
[Prometheus Metrics Library documentation](../../user/project/integrations/prometheus_library/index.md).
|
||||
|
||||
To view the metrics dashboard for an environment that has
|
||||
To view the metrics dashboard for an environment that is
|
||||
To view the [default metrics dashboard](dashboards/default.md) for an environment that is
|
||||
[configured to gather metrics](#configure-prometheus-to-gather-metrics):
|
||||
|
||||
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).
|
||||
1. In your project, navigate to **Operations > Metrics**.
|
||||
|
||||
GitLab displays the default metrics dashboard for the environment, like the
|
||||
following example:
|
||||
GitLab displays the [default metrics dashboard](dashboards/default.md) for the environment,
|
||||
like the following example:
|
||||
|
||||
![Example of metrics dashboard](img/example-dashboard_v13_3.png)
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@ type: reference, howto
|
|||
|
||||
# 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
|
||||
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
|
||||
|
|
|
@ -156,7 +156,7 @@ The following table depicts the various user permission levels in a project.
|
|||
| Transfer project to another namespace | | | | | ✓ |
|
||||
| Rename project | | | | | ✓ |
|
||||
| Remove fork relationship | | | | | ✓ |
|
||||
| Remove project | | | | | ✓ |
|
||||
| Delete project | | | | | ✓ |
|
||||
| Archive project | | | | | ✓ |
|
||||
| Delete issues | | | | | ✓ |
|
||||
| Delete pipelines | | | | | ✓ |
|
||||
|
@ -416,7 +416,7 @@ instance and project. In addition, all admins can use the admin interface under
|
|||
| See commits and jobs | ✓ | ✓ | ✓ | ✓ |
|
||||
| Retry or cancel job | | ✓ | ✓ | ✓ |
|
||||
| Erase job artifacts and trace | | ✓ (*1*) | ✓ | ✓ |
|
||||
| Remove project | | | ✓ | ✓ |
|
||||
| Delete project | | | ✓ | ✓ |
|
||||
| Create project | | | ✓ | ✓ |
|
||||
| Change project configuration | | | ✓ | ✓ |
|
||||
| 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)
|
||||
- [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. Expand the **Advanced** section.
|
||||
1. Scroll down to the **Remove project** section.
|
||||
1. Click **Remove project**
|
||||
1. Scroll down to the **Delete project** section.
|
||||
1. Click **Delete project**
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
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)**
|
||||
|
||||
|
|
|
@ -219,20 +219,20 @@ NOTE: **Note:**
|
|||
GitLab administrators can use the admin interface to move any project to any
|
||||
namespace if needed.
|
||||
|
||||
#### Remove a project
|
||||
#### Delete a project
|
||||
|
||||
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. 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.
|
||||
|
||||
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,
|
||||
group admins can [configure](../../group/index.md#enabling-delayed-project-removal-premium) projects within a group
|
||||
to be deleted after a delayed period.
|
||||
|
|
|
@ -448,7 +448,7 @@ module API
|
|||
.execute.map { |lang| [lang.name, lang.share] }.to_h
|
||||
end
|
||||
|
||||
desc 'Remove a project'
|
||||
desc 'Delete a project'
|
||||
delete ":id" do
|
||||
authorize! :remove_project, user_project
|
||||
|
||||
|
|
|
@ -17,6 +17,12 @@ module Gitlab
|
|||
def config
|
||||
Gitlab.config.service_desk_email
|
||||
end
|
||||
|
||||
def address_for_key(key)
|
||||
return if config.address.blank?
|
||||
|
||||
config.address.sub(Gitlab::IncomingEmail::WILDCARD_PLACEHOLDER, key)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7884,15 +7884,27 @@ msgstr ""
|
|||
msgid "Deleted"
|
||||
msgstr ""
|
||||
|
||||
msgid "Deleted Projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "Deleted chat nickname: %{chat_name}!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Deleted in this version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Deleted projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "Deleted projects cannot be restored!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Deleting"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
@ -7902,6 +7914,9 @@ msgstr ""
|
|||
msgid "Deleting the license failed. You are not permitted to perform this action."
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
@ -20240,9 +20255,6 @@ msgstr ""
|
|||
msgid "Remove priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remove project"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remove secondary node"
|
||||
msgstr ""
|
||||
|
||||
|
@ -20276,9 +20288,6 @@ msgstr ""
|
|||
msgid "Removed %{type} with id %{id}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Removed Projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "Removed all labels."
|
||||
msgstr ""
|
||||
|
||||
|
@ -20291,12 +20300,6 @@ msgstr ""
|
|||
msgid "Removed parent epic %{epic_ref}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Removed projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "Removed projects cannot be restored!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Removed spent time."
|
||||
msgstr ""
|
||||
|
||||
|
@ -20345,15 +20348,9 @@ msgstr ""
|
|||
msgid "Removes time estimate."
|
||||
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…"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
@ -22552,6 +22549,9 @@ msgstr ""
|
|||
msgid "SnippetsEmptyState|There are no snippets to show."
|
||||
msgstr ""
|
||||
|
||||
msgid "Snippets|Delete file"
|
||||
msgstr ""
|
||||
|
||||
msgid "Snippets|Description (optional)"
|
||||
msgstr ""
|
||||
|
||||
|
@ -22744,10 +22744,10 @@ msgstr ""
|
|||
msgid "Something went wrong, unable to add projects to dashboard"
|
||||
msgstr ""
|
||||
|
||||
msgid "Something went wrong, unable to get projects"
|
||||
msgid "Something went wrong, unable to delete project"
|
||||
msgstr ""
|
||||
|
||||
msgid "Something went wrong, unable to remove project"
|
||||
msgid "Something went wrong, unable to get projects"
|
||||
msgstr ""
|
||||
|
||||
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."
|
||||
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 ""
|
||||
|
||||
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 ""
|
||||
|
||||
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
|
||||
|
||||
it 'renders form for incident management' do
|
||||
expect(page).to have_selector('h3', text: 'Incidents')
|
||||
expect(page).to have_selector('h4', text: 'Incidents')
|
||||
end
|
||||
|
||||
it 'sets correct default values' do
|
||||
|
|
|
@ -254,13 +254,13 @@ RSpec.describe 'Project' do
|
|||
end
|
||||
|
||||
it 'focuses on the confirmation field' do
|
||||
click_button 'Remove project'
|
||||
click_button 'Delete project'
|
||||
|
||||
expect(page).to have_selector '#confirm_name_input:focus'
|
||||
end
|
||||
|
||||
it 'removes 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)
|
||||
it 'deletes a project', :sidekiq_might_not_need_inline do
|
||||
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(Project.all.count).to be_zero
|
||||
expect(project.issues).to be_empty
|
||||
|
|
|
@ -4,13 +4,18 @@ exports[`Blob Header Editing rendering matches the snapshot 1`] = `
|
|||
<div
|
||||
class="js-file-title file-title-flex-parent"
|
||||
>
|
||||
<gl-form-input-stub
|
||||
class="form-control js-snippet-file-name"
|
||||
id="snippet_file_name"
|
||||
name="snippet_file_name"
|
||||
placeholder="Give your file a name to add code highlighting, e.g. example.rb for Ruby"
|
||||
type="text"
|
||||
value="foo.md"
|
||||
/>
|
||||
<div
|
||||
class="gl-display-flex gl-align-items-center gl-w-full"
|
||||
>
|
||||
<gl-form-input-stub
|
||||
class="form-control js-snippet-file-name"
|
||||
name="snippet_file_name"
|
||||
placeholder="Give your file a name to add code highlighting, e.g. example.rb for Ruby"
|
||||
type="text"
|
||||
value="foo.md"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import BlobEditHeader from '~/blob/components/blob_edit_header.vue';
|
||||
import { GlFormInput } from '@gitlab/ui';
|
||||
import { GlFormInput, GlButton } from '@gitlab/ui';
|
||||
|
||||
describe('Blob Header Editing', () => {
|
||||
let wrapper;
|
||||
const value = 'foo.md';
|
||||
|
||||
function createComponent() {
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = shallowMount(BlobEditHeader, {
|
||||
propsData: {
|
||||
value,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
const findDeleteButton = () =>
|
||||
wrapper.findAll(GlButton).wrappers.find(x => x.text() === 'Delete file');
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
|
@ -30,6 +33,10 @@ describe('Blob Header Editing', () => {
|
|||
it('contains a form input field', () => {
|
||||
expect(wrapper.contains(GlFormInput)).toBe(true);
|
||||
});
|
||||
|
||||
it('does not show delete button', () => {
|
||||
expect(findDeleteButton()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
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
|
||||
class="settings-header"
|
||||
>
|
||||
<h3
|
||||
class="h4"
|
||||
<h4
|
||||
class="gl-my-3! gl-py-1"
|
||||
>
|
||||
|
||||
Incidents
|
||||
|
||||
</h3>
|
||||
</h4>
|
||||
|
||||
<gl-button-stub
|
||||
category="tertiary"
|
||||
|
|
|
@ -24,7 +24,7 @@ exports[`Project remove modal initialized matches the snapshot 1`] = `
|
|||
tabindex="0"
|
||||
variant="danger"
|
||||
>
|
||||
Remove project
|
||||
Delete project
|
||||
</gl-button-stub>
|
||||
|
||||
<gl-modal-stub
|
||||
|
|
|
@ -25,7 +25,7 @@ exports[`Project remove modal intialized matches the snapshot 1`] = `
|
|||
tabindex="0"
|
||||
variant="danger"
|
||||
>
|
||||
Remove project
|
||||
Delete project
|
||||
</gl-button-stub>
|
||||
|
||||
<b-modal-stub
|
||||
|
|
|
@ -2,25 +2,18 @@
|
|||
|
||||
exports[`Snippet Blob Edit component rendering matches the snapshot 1`] = `
|
||||
<div
|
||||
class="form-group file-editor"
|
||||
class="file-holder snippet"
|
||||
>
|
||||
<label>
|
||||
File
|
||||
</label>
|
||||
<blob-header-edit-stub
|
||||
data-qa-selector="file_name_field"
|
||||
id="snippet_file_path"
|
||||
value="lorem.txt"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="file-holder snippet"
|
||||
>
|
||||
<blob-header-edit-stub
|
||||
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>
|
||||
<blob-content-edit-stub
|
||||
fileglobalid="0a3d"
|
||||
filename="lorem.txt"
|
||||
value="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -7,7 +7,7 @@ import { redirectTo } from '~/lib/utils/url_utility';
|
|||
import SnippetEditApp from '~/snippets/components/edit.vue';
|
||||
import SnippetDescriptionEdit from '~/snippets/components/snippet_description_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 FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue';
|
||||
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(SnippetDescriptionEdit)).toBe(true);
|
||||
expect(wrapper.contains(SnippetBlobEdit)).toBe(true);
|
||||
expect(wrapper.contains(SnippetBlobActionsEdit)).toBe(true);
|
||||
expect(wrapper.contains(SnippetVisibilityEdit)).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
|
||||
|
||||
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
|
||||
|
|
|
@ -328,9 +328,11 @@ RSpec.describe PagesDomain do
|
|||
end
|
||||
|
||||
describe '#update_daemon' do
|
||||
let_it_be(:project) { create(:project).tap(&:mark_pages_as_deployed) }
|
||||
|
||||
context 'when usage is serverless' 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)
|
||||
end
|
||||
|
@ -352,12 +354,30 @@ RSpec.describe PagesDomain do
|
|||
domain.destroy!
|
||||
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')
|
||||
expect(Projects::UpdatePagesConfigurationService).to receive(:new) { service }
|
||||
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
|
||||
|
||||
context 'configuration updates when attributes change' do
|
||||
|
|
|
@ -1450,16 +1450,69 @@ RSpec.describe Project do
|
|||
end
|
||||
|
||||
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
|
||||
allow(Gitlab::ServiceDesk).to receive(:enabled?).and_return(true)
|
||||
allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)
|
||||
allow(Gitlab.config.incoming_email).to receive(:address).and_return("test+%{key}@mail.com")
|
||||
subject { project.service_desk_address }
|
||||
|
||||
shared_examples 'with incoming email address' do
|
||||
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
|
||||
|
||||
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")
|
||||
context 'when service_desk_email is disabled' do
|
||||
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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
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
|
||||
let(:params) { { outgoing_name: 'x' * 256 } }
|
||||
|
||||
|
|
|
@ -18,15 +18,15 @@ RSpec.describe IncidentManagement::ProcessAlertWorker do
|
|||
before do
|
||||
allow(Gitlab::AppLogger).to receive(:warn).and_call_original
|
||||
|
||||
allow(IncidentManagement::CreateIssueService)
|
||||
.to receive(:new).with(alert.project, alert)
|
||||
allow(AlertManagement::CreateAlertIssueService)
|
||||
.to receive(:new).with(alert, User.alert_bot)
|
||||
.and_call_original
|
||||
end
|
||||
|
||||
shared_examples 'creates issue successfully' do
|
||||
it 'creates an issue' do
|
||||
expect(IncidentManagement::CreateIssueService)
|
||||
.to receive(:new).with(alert.project, alert)
|
||||
expect(AlertManagement::CreateAlertIssueService)
|
||||
.to receive(:new).with(alert, User.alert_bot)
|
||||
|
||||
expect { subject }.to change { Issue.count }.by(1)
|
||||
end
|
||||
|
@ -58,10 +58,10 @@ RSpec.describe IncidentManagement::ProcessAlertWorker do
|
|||
subject
|
||||
|
||||
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,
|
||||
alert_id: alert.id,
|
||||
alert_errors: { hosts: ['hosts array is over 255 chars'] }
|
||||
errors: 'Hosts hosts array is over 255 chars'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -80,7 +80,7 @@ RSpec.describe IncidentManagement::ProcessAlertWorker do
|
|||
subject { described_class.new.perform(nil, nil, invalid_alert_id) }
|
||||
|
||||
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 }
|
||||
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