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
|
||||
ref="graphTitle"
|
||||
class="prometheus-graph-title gl-font-lg font-weight-bold text-truncate append-right-8"
|
||||
tabindex="0"
|
||||
>
|
||||
{{ title }}
|
||||
</h5>
|
||||
|
|
|
@ -52,10 +52,17 @@ export default {
|
|||
</script>
|
||||
|
||||
<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">
|
||||
<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" />
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -754,7 +754,8 @@
|
|||
margin-right: 10px;
|
||||
min-width: 0;
|
||||
|
||||
.issue-weight-icon {
|
||||
.issue-weight-icon,
|
||||
.issue-estimate-icon {
|
||||
vertical-align: sub;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -135,6 +135,10 @@ module AlertManagement
|
|||
monitoring_tool == Gitlab::AlertManagement::AlertParams::MONITORING_TOOLS[:prometheus]
|
||||
end
|
||||
|
||||
def register_new_event!
|
||||
increment!(:events, 1)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hosts_length
|
||||
|
|
|
@ -435,15 +435,7 @@ class JiraService < IssueTrackerService
|
|||
yield
|
||||
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, URI::InvalidURIError, JIRA::HTTPError, OpenSSL::SSL::SSLError => error
|
||||
@error = error
|
||||
log_error(
|
||||
"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)
|
||||
}
|
||||
)
|
||||
log_error("Error sending message", client_url: client_url, error: @error.message)
|
||||
nil
|
||||
end
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ module Projects
|
|||
return forbidden unless alerts_service_activated?
|
||||
return unauthorized unless valid_token?(token)
|
||||
|
||||
alert = create_alert
|
||||
alert = process_alert
|
||||
return bad_request unless alert.persisted?
|
||||
|
||||
process_incident_issues(alert) if process_issues?
|
||||
|
@ -26,13 +26,29 @@ module Projects
|
|||
delegate :alerts_service, :alerts_service_activated?, to: :project
|
||||
|
||||
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
|
||||
|
||||
def create_alert
|
||||
AlertManagement::Alert.create(am_alert_params)
|
||||
end
|
||||
|
||||
def find_alert_by_fingerprint(fingerprint)
|
||||
return unless fingerprint
|
||||
|
||||
AlertManagement::Alert.for_fingerprint(project, fingerprint).first
|
||||
end
|
||||
|
||||
def send_email?
|
||||
incident_management_setting.send_email?
|
||||
end
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
= link_to_label(label, small: true)
|
||||
|
||||
= render_if_exists "projects/issues/issue_weight", issue: issue
|
||||
= render "projects/issues/issue_estimate", issue: issue
|
||||
|
||||
.issuable-meta
|
||||
%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_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_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_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_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
|
||||
20200515155620
|
||||
20200519115908
|
||||
20200519171058
|
||||
\.
|
||||
|
||||
|
|
|
@ -41,8 +41,11 @@ are:
|
|||
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
|
||||
can run jobs for every project that has enabled the option **Allow shared Runners**
|
||||
under **Settings > CI/CD**.
|
||||
can run jobs for every project that allows shared Runners. To allow shared Runners on a project:
|
||||
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
## 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,
|
||||
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/)
|
||||
|
||||
## 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
|
||||
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/)
|
||||
|
||||
### 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
|
||||
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
|
||||
1. Enable any projects under **Restrict projects for this Runner** to be used
|
||||
with the Runner
|
||||
|
@ -116,7 +119,7 @@ can be changed afterwards under each Runner's settings.
|
|||
|
||||
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. Click the pencil button
|
||||
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:
|
||||
|
||||
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. 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:
|
||||
|
||||
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. Click the pencil button besides the Runner name
|
||||
1. Check the **Protected** option
|
||||
|
@ -254,7 +257,7 @@ Runner settings.
|
|||
|
||||
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. Click the pencil button.
|
||||
1. Check the **Run untagged jobs** option.
|
||||
|
@ -372,7 +375,7 @@ attacker.
|
|||
|
||||
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. Find the **Runner token** form field and click the **Reveal value** button.
|
||||
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
|
||||
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"
|
||||
|
||||
![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:
|
||||
|
||||
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. 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
|
||||
|
||||
GitLab will create at most 4 branch and tags pipelines when
|
||||
doing pushing multiple changes in single `git push` invocation.
|
||||
GitLab will create at most 4 branch and tag pipelines when
|
||||
pushing multiple changes in single `git push` invocation.
|
||||
|
||||
This limitation does not affect any of the updated Merge Request pipelines,
|
||||
all updated Merge Requests will have a pipeline created when using
|
||||
This limitation does not affect any of the updated Merge Request pipelines.
|
||||
All updated Merge Requests will have a pipeline created when using
|
||||
[pipelines for merge requests](../merge_request_pipelines/index.md).
|
||||
|
||||
## Deprecated parameters
|
||||
|
|
|
@ -28,7 +28,7 @@ To set up the generic alerts integration:
|
|||
|
||||
## 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 |
|
||||
| -------- | ---- | ----------- |
|
||||
|
@ -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. |
|
||||
| `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`. |
|
||||
| `fingerprint` | String or Array | The unique identifier of the alert. This can be used to group occurrences of the same alert. |
|
||||
|
||||
TIP: **Payload size:**
|
||||
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",
|
||||
"monitoring_tool": "value",
|
||||
"hosts": "value",
|
||||
"severity": "high",
|
||||
"fingerprint": "d19381d4e8ebca87b55cda6e8eee7385"
|
||||
}
|
||||
```
|
||||
|
|
|
@ -20,7 +20,8 @@ module Gitlab
|
|||
hosts: Array(annotations[:hosts]),
|
||||
payload: payload,
|
||||
started_at: parsed_payload['startsAt'],
|
||||
severity: annotations[:severity]
|
||||
severity: annotations[:severity],
|
||||
fingerprint: annotations[:fingerprint]
|
||||
}
|
||||
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
|
||||
|
||||
def gitlab_fingerprint
|
||||
Digest::SHA1.hexdigest(plain_gitlab_fingerprint)
|
||||
Gitlab::AlertManagement::Fingerprint.generate(plain_gitlab_fingerprint)
|
||||
end
|
||||
|
||||
def valid?
|
||||
|
|
|
@ -35,6 +35,10 @@ module Gitlab
|
|||
payload[:severity].presence || DEFAULT_SEVERITY
|
||||
end
|
||||
|
||||
def fingerprint
|
||||
Gitlab::AlertManagement::Fingerprint.generate(payload[:fingerprint])
|
||||
end
|
||||
|
||||
def annotations
|
||||
primary_params
|
||||
.reverse_merge(flatten_secondary_params)
|
||||
|
@ -49,7 +53,8 @@ module Gitlab
|
|||
'monitoring_tool' => payload[:monitoring_tool],
|
||||
'service' => payload[:service],
|
||||
'hosts' => hosts.presence,
|
||||
'severity' => severity
|
||||
'severity' => severity,
|
||||
'fingerprint' => fingerprint
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -12,12 +12,7 @@ module Gitlab
|
|||
def request(*args)
|
||||
result = make_request(*args)
|
||||
|
||||
unless result.response.is_a?(Net::HTTPSuccess)
|
||||
Gitlab::ErrorTracking.track_and_raise_exception(
|
||||
JIRA::HTTPError.new(result.response),
|
||||
response: result.body
|
||||
)
|
||||
end
|
||||
raise JIRA::HTTPError.new(result.response) unless result.response.is_a?(Net::HTTPSuccess)
|
||||
|
||||
result
|
||||
end
|
||||
|
|
|
@ -8799,6 +8799,9 @@ msgstr ""
|
|||
msgid "Errors:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Estimate"
|
||||
msgstr ""
|
||||
|
||||
msgid "Estimated"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ Disallow: /snippets/*/raw
|
|||
User-Agent: *
|
||||
Disallow: /*/*.git
|
||||
Disallow: /*/*/fork/new
|
||||
Disallow: /*/-/archive/
|
||||
Disallow: /*/*/repository/archive*
|
||||
Disallow: /*/*/activity
|
||||
Disallow: /*/*/new
|
||||
|
|
|
@ -126,6 +126,10 @@ describe('Dashboard Panel', () => {
|
|||
expect(wrapper.find(MonitorEmptyChart).exists()).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', () => {
|
||||
|
|
|
@ -8,6 +8,7 @@ describe('Graph group component', () => {
|
|||
const findGroup = () => wrapper.find({ ref: 'graph-group' });
|
||||
const findContent = () => wrapper.find({ ref: 'graph-group-content' });
|
||||
const findCaretIcon = () => wrapper.find(Icon);
|
||||
const findToggleButton = () => wrapper.find('[data-testid="group-toggle-button"]');
|
||||
|
||||
const createComponent = propsData => {
|
||||
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', () => {
|
||||
wrapper.setProps({
|
||||
collapseGroup: true,
|
||||
|
@ -69,6 +80,15 @@ describe('Graph group component', () => {
|
|||
|
||||
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', () => {
|
||||
|
|
|
@ -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',
|
||||
hosts: ['gitlab.com'],
|
||||
payload: payload,
|
||||
started_at: started_at
|
||||
started_at: started_at,
|
||||
fingerprint: nil
|
||||
)
|
||||
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
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Alerting::NotificationPayloadParser do
|
||||
describe '.call' do
|
||||
|
@ -89,6 +89,39 @@ describe Gitlab::Alerting::NotificationPayloadParser do
|
|||
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
|
||||
let(:payload) do
|
||||
{
|
||||
|
|
|
@ -317,4 +317,14 @@ describe AlertManagement::Alert do
|
|||
expect { subject }.to change { alert.reload.ended_at }.to nil
|
||||
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
|
||||
|
|
|
@ -688,22 +688,18 @@ describe JiraService do
|
|||
context 'when the test fails' do
|
||||
it 'returns result with the error' do
|
||||
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])
|
||||
.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(
|
||||
"Error sending message",
|
||||
hash_including(
|
||||
client_url: url,
|
||||
error: hash_including(
|
||||
exception_class: 'JIRA::HTTPError',
|
||||
exception_message: 'Some specific failure.'
|
||||
)
|
||||
)
|
||||
'Error sending message',
|
||||
client_url: 'http://jira.example.com',
|
||||
error: error_message
|
||||
)
|
||||
|
||||
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
|
||||
|
|
|
@ -73,6 +73,7 @@ describe Projects::Alerting::NotifyService do
|
|||
describe '#execute' do
|
||||
let(:token) { 'invalid-token' }
|
||||
let(:starts_at) { Time.current.change(usec: 0) }
|
||||
let(:fingerprint) { 'testing' }
|
||||
let(:service) { described_class.new(project, nil, payload) }
|
||||
let(:payload_raw) do
|
||||
{
|
||||
|
@ -82,7 +83,8 @@ describe Projects::Alerting::NotifyService do
|
|||
monitoring_tool: 'GitLab RSpec',
|
||||
service: 'GitLab Test Suite',
|
||||
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
|
||||
end
|
||||
let(:payload) { ActionController::Parameters.new(payload_raw).permit! }
|
||||
|
@ -131,11 +133,23 @@ describe Projects::Alerting::NotifyService do
|
|||
description: payload_raw.fetch(:description),
|
||||
monitoring_tool: payload_raw.fetch(:monitoring_tool),
|
||||
service: payload_raw.fetch(:service),
|
||||
fingerprint: nil,
|
||||
fingerprint: Digest::SHA1.hexdigest(fingerprint),
|
||||
ended_at: nil
|
||||
)
|
||||
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
|
||||
let(:payload_raw) do
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue