Commit 126e0158 authored by Peter Leitzen's avatar Peter Leitzen

Merge branch 'ck3g-graphql-query-for-persisted-custom-mapping' into 'master'

Return custom mapping fields for HttpIntegration

See merge request gitlab-org/gitlab!53629
parents 8f47c9a2 44e2a3a4
# frozen_string_literal: true
module Resolvers
module AlertManagement
class HttpIntegrationsResolver < BaseResolver
alias_method :project, :synchronized_object
type Types::AlertManagement::HttpIntegrationType.connection_type, null: true
def resolve(**args)
http_integrations
end
private
def http_integrations
return [] unless Ability.allowed?(current_user, :admin_operations, project)
::AlertManagement::HttpIntegrationsFinder.new(project, {}).execute
end
end
end
end
...@@ -20,3 +20,5 @@ module Types ...@@ -20,3 +20,5 @@ module Types
end end
end end
end end
Types::AlertManagement::HttpIntegrationType.prepend_ee_mod
...@@ -273,6 +273,12 @@ module Types ...@@ -273,6 +273,12 @@ module Types
description: 'Integrations which can receive alerts for the project.', description: 'Integrations which can receive alerts for the project.',
resolver: Resolvers::AlertManagement::IntegrationsResolver resolver: Resolvers::AlertManagement::IntegrationsResolver
field :alert_management_http_integrations,
Types::AlertManagement::HttpIntegrationType.connection_type,
null: true,
description: 'HTTP Integrations which can receive alerts for the project.',
resolver: Resolvers::AlertManagement::HttpIntegrationsResolver
field :releases, field :releases,
Types::ReleaseType.connection_type, Types::ReleaseType.connection_type,
null: true, null: true,
......
...@@ -634,6 +634,21 @@ type AlertManagementHttpIntegration implements AlertManagementIntegration { ...@@ -634,6 +634,21 @@ type AlertManagementHttpIntegration implements AlertManagementIntegration {
""" """
name: String name: String
"""
Extract alert fields from payload example for custom mapping.
"""
payloadAlertFields: [AlertManagementPayloadAlertField!]
"""
The custom mapping of GitLab alert attributes to fields from the payload_example.
"""
payloadAttributeMappings: [AlertManagementPayloadAlertMappingField!]
"""
The example of an alert payload.
"""
payloadExample: JsonString
""" """
Token used to authenticate alert notification requests. Token used to authenticate alert notification requests.
""" """
...@@ -650,6 +665,41 @@ type AlertManagementHttpIntegration implements AlertManagementIntegration { ...@@ -650,6 +665,41 @@ type AlertManagementHttpIntegration implements AlertManagementIntegration {
url: String url: String
} }
"""
The connection type for AlertManagementHttpIntegration.
"""
type AlertManagementHttpIntegrationConnection {
"""
A list of edges.
"""
edges: [AlertManagementHttpIntegrationEdge]
"""
A list of nodes.
"""
nodes: [AlertManagementHttpIntegration]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type AlertManagementHttpIntegrationEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: AlertManagementHttpIntegration
}
""" """
Identifier of AlertManagement::HttpIntegration. Identifier of AlertManagement::HttpIntegration.
""" """
...@@ -862,6 +912,31 @@ enum AlertManagementPayloadAlertFieldType { ...@@ -862,6 +912,31 @@ enum AlertManagementPayloadAlertFieldType {
STRING STRING
} }
"""
Parsed field (with its name) from an alert used for custom mappings
"""
type AlertManagementPayloadAlertMappingField {
"""
A GitLab alert field name.
"""
fieldName: AlertManagementPayloadAlertFieldName
"""
Human-readable label of the payload path.
"""
label: String
"""
Path to value inside payload JSON.
"""
path: [String!]
"""
Type of the parsed value.
"""
type: AlertManagementPayloadAlertFieldType
}
""" """
An endpoint and credentials used to accept Prometheus alerts for a project An endpoint and credentials used to accept Prometheus alerts for a project
""" """
...@@ -19031,6 +19106,31 @@ type Project { ...@@ -19031,6 +19106,31 @@ type Project {
statuses: [AlertManagementStatus!] statuses: [AlertManagementStatus!]
): AlertManagementAlertConnection ): AlertManagementAlertConnection
"""
HTTP Integrations which can receive alerts for the project.
"""
alertManagementHttpIntegrations(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): AlertManagementHttpIntegrationConnection
""" """
Integrations which can receive alerts for the project. Integrations which can receive alerts for the project.
""" """
......
...@@ -274,6 +274,9 @@ An endpoint and credentials used to accept alerts for a project. ...@@ -274,6 +274,9 @@ An endpoint and credentials used to accept alerts for a project.
| `apiUrl` | String | URL at which Prometheus metrics can be queried to populate the metrics dashboard. | | `apiUrl` | String | URL at which Prometheus metrics can be queried to populate the metrics dashboard. |
| `id` | ID! | ID of the integration. | | `id` | ID! | ID of the integration. |
| `name` | String | Name of the integration. | | `name` | String | Name of the integration. |
| `payloadAlertFields` | AlertManagementPayloadAlertField! => Array | Extract alert fields from payload example for custom mapping. |
| `payloadAttributeMappings` | AlertManagementPayloadAlertMappingField! => Array | The custom mapping of GitLab alert attributes to fields from the payload_example. |
| `payloadExample` | JsonString | The example of an alert payload. |
| `token` | String | Token used to authenticate alert notification requests. | | `token` | String | Token used to authenticate alert notification requests. |
| `type` | AlertManagementIntegrationType! | Type of integration. | | `type` | AlertManagementIntegrationType! | Type of integration. |
| `url` | String | Endpoint which accepts alert notifications. | | `url` | String | Endpoint which accepts alert notifications. |
...@@ -288,6 +291,17 @@ Parsed field from an alert used for custom mappings. ...@@ -288,6 +291,17 @@ Parsed field from an alert used for custom mappings.
| `path` | String! => Array | Path to value inside payload JSON. | | `path` | String! => Array | Path to value inside payload JSON. |
| `type` | AlertManagementPayloadAlertFieldType | Type of the parsed value. | | `type` | AlertManagementPayloadAlertFieldType | Type of the parsed value. |
### AlertManagementPayloadAlertMappingField
Parsed field (with its name) from an alert used for custom mappings.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `fieldName` | AlertManagementPayloadAlertFieldName | A GitLab alert field name. |
| `label` | String | Human-readable label of the payload path. |
| `path` | String! => Array | Path to value inside payload JSON. |
| `type` | AlertManagementPayloadAlertFieldType | Type of the parsed value. |
### AlertManagementPrometheusIntegration ### AlertManagementPrometheusIntegration
An endpoint and credentials used to accept Prometheus alerts for a project. An endpoint and credentials used to accept Prometheus alerts for a project.
...@@ -3028,6 +3042,7 @@ Autogenerated return type of PipelineRetry. ...@@ -3028,6 +3042,7 @@ Autogenerated return type of PipelineRetry.
| `alertManagementAlert` | AlertManagementAlert | A single Alert Management alert of the project. | | `alertManagementAlert` | AlertManagementAlert | A single Alert Management alert of the project. |
| `alertManagementAlertStatusCounts` | AlertManagementAlertStatusCountsType | Counts of alerts by status for the project. | | `alertManagementAlertStatusCounts` | AlertManagementAlertStatusCountsType | Counts of alerts by status for the project. |
| `alertManagementAlerts` | AlertManagementAlertConnection | Alert Management alerts of the project. | | `alertManagementAlerts` | AlertManagementAlertConnection | Alert Management alerts of the project. |
| `alertManagementHttpIntegrations` | AlertManagementHttpIntegrationConnection | HTTP Integrations which can receive alerts for the project. |
| `alertManagementIntegrations` | AlertManagementIntegrationConnection | Integrations which can receive alerts for the project. | | `alertManagementIntegrations` | AlertManagementIntegrationConnection | Integrations which can receive alerts for the project. |
| `alertManagementPayloadFields` | AlertManagementPayloadAlertField! => Array | Extract alert fields from payload for custom mapping. | | `alertManagementPayloadFields` | AlertManagementPayloadAlertField! => Array | Extract alert fields from payload for custom mapping. |
| `allowMergeOnSkippedPipeline` | Boolean | If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of the project can also be merged with skipped jobs. | | `allowMergeOnSkippedPipeline` | Boolean | If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of the project can also be merged with skipped jobs. |
......
# frozen_string_literal: true
module EE
module Types
module AlertManagement
module HttpIntegrationType
extend ActiveSupport::Concern
prepended do
field :payload_example, ::Types::JsonStringType,
null: true,
description: 'The example of an alert payload.'
field :payload_attribute_mappings, [::Types::AlertManagement::PayloadAlertMappingFieldType],
null: true,
description: 'The custom mapping of GitLab alert attributes to fields from the payload_example.',
resolver: ::Resolvers::AlertManagement::PayloadAlertMappingFieldResolver
field :payload_alert_fields, [::Types::AlertManagement::PayloadAlertFieldType],
null: true,
description: 'Extract alert fields from payload example for custom mapping.',
resolver: ::Resolvers::AlertManagement::PersistedPayloadAlertFieldResolver
end
end
end
end
end
# frozen_string_literal: true
module Resolvers
module AlertManagement
class PayloadAlertMappingFieldResolver < BaseResolver
type ::Types::AlertManagement::PayloadAlertMappingFieldType, null: true
alias_method :integration, :object
def resolve
integration.payload_attribute_mapping.map do |field_name, mapping|
::AlertManagement::AlertPayloadField.new(
project: integration.project,
field_name: field_name,
path: mapping['path'],
label: mapping['label'],
type: mapping['type']
)
end
end
end
end
end
# frozen_string_literal: true
module Resolvers
module AlertManagement
class PersistedPayloadAlertFieldResolver < BaseResolver
type ::Types::AlertManagement::PayloadAlertFieldType, null: true
alias_method :integration, :object
def resolve
Gitlab::AlertManagement::AlertPayloadFieldExtractor
.new(integration.project)
.extract(integration.payload_example)
end
end
end
end
# frozen_string_literal: true
module Types
module AlertManagement
class PayloadAlertMappingFieldType < BaseObject
graphql_name 'AlertManagementPayloadAlertMappingField'
description 'Parsed field (with its name) from an alert used for custom mappings'
authorize :read_alert_management_alert
field :field_name,
::Types::AlertManagement::PayloadAlertFieldNameEnum,
null: true,
description: 'A GitLab alert field name.'
field :path,
[GraphQL::STRING_TYPE],
null: true,
description: 'Path to value inside payload JSON.'
field :label,
GraphQL::STRING_TYPE,
null: true,
description: 'Human-readable label of the payload path.'
field :type,
::Types::AlertManagement::PayloadAlertFieldTypeEnum,
null: true,
description: 'Type of the parsed value.'
end
end
end
...@@ -5,7 +5,7 @@ module AlertManagement ...@@ -5,7 +5,7 @@ module AlertManagement
include ActiveModel::Model include ActiveModel::Model
include ActiveModel::Validations include ActiveModel::Validations
attr_accessor :project, :path, :label, :type attr_accessor :project, :path, :label, :type, :field_name
ARRAY_TYPE = 'array' ARRAY_TYPE = 'array'
DATETIME_TYPE = 'datetime' DATETIME_TYPE = 'datetime'
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['AlertManagementHttpIntegration'] do
specify { expect(described_class.graphql_name).to eq('AlertManagementHttpIntegration') }
specify { expect(described_class).to require_graphql_authorizations(:admin_operations) }
it 'exposes the expected fields' do
expected_fields = %i[
id
type
name
active
token
url
api_url
payload_example
payload_attribute_mappings
payload_alert_fields
]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'getting Alert Management HTTP Integrations' do
include ::Gitlab::Routing.url_helpers
include GraphqlHelpers
let_it_be(:payload_example) do
{
alert: {
desc: 'Alert description',
name: 'Alert name'
}
}
end
let_it_be(:payload_attribute_mapping) do
{
title: { path: %w(alert name), type: 'string' },
description: { path: %w(alert desc), type: 'string', label: 'Description' }
}
end
let_it_be(:project) { create(:project, :repository) }
let_it_be(:maintainer) { create(:user) }
let_it_be(:developer) { create(:user) }
let_it_be(:guest) { create(:user) }
let_it_be(:prometheus_service) { create(:prometheus_service, project: project) }
let_it_be(:project_alerting_setting) { create(:project_alerting_setting, project: project) }
let_it_be(:inactive_http_integration) { create(:alert_management_http_integration, :inactive, project: project) }
let_it_be(:other_project_http_integration) { create(:alert_management_http_integration) }
let_it_be(:active_http_integration) do
create(
:alert_management_http_integration,
project: project,
payload_example: payload_example,
payload_attribute_mapping: payload_attribute_mapping
)
end
let(:fields) do
<<~QUERY
nodes {
#{all_graphql_fields_for('AlertManagementHttpIntegration')}
}
QUERY
end
let(:query) do
graphql_query_for(
'project',
{ 'fullPath' => project.full_path },
query_graphql_field('alertManagementHttpIntegrations', {}, fields)
)
end
before do
stub_licensed_features(multiple_alert_http_integrations: true)
stub_feature_flags(multiple_http_integrations_custom_mapping: project)
end
before_all do
project.add_developer(developer)
project.add_maintainer(maintainer)
end
context 'with integrations' do
let(:integrations) { graphql_data.dig('project', 'alertManagementHttpIntegrations', 'nodes') }
context 'without project permissions' do
let(:current_user) { guest }
before do
post_graphql(query, current_user: current_user)
end
it_behaves_like 'a working graphql query'
specify { expect(integrations).to be_nil }
end
context 'with developer permissions' do
let(:current_user) { developer }
before do
post_graphql(query, current_user: current_user)
end
it_behaves_like 'a working graphql query'
specify { expect(integrations).to eq([]) }
end
context 'with maintainer permissions' do
let(:current_user) { maintainer }
before do
post_graphql(query, current_user: current_user)
end
it_behaves_like 'a working graphql query'
specify { expect(integrations.size).to eq(2) }
it 'returns the correct properties of the integrations' do
expect(integrations).to include(
{
'id' => GitlabSchema.id_from_object(active_http_integration).to_s,
'type' => 'HTTP',
'name' => active_http_integration.name,
'active' => active_http_integration.active,
'token' => active_http_integration.token,
'url' => active_http_integration.url,
'apiUrl' => nil,
'payloadExample' => payload_example.to_json,
'payloadAttributeMappings' => [
{
'fieldName' => 'TITLE',
'label' => nil,
'path' => %w(alert name),
'type' => 'STRING'
},
{
'fieldName' => 'DESCRIPTION',
'label' => 'Description',
'path' => %w(alert desc),
'type' => 'STRING'
}
],
'payloadAlertFields' => [
{
'label' => 'Name',
'path' => %w(alert name),
'type' => 'STRING'
},
{
'label' => 'Desc',
'path' => %w(alert desc),
'type' => 'STRING'
}
]
},
{
'id' => GitlabSchema.id_from_object(inactive_http_integration).to_s,
'type' => 'HTTP',
'name' => inactive_http_integration.name,
'active' => inactive_http_integration.active,
'token' => inactive_http_integration.token,
'url' => inactive_http_integration.url,
'apiUrl' => nil,
'payloadExample' => "{}",
'payloadAttributeMappings' => [],
'payloadAlertFields' => []
}
)
end
end
end
end
...@@ -8,9 +8,9 @@ RSpec.describe 'getting Alert Management Integrations' do ...@@ -8,9 +8,9 @@ RSpec.describe 'getting Alert Management Integrations' do
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(:prometheus_service) { create(:prometheus_service, project: project) } let_it_be(:prometheus_service) { create(:prometheus_service, project: project) }
let_it_be(:project_alerting_setting) { create(:project_alerting_setting, project: project) }
let_it_be(:active_http_integration) { create(:alert_management_http_integration, project: project) } let_it_be(:active_http_integration) { create(:alert_management_http_integration, project: project) }
let_it_be(:inactive_http_integration) { create(:alert_management_http_integration, :inactive, project: project) } let_it_be(:inactive_http_integration) { create(:alert_management_http_integration, :inactive, project: project) }
let_it_be(:project_alerting_setting) { create(:project_alerting_setting, project: project) }
let_it_be(:other_project_http_integration) { create(:alert_management_http_integration) } let_it_be(:other_project_http_integration) { create(:alert_management_http_integration) }
let(:fields) do let(:fields) do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::AlertManagement::HttpIntegrationsResolver do
include GraphqlHelpers
let_it_be(:guest) { create(:user) }
let_it_be(:developer) { create(:user) }
let_it_be(:maintainer) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:prometheus_integration) { create(:prometheus_service, project: project) }
let_it_be(:active_http_integration) { create(:alert_management_http_integration, project: project) }
let_it_be(:inactive_http_integration) { create(:alert_management_http_integration, :inactive, project: project) }
let_it_be(:other_proj_integration) { create(:alert_management_http_integration) }
subject { sync(resolve_http_integrations) }
before do
project.add_developer(developer)
project.add_maintainer(maintainer)
end
specify do
expect(described_class).to have_nullable_graphql_type(Types::AlertManagement::HttpIntegrationType.connection_type)
end
context 'user does not have permission' do
let(:current_user) { guest }
it { is_expected.to be_empty }
end
context 'user has developer permission' do
let(:current_user) { developer }
it { is_expected.to be_empty }
end
context 'user has maintainer permission' do
let(:current_user) { maintainer }
it { is_expected.to contain_exactly(active_http_integration) }
end
private
def resolve_http_integrations(args = {}, context = { current_user: current_user })
resolve(described_class, obj: project, ctx: context)
end
end
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