Commit 06589d92 authored by Shinya Maeda's avatar Shinya Maeda Committed by Adam Hegyi

Expose alert information for environments

This commit extends GraphQL endpoint and Internal API
to expose alert information for environments.
parent e4695080
...@@ -107,6 +107,16 @@ module Types ...@@ -107,6 +107,16 @@ module Types
description: 'Todos of the current user for the alert', description: 'Todos of the current user for the alert',
resolver: Resolvers::TodoResolver resolver: Resolvers::TodoResolver
field :details_url,
GraphQL::STRING_TYPE,
null: false,
description: 'The URL of the alert detail page'
field :prometheus_alert,
Types::PrometheusAlertType,
null: true,
description: 'The alert condition for Prometheus'
def notes def notes
object.ordered_notes object.ordered_notes
end end
......
...@@ -19,5 +19,10 @@ module Types ...@@ -19,5 +19,10 @@ module Types
field :metrics_dashboard, Types::Metrics::DashboardType, null: true, field :metrics_dashboard, Types::Metrics::DashboardType, null: true,
description: 'Metrics dashboard schema for the environment', description: 'Metrics dashboard schema for the environment',
resolver: Resolvers::Metrics::DashboardResolver resolver: Resolvers::Metrics::DashboardResolver
field :latest_opened_most_severe_alert,
Types::AlertManagement::AlertType,
null: true,
description: 'The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned.'
end end
end end
...@@ -169,6 +169,12 @@ module Types ...@@ -169,6 +169,12 @@ module Types
description: 'Environments of the project', description: 'Environments of the project',
resolver: Resolvers::EnvironmentsResolver resolver: Resolvers::EnvironmentsResolver
field :environment,
Types::EnvironmentType,
null: true,
description: 'A single environment of the project',
resolver: Resolvers::EnvironmentsResolver.single
field :sast_ci_configuration, ::Types::CiConfiguration::Sast::Type, null: true, field :sast_ci_configuration, ::Types::CiConfiguration::Sast::Type, null: true,
description: 'SAST CI configuration for the project', description: 'SAST CI configuration for the project',
resolver: ::Resolvers::CiConfiguration::SastResolver resolver: ::Resolvers::CiConfiguration::SastResolver
......
# frozen_string_literal: true
module Types
class PrometheusAlertType < BaseObject
graphql_name 'PrometheusAlert'
description 'The alert condition for Prometheus'
authorize :read_prometheus_alerts
present_using PrometheusAlertPresenter
field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the alert condition'
field :humanized_text,
GraphQL::STRING_TYPE,
null: false,
description: 'The human-readable text of the alert condition'
end
end
...@@ -118,7 +118,7 @@ module AlertManagement ...@@ -118,7 +118,7 @@ module AlertManagement
end end
delegate :iid, to: :issue, prefix: true, allow_nil: true delegate :iid, to: :issue, prefix: true, allow_nil: true
delegate :metrics_dashboard_url, :runbook, to: :present delegate :metrics_dashboard_url, :runbook, :details_url, to: :present
scope :for_iid, -> (iid) { where(iid: iid) } scope :for_iid, -> (iid) { where(iid: iid) }
scope :for_status, -> (status) { where(status: status) } scope :for_status, -> (status) { where(status: status) }
...@@ -137,6 +137,7 @@ module AlertManagement ...@@ -137,6 +137,7 @@ module AlertManagement
# Descending sort order sorts severity from more critical to less critical. # Descending sort order sorts severity from more critical to less critical.
# https://gitlab.com/gitlab-org/gitlab/-/issues/221242#what-is-the-expected-correct-behavior # https://gitlab.com/gitlab-org/gitlab/-/issues/221242#what-is-the-expected-correct-behavior
scope :order_severity, -> (sort_order) { order(severity: sort_order == :asc ? :desc : :asc) } scope :order_severity, -> (sort_order) { order(severity: sort_order == :asc ? :desc : :asc) }
scope :order_severity_with_open_prometheus_alert, -> { open.with_prometheus_alert.order(severity: :asc, started_at: :desc) }
# Ascending sort order sorts statuses: Ignored > Resolved > Acknowledged > Triggered # Ascending sort order sorts statuses: Ignored > Resolved > Acknowledged > Triggered
# Descending sort order sorts statuses: Triggered > Acknowledged > Resolved > Ignored # Descending sort order sorts statuses: Triggered > Acknowledged > Resolved > Ignored
......
...@@ -29,6 +29,7 @@ class Environment < ApplicationRecord ...@@ -29,6 +29,7 @@ class Environment < ApplicationRecord
has_one :last_visible_deployment, -> { visible.distinct_on_environment }, inverse_of: :environment, class_name: 'Deployment' has_one :last_visible_deployment, -> { visible.distinct_on_environment }, inverse_of: :environment, class_name: 'Deployment'
has_one :last_visible_deployable, through: :last_visible_deployment, source: 'deployable', source_type: 'CommitStatus' has_one :last_visible_deployable, through: :last_visible_deployment, source: 'deployable', source_type: 'CommitStatus'
has_one :last_visible_pipeline, through: :last_visible_deployable, source: 'pipeline' has_one :last_visible_pipeline, through: :last_visible_deployable, source: 'pipeline'
has_one :latest_opened_most_severe_alert, -> { order_severity_with_open_prometheus_alert }, class_name: 'AlertManagement::Alert', inverse_of: :environment
before_validation :nullify_external_url before_validation :nullify_external_url
before_validation :generate_slug, if: ->(env) { env.slug.blank? } before_validation :generate_slug, if: ->(env) { env.slug.blank? }
...@@ -291,6 +292,10 @@ class Environment < ApplicationRecord ...@@ -291,6 +292,10 @@ class Environment < ApplicationRecord
!!ENV['USE_SAMPLE_METRICS'] !!ENV['USE_SAMPLE_METRICS']
end end
def has_opened_alert?
latest_opened_most_severe_alert.present?
end
def metrics def metrics
prometheus_adapter.query(:environment, self) if has_metrics_and_can_query? prometheus_adapter.query(:environment, self) if has_metrics_and_can_query?
end end
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
class PrometheusAlert < ApplicationRecord class PrometheusAlert < ApplicationRecord
include Sortable include Sortable
include UsageStatistics include UsageStatistics
include Presentable
OPERATORS_MAP = { OPERATORS_MAP = {
lt: "<", lt: "<",
......
# frozen_string_literal: true
class PrometheusAlertPolicy < ::BasePolicy
delegate { @subject.project }
end
...@@ -4,6 +4,7 @@ module AlertManagement ...@@ -4,6 +4,7 @@ module AlertManagement
class AlertPresenter < Gitlab::View::Presenter::Delegated class AlertPresenter < Gitlab::View::Presenter::Delegated
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
include IncidentManagement::Settings include IncidentManagement::Settings
include ActionView::Helpers::UrlHelper
MARKDOWN_LINE_BREAK = " \n".freeze MARKDOWN_LINE_BREAK = " \n".freeze
...@@ -45,15 +46,12 @@ module AlertManagement ...@@ -45,15 +46,12 @@ module AlertManagement
def metrics_dashboard_url; end def metrics_dashboard_url; end
private
def details_url def details_url
::Gitlab::Routing.url_helpers.details_project_alert_management_url( details_project_alert_management_url(project, alert.iid)
project,
alert.iid
)
end end
private
attr_reader :alert, :project attr_reader :alert, :project
def alerting_alert def alerting_alert
......
# frozen_string_literal: true
class PrometheusAlertPresenter < Gitlab::View::Presenter::Delegated
include ActionView::Helpers::UrlHelper
presents :prometheus_alert
def humanized_text
operator_text =
case prometheus_alert.operator
when 'lt' then s_('PrometheusAlerts|is less than')
when 'eq' then s_('PrometheusAlerts|is equal to')
when 'gt' then s_('PrometheusAlerts|exceeded')
end
"#{operator_text} #{prometheus_alert.threshold}#{prometheus_alert.prometheus_metric.unit}"
end
end
...@@ -71,6 +71,8 @@ class EnvironmentEntity < Grape::Entity ...@@ -71,6 +71,8 @@ class EnvironmentEntity < Grape::Entity
can?(current_user, :destroy_environment, environment) can?(current_user, :destroy_environment, environment)
end end
expose :has_opened_alert?, if: -> (*) { can_read_alert_management_alert? }, expose_nil: false, as: :has_opened_alert
private private
alias_method :environment, :object alias_method :environment, :object
...@@ -91,6 +93,10 @@ class EnvironmentEntity < Grape::Entity ...@@ -91,6 +93,10 @@ class EnvironmentEntity < Grape::Entity
can?(current_user, :read_pod_logs, environment.project) can?(current_user, :read_pod_logs, environment.project)
end end
def can_read_alert_management_alert?
can?(current_user, :read_alert_management_alert, environment.project)
end
def cluster_platform_kubernetes? def cluster_platform_kubernetes?
deployment_platform && deployment_platform.is_a?(Clusters::Platforms::Kubernetes) deployment_platform && deployment_platform.is_a?(Clusters::Platforms::Kubernetes)
end end
......
---
title: Expose alert information for environments
merge_request: 38881
author:
type: added
...@@ -209,6 +209,11 @@ type AlertManagementAlert implements Noteable { ...@@ -209,6 +209,11 @@ type AlertManagementAlert implements Noteable {
""" """
details: JSON details: JSON
"""
The URL of the alert detail page
"""
detailsUrl: String!
""" """
All discussions on this noteable All discussions on this noteable
""" """
...@@ -294,6 +299,11 @@ type AlertManagementAlert implements Noteable { ...@@ -294,6 +299,11 @@ type AlertManagementAlert implements Noteable {
last: Int last: Int
): NoteConnection! ): NoteConnection!
"""
The alert condition for Prometheus
"""
prometheusAlert: PrometheusAlert
""" """
Runbook for the alert as defined in alert details Runbook for the alert as defined in alert details
""" """
...@@ -4418,6 +4428,11 @@ type Environment { ...@@ -4418,6 +4428,11 @@ type Environment {
""" """
id: ID! id: ID!
"""
The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned.
"""
latestOpenedMostSevereAlert: AlertManagementAlert
""" """
Metrics dashboard schema for the environment Metrics dashboard schema for the environment
""" """
...@@ -10553,6 +10568,26 @@ type Project { ...@@ -10553,6 +10568,26 @@ type Project {
""" """
descriptionHtml: String descriptionHtml: String
"""
A single environment of the project
"""
environment(
"""
Name of the environment
"""
name: String
"""
Search query for environment name
"""
search: String
"""
States of environments that should be included in result
"""
states: [String!]
): Environment
""" """
Environments of the project Environments of the project
""" """
...@@ -12116,6 +12151,21 @@ type ProjectStatistics { ...@@ -12116,6 +12151,21 @@ type ProjectStatistics {
wikiSize: Float wikiSize: Float
} }
"""
The alert condition for Prometheus
"""
type PrometheusAlert {
"""
The human-readable text of the alert condition
"""
humanizedText: String!
"""
ID of the alert condition
"""
id: ID!
}
type Query { type Query {
""" """
Get information about current user Get information about current user
......
...@@ -577,6 +577,24 @@ ...@@ -577,6 +577,24 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "detailsUrl",
"description": "The URL of the alert detail page",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "discussions", "name": "discussions",
"description": "All discussions on this noteable", "description": "All discussions on this noteable",
...@@ -801,6 +819,20 @@ ...@@ -801,6 +819,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "prometheusAlert",
"description": "The alert condition for Prometheus",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "PrometheusAlert",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "runbook", "name": "runbook",
"description": "Runbook for the alert as defined in alert details", "description": "Runbook for the alert as defined in alert details",
...@@ -12338,6 +12370,20 @@ ...@@ -12338,6 +12370,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "latestOpenedMostSevereAlert",
"description": "The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "AlertManagementAlert",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "metricsDashboard", "name": "metricsDashboard",
"description": "Metrics dashboard schema for the environment", "description": "Metrics dashboard schema for the environment",
...@@ -31457,6 +31503,57 @@ ...@@ -31457,6 +31503,57 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "environment",
"description": "A single environment of the project",
"args": [
{
"name": "name",
"description": "Name of the environment",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "search",
"description": "Search query for environment name",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "states",
"description": "States of environments that should be included in result",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "Environment",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "environments", "name": "environments",
"description": "Environments of the project", "description": "Environments of the project",
...@@ -35655,6 +35752,55 @@ ...@@ -35655,6 +35752,55 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "PrometheusAlert",
"description": "The alert condition for Prometheus",
"fields": [
{
"name": "humanizedText",
"description": "The human-readable text of the alert condition",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "id",
"description": "ID of the alert condition",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "Query", "name": "Query",
...@@ -64,6 +64,7 @@ Describes an alert from the project's Alert Management ...@@ -64,6 +64,7 @@ Describes an alert from the project's Alert Management
| `createdAt` | Time | Timestamp the alert was created | | `createdAt` | Time | Timestamp the alert was created |
| `description` | String | Description of the alert | | `description` | String | Description of the alert |
| `details` | JSON | Alert details | | `details` | JSON | Alert details |
| `detailsUrl` | String! | The URL of the alert detail page |
| `endedAt` | Time | Timestamp the alert ended | | `endedAt` | Time | Timestamp the alert ended |
| `eventCount` | Int | Number of events of this alert | | `eventCount` | Int | Number of events of this alert |
| `hosts` | String! => Array | List of hosts the alert came from | | `hosts` | String! => Array | List of hosts the alert came from |
...@@ -71,6 +72,7 @@ Describes an alert from the project's Alert Management ...@@ -71,6 +72,7 @@ Describes an alert from the project's Alert Management
| `issueIid` | ID | Internal ID of the GitLab issue attached to the alert | | `issueIid` | ID | Internal ID of the GitLab issue attached to the alert |
| `metricsDashboardUrl` | String | URL for metrics embed for the alert | | `metricsDashboardUrl` | String | URL for metrics embed for the alert |
| `monitoringTool` | String | Monitoring tool the alert came from | | `monitoringTool` | String | Monitoring tool the alert came from |
| `prometheusAlert` | PrometheusAlert | The alert condition for Prometheus |
| `runbook` | String | Runbook for the alert as defined in alert details | | `runbook` | String | Runbook for the alert as defined in alert details |
| `service` | String | Service the alert came from | | `service` | String | Service the alert came from |
| `severity` | AlertManagementSeverity | Severity of the alert | | `severity` | AlertManagementSeverity | Severity of the alert |
...@@ -739,6 +741,7 @@ Describes where code is deployed for a project ...@@ -739,6 +741,7 @@ Describes where code is deployed for a project
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `id` | ID! | ID of the environment | | `id` | ID! | ID of the environment |
| `latestOpenedMostSevereAlert` | AlertManagementAlert | The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned. |
| `metricsDashboard` | MetricsDashboard | Metrics dashboard schema for the environment | | `metricsDashboard` | MetricsDashboard | Metrics dashboard schema for the environment |
| `name` | String! | Human-readable name of the environment | | `name` | String! | Human-readable name of the environment |
| `state` | String! | State of the environment, for example: available/stopped | | `state` | String! | State of the environment, for example: available/stopped |
...@@ -1602,6 +1605,7 @@ Information about pagination in a connection. ...@@ -1602,6 +1605,7 @@ Information about pagination in a connection.
| `createdAt` | Time | Timestamp of the project creation | | `createdAt` | Time | Timestamp of the project creation |
| `description` | String | Short description of the project | | `description` | String | Short description of the project |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `environment` | Environment | A single environment of the project |
| `forksCount` | Int! | Number of times the project has been forked | | `forksCount` | Int! | Number of times the project has been forked |
| `fullPath` | ID! | Full path of the project | | `fullPath` | ID! | Full path of the project |
| `grafanaIntegration` | GrafanaIntegration | Grafana integration details for the project | | `grafanaIntegration` | GrafanaIntegration | Grafana integration details for the project |
...@@ -1732,6 +1736,15 @@ Represents a Project Member ...@@ -1732,6 +1736,15 @@ Represents a Project Member
| `storageSize` | Float! | Storage size of the project | | `storageSize` | Float! | Storage size of the project |
| `wikiSize` | Float | Wiki size of the project | | `wikiSize` | Float | Wiki size of the project |
## PrometheusAlert
The alert condition for Prometheus
| Name | Type | Description |
| --- | ---- | ---------- |
| `humanizedText` | String! | The human-readable text of the alert condition |
| `id` | ID! | ID of the alert condition |
## Release ## Release
Represents a release Represents a release
......
...@@ -75,6 +75,7 @@ ...@@ -75,6 +75,7 @@
"can_stop": { "can_stop": {
"type": "boolean" "type": "boolean"
}, },
"has_opened_alert": { "type": "boolean" },
"cancel_auto_stop_path": { "type": "string" }, "cancel_auto_stop_path": { "type": "string" },
"auto_stop_at": { "type": "string", "format": "date-time" }, "auto_stop_at": { "type": "string", "format": "date-time" },
"can_delete": { "can_delete": {
......
...@@ -19432,9 +19432,18 @@ msgstr "" ...@@ -19432,9 +19432,18 @@ msgstr ""
msgid "PrometheusAlerts|Threshold" msgid "PrometheusAlerts|Threshold"
msgstr "" msgstr ""
msgid "PrometheusAlerts|exceeded"
msgstr ""
msgid "PrometheusAlerts|https://gitlab.com/gitlab-com/runbooks" msgid "PrometheusAlerts|https://gitlab.com/gitlab-com/runbooks"
msgstr "" msgstr ""
msgid "PrometheusAlerts|is equal to"
msgstr ""
msgid "PrometheusAlerts|is less than"
msgstr ""
msgid "PrometheusService|%{exporters} with %{metrics} were found" msgid "PrometheusService|%{exporters} with %{metrics} were found"
msgstr "" msgstr ""
......
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
"updated_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" },
"auto_stop_at": { "type": "string", "format": "date-time" }, "auto_stop_at": { "type": "string", "format": "date-time" },
"can_stop": { "type": "boolean" }, "can_stop": { "type": "boolean" },
"has_opened_alert": { "type": "boolean" },
"cluster_type": { "type": "types/nullable_string.json" }, "cluster_type": { "type": "types/nullable_string.json" },
"terminal_path": { "type": "types/nullable_string.json" }, "terminal_path": { "type": "types/nullable_string.json" },
"last_deployment": { "last_deployment": {
......
...@@ -30,6 +30,8 @@ RSpec.describe GitlabSchema.types['AlertManagementAlert'] do ...@@ -30,6 +30,8 @@ RSpec.describe GitlabSchema.types['AlertManagementAlert'] do
metrics_dashboard_url metrics_dashboard_url
runbook runbook
todos todos
details_url
prometheus_alert
] ]
expect(described_class).to have_graphql_fields(*expected_fields) expect(described_class).to have_graphql_fields(*expected_fields)
......
...@@ -7,11 +7,76 @@ RSpec.describe GitlabSchema.types['Environment'] do ...@@ -7,11 +7,76 @@ RSpec.describe GitlabSchema.types['Environment'] do
it 'has the expected fields' do it 'has the expected fields' do
expected_fields = %w[ expected_fields = %w[
name id state metrics_dashboard name id state metrics_dashboard latest_opened_most_severe_alert
] ]
expect(described_class).to have_graphql_fields(*expected_fields) expect(described_class).to have_graphql_fields(*expected_fields)
end end
specify { expect(described_class).to require_graphql_authorizations(:read_environment) } specify { expect(described_class).to require_graphql_authorizations(:read_environment) }
context 'when there is an environment' do
let_it_be(:project) { create(:project) }
let_it_be(:environment) { create(:environment, project: project) }
let_it_be(:user) { create(:user) }
subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
let(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
environment(name: "#{environment.name}") {
name
state
}
}
}
)
end
before do
project.add_developer(user)
end
it 'returns an environment' do
expect(subject['data']['project']['environment']['name']).to eq(environment.name)
end
context 'when query alert data for the environment' do
let_it_be(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
environment(name: "#{environment.name}") {
name
state
latestOpenedMostSevereAlert {
severity
title
detailsUrl
prometheusAlert {
humanizedText
}
}
}
}
}
)
end
it 'does not return alert information' do
expect(subject['data']['project']['environment']['latestOpenedMostSevereAlert']).to be_nil
end
context 'when alert is raised on the environment' do
let!(:prometheus_alert) { create(:prometheus_alert, project: project, environment: environment) }
let!(:alert) { create(:alert_management_alert, :triggered, :prometheus, project: project, environment: environment, prometheus_alert: prometheus_alert) }
it 'returns alert information' do
expect(subject['data']['project']['environment']['latestOpenedMostSevereAlert']['severity']).to eq(alert.severity.upcase)
end
end
end
end
end end
...@@ -24,7 +24,7 @@ RSpec.describe GitlabSchema.types['Project'] do ...@@ -24,7 +24,7 @@ RSpec.describe GitlabSchema.types['Project'] do
namespace group statistics repository merge_requests merge_request issues namespace group statistics repository merge_requests merge_request issues
issue milestones pipelines removeSourceBranchAfterMerge sentryDetailedError snippets issue milestones pipelines removeSourceBranchAfterMerge sentryDetailedError snippets
grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments
boards jira_import_status jira_imports services releases release environment boards jira_import_status jira_imports services releases release
alert_management_alerts alert_management_alert alert_management_alert_status_counts alert_management_alerts alert_management_alert alert_management_alert_status_counts
container_expiration_policy sast_ci_configuration service_desk_enabled service_desk_address container_expiration_policy sast_ci_configuration service_desk_enabled service_desk_address
issue_status_counts issue_status_counts
...@@ -98,6 +98,13 @@ RSpec.describe GitlabSchema.types['Project'] do ...@@ -98,6 +98,13 @@ RSpec.describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_resolver(Resolvers::EnvironmentsResolver) } it { is_expected.to have_graphql_resolver(Resolvers::EnvironmentsResolver) }
end end
describe 'environment field' do
subject { described_class.fields['environment'] }
it { is_expected.to have_graphql_type(Types::EnvironmentType) }
it { is_expected.to have_graphql_resolver(Resolvers::EnvironmentsResolver.single) }
end
describe 'members field' do describe 'members field' do
subject { described_class.fields['projectMembers'] } subject { described_class.fields['projectMembers'] }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['PrometheusAlert'] do
specify { expect(described_class.graphql_name).to eq('PrometheusAlert') }
it 'has the expected fields' do
expected_fields = %w[
id humanized_text
]
expect(described_class).to have_graphql_fields(*expected_fields)
end
specify { expect(described_class).to require_graphql_authorizations(:read_prometheus_alerts) }
end
...@@ -230,6 +230,17 @@ RSpec.describe AlertManagement::Alert do ...@@ -230,6 +230,17 @@ RSpec.describe AlertManagement::Alert do
it { is_expected.to match_array(env_alert) } it { is_expected.to match_array(env_alert) }
end end
describe '.order_severity_with_open_prometheus_alert' do
subject { described_class.where(project: alert_project).order_severity_with_open_prometheus_alert }
let_it_be(:alert_project) { create(:project) }
let_it_be(:resolved_critical_alert) { create(:alert_management_alert, :resolved, :critical, project: alert_project) }
let_it_be(:triggered_critical_alert) { create(:alert_management_alert, :triggered, :critical, project: alert_project) }
let_it_be(:triggered_high_alert) { create(:alert_management_alert, :triggered, :high, project: alert_project) }
it { is_expected.to eq([triggered_critical_alert, triggered_high_alert]) }
end
describe '.counts_by_status' do describe '.counts_by_status' do
subject { described_class.counts_by_status } subject { described_class.counts_by_status }
......
...@@ -19,6 +19,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do ...@@ -19,6 +19,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
it { is_expected.to have_many(:deployments) } it { is_expected.to have_many(:deployments) }
it { is_expected.to have_many(:metrics_dashboard_annotations) } it { is_expected.to have_many(:metrics_dashboard_annotations) }
it { is_expected.to have_many(:alert_management_alerts) } it { is_expected.to have_many(:alert_management_alerts) }
it { is_expected.to have_one(:latest_opened_most_severe_alert) }
it { is_expected.to delegate_method(:stop_action).to(:last_deployment) } it { is_expected.to delegate_method(:stop_action).to(:last_deployment) }
it { is_expected.to delegate_method(:manual_actions).to(:last_deployment) } it { is_expected.to delegate_method(:manual_actions).to(:last_deployment) }
...@@ -1347,4 +1348,27 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do ...@@ -1347,4 +1348,27 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
expect(project.environments.count_by_state).to eq({ stopped: 0, available: 0 }) expect(project.environments.count_by_state).to eq({ stopped: 0, available: 0 })
end end
end end
describe '#has_opened_alert?' do
subject { environment.has_opened_alert? }
let_it_be(:project) { create(:project) }
let_it_be(:environment, reload: true) { create(:environment, project: project) }
context 'when environment has an triggered alert' do
let!(:alert) { create(:alert_management_alert, :triggered, project: project, environment: environment) }
it { is_expected.to be(true) }
end
context 'when environment has an resolved alert' do
let!(:alert) { create(:alert_management_alert, :resolved, project: project, environment: environment) }
it { is_expected.to be(false) }
end
context 'when environment does not have an alert' do
it { is_expected.to be(false) }
end
end
end end
...@@ -58,4 +58,10 @@ RSpec.describe AlertManagement::AlertPresenter do ...@@ -58,4 +58,10 @@ RSpec.describe AlertManagement::AlertPresenter do
expect(presenter.runbook).to eq('https://runbook.com') expect(presenter.runbook).to eq('https://runbook.com')
end end
end end
describe '#details_url' do
it 'returns the details URL' do
expect(presenter.details_url).to match(%r{#{project.web_url}/-/alert_management/#{alert.iid}/details})
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe PrometheusAlertPresenter do
let_it_be(:project) { create(:project) }
let_it_be(:environment) { create(:environment, project: project) }
let(:presenter) { described_class.new(prometheus_alert) }
describe '#humanized_text' do
subject { presenter.humanized_text }
let_it_be(:prometheus_metric) { create(:prometheus_metric, project: project) }
let(:prometheus_alert) { create(:prometheus_alert, operator: operator, project: project, environment: environment, prometheus_metric: prometheus_metric) }
let(:operator) { :gt }
it { is_expected.to eq('exceeded 1.0m/s') }
context 'when operator is eq' do
let(:operator) { :eq }
it { is_expected.to eq('is equal to 1.0m/s') }
end
context 'when operator is lt' do
let(:operator) { :lt }
it { is_expected.to eq('is less than 1.0m/s') }
end
end
end
...@@ -7,9 +7,9 @@ RSpec.describe 'getting Alert Management Alerts' do ...@@ -7,9 +7,9 @@ RSpec.describe 'getting Alert Management Alerts' do
let_it_be(:payload) { { 'custom' => { 'alert' => 'payload' }, 'runbook' => 'runbook' } } let_it_be(:payload) { { 'custom' => { 'alert' => 'payload' }, 'runbook' => 'runbook' } }
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:resolved_alert) { create(:alert_management_alert, :all_fields, :resolved, project: project, issue: nil, severity: :low) } let_it_be(:resolved_alert) { create(:alert_management_alert, :all_fields, :resolved, project: project, issue: nil, severity: :low).present }
let_it_be(:triggered_alert) { create(:alert_management_alert, :all_fields, project: project, severity: :critical, payload: payload) } let_it_be(:triggered_alert) { create(:alert_management_alert, :all_fields, project: project, severity: :critical, payload: payload).present }
let_it_be(:other_project_alert) { create(:alert_management_alert, :all_fields) } let_it_be(:other_project_alert) { create(:alert_management_alert, :all_fields).present }
let(:params) { {} } let(:params) { {} }
...@@ -75,6 +75,8 @@ RSpec.describe 'getting Alert Management Alerts' do ...@@ -75,6 +75,8 @@ RSpec.describe 'getting Alert Management Alerts' do
'createdAt' => triggered_alert.created_at.strftime('%Y-%m-%dT%H:%M:%SZ'), 'createdAt' => triggered_alert.created_at.strftime('%Y-%m-%dT%H:%M:%SZ'),
'updatedAt' => triggered_alert.updated_at.strftime('%Y-%m-%dT%H:%M:%SZ'), 'updatedAt' => triggered_alert.updated_at.strftime('%Y-%m-%dT%H:%M:%SZ'),
'metricsDashboardUrl' => nil, 'metricsDashboardUrl' => nil,
'detailsUrl' => triggered_alert.details_url,
'prometheusAlert' => nil,
'runbook' => 'runbook' 'runbook' => 'runbook'
) )
......
...@@ -82,6 +82,26 @@ RSpec.describe EnvironmentEntity do ...@@ -82,6 +82,26 @@ RSpec.describe EnvironmentEntity do
end end
end end
context 'with alert' do
let!(:environment) { create(:environment, project: project) }
let!(:prometheus_alert) { create(:prometheus_alert, project: project, environment: environment) }
let!(:alert) { create(:alert_management_alert, :triggered, :prometheus, project: project, environment: environment, prometheus_alert: prometheus_alert) }
it 'exposes active alert flag' do
project.add_maintainer(user)
expect(subject[:has_opened_alert]).to eq(true)
end
context 'when user does not have permission to read alert' do
it 'does not expose active alert flag' do
project.add_reporter(user)
expect(subject[:has_opened_alert]).to be_nil
end
end
end
context 'pod_logs' do context 'pod_logs' do
context 'with reporter access' do context 'with reporter access' do
before do before do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment