Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-05-25 09:08:30 +00:00
parent 0ebaa8a2fd
commit 03c3f9f501
35 changed files with 312 additions and 223 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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;
} }
} }

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View 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') }
&nbsp;
= sprite_icon('timer', size: 16, css_class: 'issue-estimate-icon')
= Gitlab::TimeTrackingFormatter.output(issue.time_estimate)

View file

@ -0,0 +1,5 @@
---
title: Set fingerprints and increment events count for Alert Management alerts
merge_request: 32613
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: Less verbose JiraService error logs
merge_request: 32847
author:
type: other

View file

@ -0,0 +1,5 @@
---
title: Focus and toggle metrics dashboard panels via keyboard
merge_request: 28603
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: Show estimate on issues list
merge_request: 28271
author: Lee Tickett
type: added

View 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

View file

@ -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

View file

@ -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
\. \.

View file

@ -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"

View file

@ -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

View file

@ -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"
} }
``` ```

View file

@ -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

View 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

View file

@ -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?

View file

@ -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

View file

@ -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

View file

@ -8799,6 +8799,9 @@ msgstr ""
msgid "Errors:" msgid "Errors:"
msgstr "" msgstr ""
msgid "Estimate"
msgstr ""
msgid "Estimated" msgid "Estimated"
msgstr "" msgstr ""

View file

@ -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

View file

@ -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', () => {

View file

@ -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', () => {

View file

@ -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);
});
});

View file

@ -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

View 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

View file

@ -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
{ {

View file

@ -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

View file

@ -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

View file

@ -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
{ {