+
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index b241d0a2bdc..59ac301301b 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -754,7 +754,8 @@
margin-right: 10px;
min-width: 0;
- .issue-weight-icon {
+ .issue-weight-icon,
+ .issue-estimate-icon {
vertical-align: sub;
}
}
diff --git a/app/models/alert_management/alert.rb b/app/models/alert_management/alert.rb
index acaf474ecc2..be51dab5d68 100644
--- a/app/models/alert_management/alert.rb
+++ b/app/models/alert_management/alert.rb
@@ -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
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index c71c939f452..1e73541fd85 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -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
diff --git a/app/services/projects/alerting/notify_service.rb b/app/services/projects/alerting/notify_service.rb
index 76c89e85f17..3598e723429 100644
--- a/app/services/projects/alerting/notify_service.rb
+++ b/app/services/projects/alerting/notify_service.rb
@@ -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
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 1bf0c8eb031..e325d585d0c 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -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
diff --git a/app/views/projects/issues/_issue_estimate.html.haml b/app/views/projects/issues/_issue_estimate.html.haml
new file mode 100644
index 00000000000..46797d0f1a0
--- /dev/null
+++ b/app/views/projects/issues/_issue_estimate.html.haml
@@ -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)
diff --git a/changelogs/unreleased/214556-user-defined-alert-identification.yml b/changelogs/unreleased/214556-user-defined-alert-identification.yml
new file mode 100644
index 00000000000..d65f76e97c0
--- /dev/null
+++ b/changelogs/unreleased/214556-user-defined-alert-identification.yml
@@ -0,0 +1,5 @@
+---
+title: Set fingerprints and increment events count for Alert Management alerts
+merge_request: 32613
+author:
+type: added
diff --git a/changelogs/unreleased/218648-remove-jira-httperror-non-actionable-exceptions.yml b/changelogs/unreleased/218648-remove-jira-httperror-non-actionable-exceptions.yml
new file mode 100644
index 00000000000..5ac63075c1f
--- /dev/null
+++ b/changelogs/unreleased/218648-remove-jira-httperror-non-actionable-exceptions.yml
@@ -0,0 +1,5 @@
+---
+title: Less verbose JiraService error logs
+merge_request: 32847
+author:
+type: other
diff --git a/changelogs/unreleased/jivanvl-make-chart-panels-focusable-keyboard.yml b/changelogs/unreleased/jivanvl-make-chart-panels-focusable-keyboard.yml
new file mode 100644
index 00000000000..99798f4c58a
--- /dev/null
+++ b/changelogs/unreleased/jivanvl-make-chart-panels-focusable-keyboard.yml
@@ -0,0 +1,5 @@
+---
+title: Focus and toggle metrics dashboard panels via keyboard
+merge_request: 28603
+author:
+type: added
diff --git a/changelogs/unreleased/show_estimate_on_issues_list.yml b/changelogs/unreleased/show_estimate_on_issues_list.yml
new file mode 100644
index 00000000000..9845fcc2f57
--- /dev/null
+++ b/changelogs/unreleased/show_estimate_on_issues_list.yml
@@ -0,0 +1,5 @@
+---
+title: Show estimate on issues list
+merge_request: 28271
+author: Lee Tickett
+type: added
diff --git a/changelogs/unreleased/update-index-artifacts-expire.yml b/changelogs/unreleased/update-index-artifacts-expire.yml
new file mode 100644
index 00000000000..9eaed111aa8
--- /dev/null
+++ b/changelogs/unreleased/update-index-artifacts-expire.yml
@@ -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
diff --git a/db/migrate/20200519171058_update_index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial.rb b/db/migrate/20200519171058_update_index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial.rb
new file mode 100644
index 00000000000..e09ad4bdadf
--- /dev/null
+++ b/db/migrate/20200519171058_update_index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial.rb
@@ -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
diff --git a/db/structure.sql b/db/structure.sql
index 7d37ca11ae4..3930ee67db0 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -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
\.
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index deefd2f0e73..dae088c5222 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -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"
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 2631d9eecb7..f0465edf06b 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -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
diff --git a/doc/user/project/integrations/generic_alerts.md b/doc/user/project/integrations/generic_alerts.md
index 4a12845e6ea..8fa392510db 100644
--- a/doc/user/project/integrations/generic_alerts.md
+++ b/doc/user/project/integrations/generic_alerts.md
@@ -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"
}
```
diff --git a/lib/gitlab/alert_management/alert_params.rb b/lib/gitlab/alert_management/alert_params.rb
index 982479784a9..789a4fe246a 100644
--- a/lib/gitlab/alert_management/alert_params.rb
+++ b/lib/gitlab/alert_management/alert_params.rb
@@ -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
diff --git a/lib/gitlab/alert_management/fingerprint.rb b/lib/gitlab/alert_management/fingerprint.rb
new file mode 100644
index 00000000000..6ab47c88ca1
--- /dev/null
+++ b/lib/gitlab/alert_management/fingerprint.rb
@@ -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
diff --git a/lib/gitlab/alerting/alert.rb b/lib/gitlab/alerting/alert.rb
index 8674bd437d4..802423a93f4 100644
--- a/lib/gitlab/alerting/alert.rb
+++ b/lib/gitlab/alerting/alert.rb
@@ -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?
diff --git a/lib/gitlab/alerting/notification_payload_parser.rb b/lib/gitlab/alerting/notification_payload_parser.rb
index c79d69613f3..d98b9296347 100644
--- a/lib/gitlab/alerting/notification_payload_parser.rb
+++ b/lib/gitlab/alerting/notification_payload_parser.rb
@@ -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
diff --git a/lib/gitlab/jira/http_client.rb b/lib/gitlab/jira/http_client.rb
index b973244a531..c09d8170d17 100644
--- a/lib/gitlab/jira/http_client.rb
+++ b/lib/gitlab/jira/http_client.rb
@@ -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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9ee29a9c441..8a9020dc0a5 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8799,6 +8799,9 @@ msgstr ""
msgid "Errors:"
msgstr ""
+msgid "Estimate"
+msgstr ""
+
msgid "Estimated"
msgstr ""
diff --git a/public/robots.txt b/public/robots.txt
index 6f2396d0869..20cd97a596f 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -35,6 +35,7 @@ Disallow: /snippets/*/raw
User-Agent: *
Disallow: /*/*.git
Disallow: /*/*/fork/new
+Disallow: /*/-/archive/
Disallow: /*/*/repository/archive*
Disallow: /*/*/activity
Disallow: /*/*/new
diff --git a/spec/javascripts/boards/board_list_common_spec.js b/spec/frontend/boards/board_list_helper.js
similarity index 100%
rename from spec/javascripts/boards/board_list_common_spec.js
rename to spec/frontend/boards/board_list_helper.js
diff --git a/spec/frontend/monitoring/components/dashboard_panel_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js
index f8c9bd56721..bd2dd8dc5b2 100644
--- a/spec/frontend/monitoring/components/dashboard_panel_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js
@@ -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', () => {
diff --git a/spec/frontend/monitoring/components/graph_group_spec.js b/spec/frontend/monitoring/components/graph_group_spec.js
index 28a6af64394..92829135c0f 100644
--- a/spec/frontend/monitoring/components/graph_group_spec.js
+++ b/spec/frontend/monitoring/components/graph_group_spec.js
@@ -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', () => {
diff --git a/spec/javascripts/helpers/vuex_action_helper_spec.js b/spec/javascripts/helpers/vuex_action_helper_spec.js
deleted file mode 100644
index 09f0bd395c3..00000000000
--- a/spec/javascripts/helpers/vuex_action_helper_spec.js
+++ /dev/null
@@ -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);
- });
-});
diff --git a/spec/lib/gitlab/alert_management/alert_params_spec.rb b/spec/lib/gitlab/alert_management/alert_params_spec.rb
index 5cf34038f68..284af421f05 100644
--- a/spec/lib/gitlab/alert_management/alert_params_spec.rb
+++ b/spec/lib/gitlab/alert_management/alert_params_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/alert_management/fingerprint_spec.rb b/spec/lib/gitlab/alert_management/fingerprint_spec.rb
new file mode 100644
index 00000000000..7865d667f71
--- /dev/null
+++ b/spec/lib/gitlab/alert_management/fingerprint_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/alerting/notification_payload_parser_spec.rb b/spec/lib/gitlab/alerting/notification_payload_parser_spec.rb
index f32095b3c86..889efae9585 100644
--- a/spec/lib/gitlab/alerting/notification_payload_parser_spec.rb
+++ b/spec/lib/gitlab/alerting/notification_payload_parser_spec.rb
@@ -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
{
diff --git a/spec/models/alert_management/alert_spec.rb b/spec/models/alert_management/alert_spec.rb
index 1da0c6d4071..af9f890da79 100644
--- a/spec/models/alert_management/alert_spec.rb
+++ b/spec/models/alert_management/alert_spec.rb
@@ -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
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 4399a722f89..2ee193a4d10 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -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
diff --git a/spec/services/projects/alerting/notify_service_spec.rb b/spec/services/projects/alerting/notify_service_spec.rb
index b88f0ef5149..d0e911203f4 100644
--- a/spec/services/projects/alerting/notify_service_spec.rb
+++ b/spec/services/projects/alerting/notify_service_spec.rb
@@ -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
{