Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0ebaa8a2fd
commit
03c3f9f501
35 changed files with 312 additions and 223 deletions
|
@ -277,6 +277,7 @@ export default {
|
||||||
<h5
|
<h5
|
||||||
ref="graphTitle"
|
ref="graphTitle"
|
||||||
class="prometheus-graph-title gl-font-lg font-weight-bold text-truncate append-right-8"
|
class="prometheus-graph-title gl-font-lg font-weight-bold text-truncate append-right-8"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</h5>
|
</h5>
|
||||||
|
|
|
@ -52,10 +52,17 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="showPanels" ref="graph-group" class="card prometheus-panel">
|
<div v-if="showPanels" ref="graph-group" class="card prometheus-panel" tabindex="0">
|
||||||
<div class="card-header d-flex align-items-center">
|
<div class="card-header d-flex align-items-center">
|
||||||
<h4 class="flex-grow-1">{{ name }}</h4>
|
<h4 class="flex-grow-1">{{ name }}</h4>
|
||||||
<a role="button" class="js-graph-group-toggle" @click="collapse">
|
<a
|
||||||
|
data-testid="group-toggle-button"
|
||||||
|
role="button"
|
||||||
|
class="js-graph-group-toggle gl-text-gray-900"
|
||||||
|
tabindex="0"
|
||||||
|
@click="collapse"
|
||||||
|
@keyup.enter="collapse"
|
||||||
|
>
|
||||||
<icon :size="16" :aria-label="__('Toggle collapse')" :name="caretIcon" />
|
<icon :size="16" :aria-label="__('Toggle collapse')" :name="caretIcon" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -754,7 +754,8 @@
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
.issue-weight-icon {
|
.issue-weight-icon,
|
||||||
|
.issue-estimate-icon {
|
||||||
vertical-align: sub;
|
vertical-align: sub;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,10 @@ module AlertManagement
|
||||||
monitoring_tool == Gitlab::AlertManagement::AlertParams::MONITORING_TOOLS[:prometheus]
|
monitoring_tool == Gitlab::AlertManagement::AlertParams::MONITORING_TOOLS[:prometheus]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def register_new_event!
|
||||||
|
increment!(:events, 1)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def hosts_length
|
def hosts_length
|
||||||
|
|
|
@ -435,15 +435,7 @@ class JiraService < IssueTrackerService
|
||||||
yield
|
yield
|
||||||
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, URI::InvalidURIError, JIRA::HTTPError, OpenSSL::SSL::SSLError => error
|
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, URI::InvalidURIError, JIRA::HTTPError, OpenSSL::SSL::SSLError => error
|
||||||
@error = error
|
@error = error
|
||||||
log_error(
|
log_error("Error sending message", client_url: client_url, error: @error.message)
|
||||||
"Error sending message",
|
|
||||||
client_url: client_url,
|
|
||||||
error: {
|
|
||||||
exception_class: error.class.name,
|
|
||||||
exception_message: error.message,
|
|
||||||
exception_backtrace: Gitlab::BacktraceCleaner.clean_backtrace(error.backtrace)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ module Projects
|
||||||
return forbidden unless alerts_service_activated?
|
return forbidden unless alerts_service_activated?
|
||||||
return unauthorized unless valid_token?(token)
|
return unauthorized unless valid_token?(token)
|
||||||
|
|
||||||
alert = create_alert
|
alert = process_alert
|
||||||
return bad_request unless alert.persisted?
|
return bad_request unless alert.persisted?
|
||||||
|
|
||||||
process_incident_issues(alert) if process_issues?
|
process_incident_issues(alert) if process_issues?
|
||||||
|
@ -26,13 +26,29 @@ module Projects
|
||||||
delegate :alerts_service, :alerts_service_activated?, to: :project
|
delegate :alerts_service, :alerts_service_activated?, to: :project
|
||||||
|
|
||||||
def am_alert_params
|
def am_alert_params
|
||||||
Gitlab::AlertManagement::AlertParams.from_generic_alert(project: project, payload: params.to_h)
|
strong_memoize(:am_alert_params) do
|
||||||
|
Gitlab::AlertManagement::AlertParams.from_generic_alert(project: project, payload: params.to_h)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_alert
|
||||||
|
if alert = find_alert_by_fingerprint(am_alert_params[:fingerprint])
|
||||||
|
alert.register_new_event!
|
||||||
|
else
|
||||||
|
create_alert
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_alert
|
def create_alert
|
||||||
AlertManagement::Alert.create(am_alert_params)
|
AlertManagement::Alert.create(am_alert_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def find_alert_by_fingerprint(fingerprint)
|
||||||
|
return unless fingerprint
|
||||||
|
|
||||||
|
AlertManagement::Alert.for_fingerprint(project, fingerprint).first
|
||||||
|
end
|
||||||
|
|
||||||
def send_email?
|
def send_email?
|
||||||
incident_management_setting.send_email?
|
incident_management_setting.send_email?
|
||||||
end
|
end
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
= link_to_label(label, small: true)
|
= link_to_label(label, small: true)
|
||||||
|
|
||||||
= render_if_exists "projects/issues/issue_weight", issue: issue
|
= render_if_exists "projects/issues/issue_weight", issue: issue
|
||||||
|
= render "projects/issues/issue_estimate", issue: issue
|
||||||
|
|
||||||
.issuable-meta
|
.issuable-meta
|
||||||
%ul.controls
|
%ul.controls
|
||||||
|
|
7
app/views/projects/issues/_issue_estimate.html.haml
Normal file
7
app/views/projects/issues/_issue_estimate.html.haml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
- issue = local_assigns.fetch(:issue)
|
||||||
|
|
||||||
|
- if issue.time_estimate > 0
|
||||||
|
%span.issuable-estimate.d-none.d-sm-inline-block.has-tooltip{ data: { container: 'body', qa_selector: 'issuable_estimate' }, title: _('Estimate') }
|
||||||
|
|
||||||
|
= sprite_icon('timer', size: 16, css_class: 'issue-estimate-icon')
|
||||||
|
= Gitlab::TimeTrackingFormatter.output(issue.time_estimate)
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Set fingerprints and increment events count for Alert Management alerts
|
||||||
|
merge_request: 32613
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Less verbose JiraService error logs
|
||||||
|
merge_request: 32847
|
||||||
|
author:
|
||||||
|
type: other
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Focus and toggle metrics dashboard panels via keyboard
|
||||||
|
merge_request: 28603
|
||||||
|
author:
|
||||||
|
type: added
|
5
changelogs/unreleased/show_estimate_on_issues_list.yml
Normal file
5
changelogs/unreleased/show_estimate_on_issues_list.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Show estimate on issues list
|
||||||
|
merge_request: 28271
|
||||||
|
author: Lee Tickett
|
||||||
|
type: added
|
6
changelogs/unreleased/update-index-artifacts-expire.yml
Normal file
6
changelogs/unreleased/update-index-artifacts-expire.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
title: Update index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial index
|
||||||
|
for secret_detection
|
||||||
|
merge_request: 32584
|
||||||
|
author:
|
||||||
|
type: performance
|
|
@ -0,0 +1,35 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class UpdateIndexCiBuildsOnCommitIdAndArtifactsExpireatandidpartial < ActiveRecord::Migration[6.0]
|
||||||
|
include Gitlab::Database::MigrationHelpers
|
||||||
|
|
||||||
|
DOWNTIME = false
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
OLD_INDEX_NAME = 'index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial'
|
||||||
|
NEW_INDEX_NAME = 'index_ci_builds_on_commit_id_artifacts_expired_at_and_id'
|
||||||
|
|
||||||
|
OLD_CLAUSE = "type::text = 'Ci::Build'::text AND (retried = false OR retried IS NULL) AND
|
||||||
|
(name::text = ANY (ARRAY['sast'::character varying,
|
||||||
|
'dependency_scanning'::character varying,
|
||||||
|
'sast:container'::character varying,
|
||||||
|
'container_scanning'::character varying,
|
||||||
|
'dast'::character varying]::text[]))"
|
||||||
|
|
||||||
|
NEW_CLAUSE = "type::text = 'Ci::Build'::text AND (retried = false OR retried IS NULL) AND
|
||||||
|
(name::text = ANY (ARRAY['sast'::character varying,
|
||||||
|
'secret_detection'::character varying,
|
||||||
|
'dependency_scanning'::character varying,
|
||||||
|
'container_scanning'::character varying,
|
||||||
|
'dast'::character varying]::text[]))"
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_concurrent_index :ci_builds, [:commit_id, :artifacts_expire_at, :id], name: NEW_INDEX_NAME, where: NEW_CLAUSE
|
||||||
|
remove_concurrent_index_by_name :ci_builds, OLD_INDEX_NAME
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
add_concurrent_index :ci_builds, [:commit_id, :artifacts_expire_at, :id], name: OLD_INDEX_NAME, where: OLD_CLAUSE
|
||||||
|
remove_concurrent_index_by_name :ci_builds, NEW_INDEX_NAME
|
||||||
|
end
|
||||||
|
end
|
|
@ -9229,8 +9229,6 @@ CREATE INDEX index_ci_builds_on_artifacts_expire_at ON public.ci_builds USING bt
|
||||||
|
|
||||||
CREATE INDEX index_ci_builds_on_auto_canceled_by_id ON public.ci_builds USING btree (auto_canceled_by_id);
|
CREATE INDEX index_ci_builds_on_auto_canceled_by_id ON public.ci_builds USING btree (auto_canceled_by_id);
|
||||||
|
|
||||||
CREATE INDEX index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial ON public.ci_builds USING btree (commit_id, artifacts_expire_at, id) WHERE (((type)::text = 'Ci::Build'::text) AND ((retried = false) OR (retried IS NULL)) AND ((name)::text = ANY (ARRAY[('sast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('sast:container'::character varying)::text, ('container_scanning'::character varying)::text, ('dast'::character varying)::text])));
|
|
||||||
|
|
||||||
CREATE INDEX index_ci_builds_on_commit_id_and_stage_idx_and_created_at ON public.ci_builds USING btree (commit_id, stage_idx, created_at);
|
CREATE INDEX index_ci_builds_on_commit_id_and_stage_idx_and_created_at ON public.ci_builds USING btree (commit_id, stage_idx, created_at);
|
||||||
|
|
||||||
CREATE INDEX index_ci_builds_on_commit_id_and_status_and_type ON public.ci_builds USING btree (commit_id, status, type);
|
CREATE INDEX index_ci_builds_on_commit_id_and_status_and_type ON public.ci_builds USING btree (commit_id, status, type);
|
||||||
|
@ -9239,6 +9237,8 @@ CREATE INDEX index_ci_builds_on_commit_id_and_type_and_name_and_ref ON public.ci
|
||||||
|
|
||||||
CREATE INDEX index_ci_builds_on_commit_id_and_type_and_ref ON public.ci_builds USING btree (commit_id, type, ref);
|
CREATE INDEX index_ci_builds_on_commit_id_and_type_and_ref ON public.ci_builds USING btree (commit_id, type, ref);
|
||||||
|
|
||||||
|
CREATE INDEX index_ci_builds_on_commit_id_artifacts_expired_at_and_id ON public.ci_builds USING btree (commit_id, artifacts_expire_at, id) WHERE (((type)::text = 'Ci::Build'::text) AND ((retried = false) OR (retried IS NULL)) AND ((name)::text = ANY (ARRAY[('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('dependency_scanning'::character varying)::text, ('container_scanning'::character varying)::text, ('dast'::character varying)::text])));
|
||||||
|
|
||||||
CREATE INDEX index_ci_builds_on_project_id_and_id ON public.ci_builds USING btree (project_id, id);
|
CREATE INDEX index_ci_builds_on_project_id_and_id ON public.ci_builds USING btree (project_id, id);
|
||||||
|
|
||||||
CREATE INDEX index_ci_builds_on_project_id_and_name_and_ref ON public.ci_builds USING btree (project_id, name, ref) WHERE (((type)::text = 'Ci::Build'::text) AND ((status)::text = 'success'::text) AND ((retried = false) OR (retried IS NULL)));
|
CREATE INDEX index_ci_builds_on_project_id_and_name_and_ref ON public.ci_builds USING btree (project_id, name, ref) WHERE (((type)::text = 'Ci::Build'::text) AND ((status)::text = 'success'::text) AND ((retried = false) OR (retried IS NULL)));
|
||||||
|
@ -13922,5 +13922,6 @@ COPY "schema_migrations" (version) FROM STDIN;
|
||||||
20200514000340
|
20200514000340
|
||||||
20200515155620
|
20200515155620
|
||||||
20200519115908
|
20200519115908
|
||||||
|
20200519171058
|
||||||
\.
|
\.
|
||||||
|
|
||||||
|
|
|
@ -41,8 +41,11 @@ are:
|
||||||
process jobs using a [FIFO](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)) queue.
|
process jobs using a [FIFO](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)) queue.
|
||||||
|
|
||||||
A Runner that is specific only runs for the specified project(s). A shared Runner
|
A Runner that is specific only runs for the specified project(s). A shared Runner
|
||||||
can run jobs for every project that has enabled the option **Allow shared Runners**
|
can run jobs for every project that allows shared Runners. To allow shared Runners on a project:
|
||||||
under **Settings > CI/CD**.
|
|
||||||
|
1. Navigate to the project's **{settings}** **Settings > CI/CD**.
|
||||||
|
1. Expand the **Runners** section.
|
||||||
|
1. Click on **Allow shared Runners**.
|
||||||
|
|
||||||
Projects with high demand of CI activity can also benefit from using specific
|
Projects with high demand of CI activity can also benefit from using specific
|
||||||
Runners. By having dedicated Runners you are guaranteed that the Runner is not
|
Runners. By having dedicated Runners you are guaranteed that the Runner is not
|
||||||
|
@ -68,7 +71,7 @@ You can only register a shared Runner if you are an admin of the GitLab instance
|
||||||
|
|
||||||
Shared Runners are enabled by default as of GitLab 8.2, but can be disabled
|
Shared Runners are enabled by default as of GitLab 8.2, but can be disabled
|
||||||
with the **Disable shared Runners** button which is present under each project's
|
with the **Disable shared Runners** button which is present under each project's
|
||||||
**Settings > CI/CD** page. Previous versions of GitLab defaulted shared
|
**{settings}** **Settings > CI/CD** page. Previous versions of GitLab defaulted shared
|
||||||
Runners to disabled.
|
Runners to disabled.
|
||||||
|
|
||||||
## Registering a specific Runner
|
## Registering a specific Runner
|
||||||
|
@ -83,7 +86,7 @@ Registering a specific Runner can be done in two ways:
|
||||||
To create a specific Runner without having admin rights to the GitLab instance,
|
To create a specific Runner without having admin rights to the GitLab instance,
|
||||||
visit the project you want to make the Runner work for in GitLab:
|
visit the project you want to make the Runner work for in GitLab:
|
||||||
|
|
||||||
1. Go to **Settings > CI/CD** to obtain the token
|
1. Go to **{settings}** **Settings > CI/CD** to obtain the token
|
||||||
1. [Register the Runner](https://docs.gitlab.com/runner/register/)
|
1. [Register the Runner](https://docs.gitlab.com/runner/register/)
|
||||||
|
|
||||||
## Registering a group Runner
|
## Registering a group Runner
|
||||||
|
@ -91,7 +94,7 @@ visit the project you want to make the Runner work for in GitLab:
|
||||||
Creating a group Runner requires Owner permissions for the group. To create a
|
Creating a group Runner requires Owner permissions for the group. To create a
|
||||||
group Runner visit the group you want to make the Runner work for in GitLab:
|
group Runner visit the group you want to make the Runner work for in GitLab:
|
||||||
|
|
||||||
1. Go to **Settings > CI/CD** to obtain the token
|
1. Go to **{settings}** **Settings > CI/CD** to obtain the token
|
||||||
1. [Register the Runner](https://docs.gitlab.com/runner/register/)
|
1. [Register the Runner](https://docs.gitlab.com/runner/register/)
|
||||||
|
|
||||||
### Making an existing shared Runner specific
|
### Making an existing shared Runner specific
|
||||||
|
@ -100,7 +103,7 @@ If you are an admin on your GitLab instance, you can turn any shared Runner into
|
||||||
a specific one, but not the other way around. Keep in mind that this is a one
|
a specific one, but not the other way around. Keep in mind that this is a one
|
||||||
way transition.
|
way transition.
|
||||||
|
|
||||||
1. Go to the Runners in the **Admin Area > Overview > Runners** (`/admin/runners`)
|
1. Go to the Runners in the **{admin}** **Admin Area > Overview > Runners** (`/admin/runners`)
|
||||||
and find your Runner
|
and find your Runner
|
||||||
1. Enable any projects under **Restrict projects for this Runner** to be used
|
1. Enable any projects under **Restrict projects for this Runner** to be used
|
||||||
with the Runner
|
with the Runner
|
||||||
|
@ -116,7 +119,7 @@ can be changed afterwards under each Runner's settings.
|
||||||
|
|
||||||
To lock/unlock a Runner:
|
To lock/unlock a Runner:
|
||||||
|
|
||||||
1. Visit your project's **Settings > CI/CD**
|
1. Visit your project's **{settings}** **Settings > CI/CD**
|
||||||
1. Find the Runner you wish to lock/unlock and make sure it's enabled
|
1. Find the Runner you wish to lock/unlock and make sure it's enabled
|
||||||
1. Click the pencil button
|
1. Click the pencil button
|
||||||
1. Check the **Lock to current projects** option
|
1. Check the **Lock to current projects** option
|
||||||
|
@ -130,7 +133,7 @@ you can enable the Runner also on any other project where you have Owner permiss
|
||||||
|
|
||||||
To enable/disable a Runner in your project:
|
To enable/disable a Runner in your project:
|
||||||
|
|
||||||
1. Visit your project's **Settings > CI/CD**
|
1. Visit your project's **{settings}** **Settings > CI/CD**
|
||||||
1. Find the Runner you wish to enable/disable
|
1. Find the Runner you wish to enable/disable
|
||||||
1. Click **Enable for this project** or **Disable for this project**
|
1. Click **Enable for this project** or **Disable for this project**
|
||||||
|
|
||||||
|
@ -164,7 +167,7 @@ Whenever a Runner is protected, the Runner picks only jobs created on
|
||||||
|
|
||||||
To protect/unprotect Runners:
|
To protect/unprotect Runners:
|
||||||
|
|
||||||
1. Visit your project's **Settings > CI/CD**
|
1. Visit your project's **{settings}** **Settings > CI/CD**
|
||||||
1. Find a Runner you want to protect/unprotect and make sure it's enabled
|
1. Find a Runner you want to protect/unprotect and make sure it's enabled
|
||||||
1. Click the pencil button besides the Runner name
|
1. Click the pencil button besides the Runner name
|
||||||
1. Check the **Protected** option
|
1. Check the **Protected** option
|
||||||
|
@ -254,7 +257,7 @@ Runner settings.
|
||||||
|
|
||||||
To make a Runner pick untagged jobs:
|
To make a Runner pick untagged jobs:
|
||||||
|
|
||||||
1. Visit your project's **Settings > CI/CD > Runners**.
|
1. Visit your project's **{settings}** **Settings > CI/CD > Runners**.
|
||||||
1. Find the Runner you want to pick untagged jobs and make sure it's enabled.
|
1. Find the Runner you want to pick untagged jobs and make sure it's enabled.
|
||||||
1. Click the pencil button.
|
1. Click the pencil button.
|
||||||
1. Check the **Run untagged jobs** option.
|
1. Check the **Run untagged jobs** option.
|
||||||
|
@ -372,7 +375,7 @@ attacker.
|
||||||
|
|
||||||
To reset the token:
|
To reset the token:
|
||||||
|
|
||||||
1. Go to **Settings > CI/CD** for a specified Project.
|
1. Go to **{settings}** **Settings > CI/CD** for a specified Project.
|
||||||
1. Expand the **General pipelines settings** section.
|
1. Expand the **General pipelines settings** section.
|
||||||
1. Find the **Runner token** form field and click the **Reveal value** button.
|
1. Find the **Runner token** form field and click the **Reveal value** button.
|
||||||
1. Delete the value and save the form.
|
1. Delete the value and save the form.
|
||||||
|
@ -402,7 +405,7 @@ different places.
|
||||||
To view the IP address of a shared Runner you must have admin access to
|
To view the IP address of a shared Runner you must have admin access to
|
||||||
the GitLab instance. To determine this:
|
the GitLab instance. To determine this:
|
||||||
|
|
||||||
1. Visit **Admin Area > Overview > Runners**
|
1. Visit **{admin}** **Admin Area > Overview > Runners**
|
||||||
1. Look for the Runner in the table and you should see a column for "IP Address"
|
1. Look for the Runner in the table and you should see a column for "IP Address"
|
||||||
|
|
||||||
![shared Runner IP address](img/shared_runner_ip_address.png)
|
![shared Runner IP address](img/shared_runner_ip_address.png)
|
||||||
|
@ -411,7 +414,7 @@ the GitLab instance. To determine this:
|
||||||
|
|
||||||
You can find the IP address of a Runner for a specific project by:
|
You can find the IP address of a Runner for a specific project by:
|
||||||
|
|
||||||
1. Visit your project's **Settings > CI/CD**
|
1. Visit your project's **{settings}** **Settings > CI/CD**
|
||||||
1. Find the Runner and click on it's ID which links you to the details page
|
1. Find the Runner and click on it's ID which links you to the details page
|
||||||
1. On the details page you should see a row for "IP Address"
|
1. On the details page you should see a row for "IP Address"
|
||||||
|
|
||||||
|
|
|
@ -3880,11 +3880,11 @@ if using Git 2.10 or newer.
|
||||||
|
|
||||||
## Processing Git pushes
|
## Processing Git pushes
|
||||||
|
|
||||||
GitLab will create at most 4 branch and tags pipelines when
|
GitLab will create at most 4 branch and tag pipelines when
|
||||||
doing pushing multiple changes in single `git push` invocation.
|
pushing multiple changes in single `git push` invocation.
|
||||||
|
|
||||||
This limitation does not affect any of the updated Merge Request pipelines,
|
This limitation does not affect any of the updated Merge Request pipelines.
|
||||||
all updated Merge Requests will have a pipeline created when using
|
All updated Merge Requests will have a pipeline created when using
|
||||||
[pipelines for merge requests](../merge_request_pipelines/index.md).
|
[pipelines for merge requests](../merge_request_pipelines/index.md).
|
||||||
|
|
||||||
## Deprecated parameters
|
## Deprecated parameters
|
||||||
|
|
|
@ -28,7 +28,7 @@ To set up the generic alerts integration:
|
||||||
|
|
||||||
## Customizing the payload
|
## Customizing the payload
|
||||||
|
|
||||||
You can customize the payload by sending the following parameters. All fields are optional:
|
You can customize the payload by sending the following parameters. All fields other than `title` are optional:
|
||||||
|
|
||||||
| Property | Type | Description |
|
| Property | Type | Description |
|
||||||
| -------- | ---- | ----------- |
|
| -------- | ---- | ----------- |
|
||||||
|
@ -39,6 +39,7 @@ You can customize the payload by sending the following parameters. All fields ar
|
||||||
| `monitoring_tool` | String | The name of the associated monitoring tool. |
|
| `monitoring_tool` | String | The name of the associated monitoring tool. |
|
||||||
| `hosts` | String or Array | One or more hosts, as to where this incident occurred. |
|
| `hosts` | String or Array | One or more hosts, as to where this incident occurred. |
|
||||||
| `severity` | String | The severity of the alert. Must be one of `critical`, `high`, `medium`, `low`, `info`, `unknown`. Default is `critical`. |
|
| `severity` | String | The severity of the alert. Must be one of `critical`, `high`, `medium`, `low`, `info`, `unknown`. Default is `critical`. |
|
||||||
|
| `fingerprint` | String or Array | The unique identifier of the alert. This can be used to group occurrences of the same alert. |
|
||||||
|
|
||||||
TIP: **Payload size:**
|
TIP: **Payload size:**
|
||||||
Ensure your requests are smaller than the [payload application limits](../../../administration/instance_limits.md#generic-alert-json-payloads).
|
Ensure your requests are smaller than the [payload application limits](../../../administration/instance_limits.md#generic-alert-json-payloads).
|
||||||
|
@ -65,5 +66,7 @@ Example payload:
|
||||||
"service": "service affected",
|
"service": "service affected",
|
||||||
"monitoring_tool": "value",
|
"monitoring_tool": "value",
|
||||||
"hosts": "value",
|
"hosts": "value",
|
||||||
|
"severity": "high",
|
||||||
|
"fingerprint": "d19381d4e8ebca87b55cda6e8eee7385"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -20,7 +20,8 @@ module Gitlab
|
||||||
hosts: Array(annotations[:hosts]),
|
hosts: Array(annotations[:hosts]),
|
||||||
payload: payload,
|
payload: payload,
|
||||||
started_at: parsed_payload['startsAt'],
|
started_at: parsed_payload['startsAt'],
|
||||||
severity: annotations[:severity]
|
severity: annotations[:severity],
|
||||||
|
fingerprint: annotations[:fingerprint]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
27
lib/gitlab/alert_management/fingerprint.rb
Normal file
27
lib/gitlab/alert_management/fingerprint.rb
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module AlertManagement
|
||||||
|
class Fingerprint
|
||||||
|
def self.generate(data)
|
||||||
|
new.generate(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate(data)
|
||||||
|
return unless data.present?
|
||||||
|
|
||||||
|
if data.is_a?(Array)
|
||||||
|
data = flatten_array(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
Digest::SHA1.hexdigest(data.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def flatten_array(array)
|
||||||
|
array.flatten.map!(&:to_s).join
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -106,7 +106,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def gitlab_fingerprint
|
def gitlab_fingerprint
|
||||||
Digest::SHA1.hexdigest(plain_gitlab_fingerprint)
|
Gitlab::AlertManagement::Fingerprint.generate(plain_gitlab_fingerprint)
|
||||||
end
|
end
|
||||||
|
|
||||||
def valid?
|
def valid?
|
||||||
|
|
|
@ -35,6 +35,10 @@ module Gitlab
|
||||||
payload[:severity].presence || DEFAULT_SEVERITY
|
payload[:severity].presence || DEFAULT_SEVERITY
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fingerprint
|
||||||
|
Gitlab::AlertManagement::Fingerprint.generate(payload[:fingerprint])
|
||||||
|
end
|
||||||
|
|
||||||
def annotations
|
def annotations
|
||||||
primary_params
|
primary_params
|
||||||
.reverse_merge(flatten_secondary_params)
|
.reverse_merge(flatten_secondary_params)
|
||||||
|
@ -49,7 +53,8 @@ module Gitlab
|
||||||
'monitoring_tool' => payload[:monitoring_tool],
|
'monitoring_tool' => payload[:monitoring_tool],
|
||||||
'service' => payload[:service],
|
'service' => payload[:service],
|
||||||
'hosts' => hosts.presence,
|
'hosts' => hosts.presence,
|
||||||
'severity' => severity
|
'severity' => severity,
|
||||||
|
'fingerprint' => fingerprint
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,7 @@ module Gitlab
|
||||||
def request(*args)
|
def request(*args)
|
||||||
result = make_request(*args)
|
result = make_request(*args)
|
||||||
|
|
||||||
unless result.response.is_a?(Net::HTTPSuccess)
|
raise JIRA::HTTPError.new(result.response) unless result.response.is_a?(Net::HTTPSuccess)
|
||||||
Gitlab::ErrorTracking.track_and_raise_exception(
|
|
||||||
JIRA::HTTPError.new(result.response),
|
|
||||||
response: result.body
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
|
@ -8799,6 +8799,9 @@ msgstr ""
|
||||||
msgid "Errors:"
|
msgid "Errors:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Estimate"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Estimated"
|
msgid "Estimated"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ Disallow: /snippets/*/raw
|
||||||
User-Agent: *
|
User-Agent: *
|
||||||
Disallow: /*/*.git
|
Disallow: /*/*.git
|
||||||
Disallow: /*/*/fork/new
|
Disallow: /*/*/fork/new
|
||||||
|
Disallow: /*/-/archive/
|
||||||
Disallow: /*/*/repository/archive*
|
Disallow: /*/*/repository/archive*
|
||||||
Disallow: /*/*/activity
|
Disallow: /*/*/activity
|
||||||
Disallow: /*/*/new
|
Disallow: /*/*/new
|
||||||
|
|
|
@ -126,6 +126,10 @@ describe('Dashboard Panel', () => {
|
||||||
expect(wrapper.find(MonitorEmptyChart).exists()).toBe(true);
|
expect(wrapper.find(MonitorEmptyChart).exists()).toBe(true);
|
||||||
expect(wrapper.find(MonitorEmptyChart).isVueInstance()).toBe(true);
|
expect(wrapper.find(MonitorEmptyChart).isVueInstance()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not contain a tabindex attribute', () => {
|
||||||
|
expect(wrapper.find(MonitorEmptyChart).contains('[tabindex]')).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When graphData is null', () => {
|
describe('When graphData is null', () => {
|
||||||
|
|
|
@ -8,6 +8,7 @@ describe('Graph group component', () => {
|
||||||
const findGroup = () => wrapper.find({ ref: 'graph-group' });
|
const findGroup = () => wrapper.find({ ref: 'graph-group' });
|
||||||
const findContent = () => wrapper.find({ ref: 'graph-group-content' });
|
const findContent = () => wrapper.find({ ref: 'graph-group-content' });
|
||||||
const findCaretIcon = () => wrapper.find(Icon);
|
const findCaretIcon = () => wrapper.find(Icon);
|
||||||
|
const findToggleButton = () => wrapper.find('[data-testid="group-toggle-button"]');
|
||||||
|
|
||||||
const createComponent = propsData => {
|
const createComponent = propsData => {
|
||||||
wrapper = shallowMount(GraphGroup, {
|
wrapper = shallowMount(GraphGroup, {
|
||||||
|
@ -41,6 +42,16 @@ describe('Graph group component', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should contain a tabindex', () => {
|
||||||
|
expect(findGroup().contains('[tabindex]')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a tab index for the collapse button', () => {
|
||||||
|
const groupToggle = findToggleButton();
|
||||||
|
|
||||||
|
expect(groupToggle.contains('[tabindex]')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('should show the open the group when collapseGroup is set to true', () => {
|
it('should show the open the group when collapseGroup is set to true', () => {
|
||||||
wrapper.setProps({
|
wrapper.setProps({
|
||||||
collapseGroup: true,
|
collapseGroup: true,
|
||||||
|
@ -69,6 +80,15 @@ describe('Graph group component', () => {
|
||||||
|
|
||||||
expect(wrapper.vm.caretIcon).toBe('angle-down');
|
expect(wrapper.vm.caretIcon).toBe('angle-down');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should call collapse the graph group content when enter is pressed on the caret icon', () => {
|
||||||
|
const graphGroupContent = findContent();
|
||||||
|
const button = findToggleButton();
|
||||||
|
|
||||||
|
button.trigger('keyup.enter');
|
||||||
|
|
||||||
|
expect(graphGroupContent.isVisible()).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When groups can not be collapsed', () => {
|
describe('When groups can not be collapsed', () => {
|
||||||
|
|
|
@ -1,166 +0,0 @@
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
|
||||||
import { TEST_HOST } from 'spec/test_constants';
|
|
||||||
import axios from '~/lib/utils/axios_utils';
|
|
||||||
import testAction from './vuex_action_helper';
|
|
||||||
|
|
||||||
describe('VueX test helper (testAction)', () => {
|
|
||||||
let originalExpect;
|
|
||||||
let assertion;
|
|
||||||
let mock;
|
|
||||||
const noop = () => {};
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
mock = new MockAdapter(axios);
|
|
||||||
/*
|
|
||||||
In order to test the helper properly, we need to overwrite the jasmine `expect` helper.
|
|
||||||
We test that the testAction helper properly passes the dispatched actions/committed mutations
|
|
||||||
to the jasmine helper.
|
|
||||||
*/
|
|
||||||
originalExpect = expect;
|
|
||||||
assertion = null;
|
|
||||||
global.expect = actual => ({
|
|
||||||
toEqual: () => {
|
|
||||||
originalExpect(actual).toEqual(assertion);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
mock.restore();
|
|
||||||
global.expect = originalExpect;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should properly pass on state and payload', () => {
|
|
||||||
const exampleState = { FOO: 12, BAR: 3 };
|
|
||||||
const examplePayload = { BAZ: 73, BIZ: 55 };
|
|
||||||
|
|
||||||
const action = ({ state }, payload) => {
|
|
||||||
originalExpect(state).toEqual(exampleState);
|
|
||||||
originalExpect(payload).toEqual(examplePayload);
|
|
||||||
};
|
|
||||||
|
|
||||||
assertion = { mutations: [], actions: [] };
|
|
||||||
|
|
||||||
testAction(action, examplePayload, exampleState);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('should work with synchronous actions', () => {
|
|
||||||
it('committing mutation', () => {
|
|
||||||
const action = ({ commit }) => {
|
|
||||||
commit('MUTATION');
|
|
||||||
};
|
|
||||||
|
|
||||||
assertion = { mutations: [{ type: 'MUTATION' }], actions: [] };
|
|
||||||
|
|
||||||
testAction(action, null, {}, assertion.mutations, assertion.actions, noop);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('dispatching action', () => {
|
|
||||||
const action = ({ dispatch }) => {
|
|
||||||
dispatch('ACTION');
|
|
||||||
};
|
|
||||||
|
|
||||||
assertion = { actions: [{ type: 'ACTION' }], mutations: [] };
|
|
||||||
|
|
||||||
testAction(action, null, {}, assertion.mutations, assertion.actions, noop);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('work with jasmine done once finished', done => {
|
|
||||||
assertion = { mutations: [], actions: [] };
|
|
||||||
|
|
||||||
testAction(noop, null, {}, assertion.mutations, assertion.actions, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('provide promise interface', done => {
|
|
||||||
assertion = { mutations: [], actions: [] };
|
|
||||||
|
|
||||||
testAction(noop, null, {}, assertion.mutations, assertion.actions)
|
|
||||||
.then(done)
|
|
||||||
.catch(done.fail);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('should work with promise based actions (fetch action)', () => {
|
|
||||||
let lastError;
|
|
||||||
const data = { FOO: 'BAR' };
|
|
||||||
|
|
||||||
const promiseAction = ({ commit, dispatch }) => {
|
|
||||||
dispatch('ACTION');
|
|
||||||
|
|
||||||
return axios
|
|
||||||
.get(TEST_HOST)
|
|
||||||
.catch(error => {
|
|
||||||
commit('ERROR');
|
|
||||||
lastError = error;
|
|
||||||
throw error;
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
commit('SUCCESS');
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
lastError = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('work with jasmine done once finished', done => {
|
|
||||||
mock.onGet(TEST_HOST).replyOnce(200, 42);
|
|
||||||
|
|
||||||
assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
|
|
||||||
|
|
||||||
testAction(promiseAction, null, {}, assertion.mutations, assertion.actions, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('return original data of successful promise while checking actions/mutations', done => {
|
|
||||||
mock.onGet(TEST_HOST).replyOnce(200, 42);
|
|
||||||
|
|
||||||
assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
|
|
||||||
|
|
||||||
testAction(promiseAction, null, {}, assertion.mutations, assertion.actions)
|
|
||||||
.then(res => {
|
|
||||||
originalExpect(res).toEqual(data);
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
.catch(done.fail);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('return original error of rejected promise while checking actions/mutations', done => {
|
|
||||||
mock.onGet(TEST_HOST).replyOnce(500, '');
|
|
||||||
|
|
||||||
assertion = { mutations: [{ type: 'ERROR' }], actions: [{ type: 'ACTION' }] };
|
|
||||||
|
|
||||||
testAction(promiseAction, null, {}, assertion.mutations, assertion.actions)
|
|
||||||
.then(done.fail)
|
|
||||||
.catch(error => {
|
|
||||||
originalExpect(error).toBe(lastError);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work with async actions not returning promises', done => {
|
|
||||||
const data = { FOO: 'BAR' };
|
|
||||||
|
|
||||||
const promiseAction = ({ commit, dispatch }) => {
|
|
||||||
dispatch('ACTION');
|
|
||||||
|
|
||||||
axios
|
|
||||||
.get(TEST_HOST)
|
|
||||||
.then(() => {
|
|
||||||
commit('SUCCESS');
|
|
||||||
return data;
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
commit('ERROR');
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
mock.onGet(TEST_HOST).replyOnce(200, 42);
|
|
||||||
|
|
||||||
assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
|
|
||||||
|
|
||||||
testAction(promiseAction, null, {}, assertion.mutations, assertion.actions, done);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -32,7 +32,8 @@ describe Gitlab::AlertManagement::AlertParams do
|
||||||
severity: 'critical',
|
severity: 'critical',
|
||||||
hosts: ['gitlab.com'],
|
hosts: ['gitlab.com'],
|
||||||
payload: payload,
|
payload: payload,
|
||||||
started_at: started_at
|
started_at: started_at,
|
||||||
|
fingerprint: nil
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
48
spec/lib/gitlab/alert_management/fingerprint_spec.rb
Normal file
48
spec/lib/gitlab/alert_management/fingerprint_spec.rb
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Gitlab::AlertManagement::Fingerprint do
|
||||||
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
|
let_it_be(:alert) { create(:alert_management_alert) }
|
||||||
|
|
||||||
|
describe '.generate' do
|
||||||
|
subject { described_class.generate(data) }
|
||||||
|
|
||||||
|
context 'when data is an array' do
|
||||||
|
let(:data) { [1, 'fingerprint', 'given'] }
|
||||||
|
|
||||||
|
it 'flattens the array' do
|
||||||
|
expect_next_instance_of(described_class) do |obj|
|
||||||
|
expect(obj).to receive(:flatten_array)
|
||||||
|
end
|
||||||
|
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the hashed fingerprint' do
|
||||||
|
expected_fingerprint = Digest::SHA1.hexdigest(data.flatten.map!(&:to_s).join)
|
||||||
|
expect(subject).to eq(expected_fingerprint)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when data is a non-array type' do
|
||||||
|
where(:data) do
|
||||||
|
[
|
||||||
|
111,
|
||||||
|
'fingerprint',
|
||||||
|
:fingerprint,
|
||||||
|
true,
|
||||||
|
{ test: true }
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
it 'performs like a hashed fingerprint' do
|
||||||
|
expect(subject).to eq(Digest::SHA1.hexdigest(data.to_s))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'fast_spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe Gitlab::Alerting::NotificationPayloadParser do
|
describe Gitlab::Alerting::NotificationPayloadParser do
|
||||||
describe '.call' do
|
describe '.call' do
|
||||||
|
@ -89,6 +89,39 @@ describe Gitlab::Alerting::NotificationPayloadParser do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with fingerprint' do
|
||||||
|
before do
|
||||||
|
payload[:fingerprint] = data
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples 'fingerprint generation' do
|
||||||
|
it 'generates the fingerprint correctly' do
|
||||||
|
expect(result).to eq(Gitlab::AlertManagement::Fingerprint.generate(data))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with blank fingerprint' do
|
||||||
|
it_behaves_like 'fingerprint generation' do
|
||||||
|
let(:data) { ' ' }
|
||||||
|
let(:result) { subject.dig('annotations', 'fingerprint') }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with fingerprint given' do
|
||||||
|
it_behaves_like 'fingerprint generation' do
|
||||||
|
let(:data) { 'fingerprint' }
|
||||||
|
let(:result) { subject.dig('annotations', 'fingerprint') }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with array fingerprint given' do
|
||||||
|
it_behaves_like 'fingerprint generation' do
|
||||||
|
let(:data) { [1, 'fingerprint', 'given'] }
|
||||||
|
let(:result) { subject.dig('annotations', 'fingerprint') }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when payload attributes have blank lines' do
|
context 'when payload attributes have blank lines' do
|
||||||
let(:payload) do
|
let(:payload) do
|
||||||
{
|
{
|
||||||
|
|
|
@ -317,4 +317,14 @@ describe AlertManagement::Alert do
|
||||||
expect { subject }.to change { alert.reload.ended_at }.to nil
|
expect { subject }.to change { alert.reload.ended_at }.to nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#register_new_event!' do
|
||||||
|
subject { alert.register_new_event! }
|
||||||
|
|
||||||
|
let(:alert) { create(:alert_management_alert) }
|
||||||
|
|
||||||
|
it 'increments the events count by 1' do
|
||||||
|
expect { subject }.to change { alert.events}.by(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -688,22 +688,18 @@ describe JiraService do
|
||||||
context 'when the test fails' do
|
context 'when the test fails' do
|
||||||
it 'returns result with the error' do
|
it 'returns result with the error' do
|
||||||
test_url = 'http://jira.example.com/rest/api/2/serverInfo'
|
test_url = 'http://jira.example.com/rest/api/2/serverInfo'
|
||||||
|
error_message = 'Some specific failure.'
|
||||||
|
|
||||||
WebMock.stub_request(:get, test_url).with(basic_auth: [username, password])
|
WebMock.stub_request(:get, test_url).with(basic_auth: [username, password])
|
||||||
.to_raise(JIRA::HTTPError.new(double(message: 'Some specific failure.')))
|
.to_raise(JIRA::HTTPError.new(double(message: error_message)))
|
||||||
|
|
||||||
expect(jira_service).to receive(:log_error).with(
|
expect(jira_service).to receive(:log_error).with(
|
||||||
"Error sending message",
|
'Error sending message',
|
||||||
hash_including(
|
client_url: 'http://jira.example.com',
|
||||||
client_url: url,
|
error: error_message
|
||||||
error: hash_including(
|
|
||||||
exception_class: 'JIRA::HTTPError',
|
|
||||||
exception_message: 'Some specific failure.'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(jira_service.test(nil)).to eq(success: false, result: 'Some specific failure.')
|
expect(jira_service.test(nil)).to eq(success: false, result: error_message)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -73,6 +73,7 @@ describe Projects::Alerting::NotifyService do
|
||||||
describe '#execute' do
|
describe '#execute' do
|
||||||
let(:token) { 'invalid-token' }
|
let(:token) { 'invalid-token' }
|
||||||
let(:starts_at) { Time.current.change(usec: 0) }
|
let(:starts_at) { Time.current.change(usec: 0) }
|
||||||
|
let(:fingerprint) { 'testing' }
|
||||||
let(:service) { described_class.new(project, nil, payload) }
|
let(:service) { described_class.new(project, nil, payload) }
|
||||||
let(:payload_raw) do
|
let(:payload_raw) do
|
||||||
{
|
{
|
||||||
|
@ -82,7 +83,8 @@ describe Projects::Alerting::NotifyService do
|
||||||
monitoring_tool: 'GitLab RSpec',
|
monitoring_tool: 'GitLab RSpec',
|
||||||
service: 'GitLab Test Suite',
|
service: 'GitLab Test Suite',
|
||||||
description: 'Very detailed description',
|
description: 'Very detailed description',
|
||||||
hosts: ['1.1.1.1', '2.2.2.2']
|
hosts: ['1.1.1.1', '2.2.2.2'],
|
||||||
|
fingerprint: fingerprint
|
||||||
}.with_indifferent_access
|
}.with_indifferent_access
|
||||||
end
|
end
|
||||||
let(:payload) { ActionController::Parameters.new(payload_raw).permit! }
|
let(:payload) { ActionController::Parameters.new(payload_raw).permit! }
|
||||||
|
@ -131,11 +133,23 @@ describe Projects::Alerting::NotifyService do
|
||||||
description: payload_raw.fetch(:description),
|
description: payload_raw.fetch(:description),
|
||||||
monitoring_tool: payload_raw.fetch(:monitoring_tool),
|
monitoring_tool: payload_raw.fetch(:monitoring_tool),
|
||||||
service: payload_raw.fetch(:service),
|
service: payload_raw.fetch(:service),
|
||||||
fingerprint: nil,
|
fingerprint: Digest::SHA1.hexdigest(fingerprint),
|
||||||
ended_at: nil
|
ended_at: nil
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'existing alert with same fingerprint' do
|
||||||
|
let!(:existing_alert) { create(:alert_management_alert, project: project, fingerprint: Digest::SHA1.hexdigest(fingerprint)) }
|
||||||
|
|
||||||
|
it 'does not create AlertManagement::Alert' do
|
||||||
|
expect { subject }.not_to change(AlertManagement::Alert, :count)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'increments the existing alert count' do
|
||||||
|
expect { subject }.to change { existing_alert.reload.events }.from(1).to(2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with a minimal payload' do
|
context 'with a minimal payload' do
|
||||||
let(:payload_raw) do
|
let(:payload_raw) do
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue