Commit 996bd788 authored by Peter Leitzen's avatar Peter Leitzen Committed by Vitali Tatarintev

Extract alert fields from the payload example

Implement service and GraphQL for extracting fields from example payload
parent 5a2dc95f
......@@ -742,6 +742,26 @@ enum AlertManagementIntegrationType {
PROMETHEUS
}
"""
Parsed field from an alert used for custom mappings
"""
type AlertManagementPayloadAlertField {
"""
Human-readable label of the payload path.
"""
label: String
"""
Path to value inside payload JSON.
"""
path: [String!]
"""
Type of the parsed value.
"""
type: AlertManagementPayloadAlertFieldType
}
"""
Field that are available while modifying the custom mapping attributes for an HTTP integration
"""
......@@ -18308,6 +18328,16 @@ type Project {
last: Int
): AlertManagementIntegrationConnection
"""
Extract alert fields from payload for custom mapping
"""
alertManagementPayloadFields(
"""
Sample payload for extracting alert fields for custom mappings.
"""
payloadExample: String!
): [AlertManagementPayloadAlertField!]
"""
If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge
requests of the project can also be merged with skipped jobs
......
......@@ -1909,6 +1909,69 @@
],
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "AlertManagementPayloadAlertField",
"description": "Parsed field from an alert used for custom mappings",
"fields": [
{
"name": "label",
"description": "Human-readable label of the payload path.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "path",
"description": "Path to value inside payload JSON.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "type",
"description": "Type of the parsed value.",
"args": [
],
"type": {
"kind": "ENUM",
"name": "AlertManagementPayloadAlertFieldType",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "AlertManagementPayloadAlertFieldInput",
......@@ -53968,6 +54031,41 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "alertManagementPayloadFields",
"description": "Extract alert fields from payload for custom mapping",
"args": [
{
"name": "payloadExample",
"description": "Sample payload for extracting alert fields for custom mappings.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "AlertManagementPayloadAlertField",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "allowMergeOnSkippedPipeline",
"description": "If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of the project can also be merged with skipped jobs",
......@@ -132,6 +132,16 @@ An endpoint and credentials used to accept alerts for a project.
| `type` | AlertManagementIntegrationType! | Type of integration. |
| `url` | String | Endpoint which accepts alert notifications. |
### AlertManagementPayloadAlertField
Parsed field from an alert used for custom mappings.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `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
An endpoint and credentials used to accept Prometheus alerts for a project.
......@@ -2767,6 +2777,7 @@ Autogenerated return type of PipelineRetry.
| `alertManagementAlertStatusCounts` | AlertManagementAlertStatusCountsType | Counts of alerts by status for the project |
| `alertManagementAlerts` | AlertManagementAlertConnection | Alert Management alerts of the project |
| `alertManagementIntegrations` | AlertManagementIntegrationConnection | Integrations which can receive alerts for the project |
| `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 |
| `archived` | Boolean | Indicates the archived status of the project |
| `autocloseReferencedIssues` | Boolean | Indicates if issues referenced by merge requests and commits within the default branch are closed automatically |
......
......@@ -116,6 +116,12 @@ module EE
description: 'Code coverage summary associated with the project',
resolver: ::Resolvers::Ci::CodeCoverageSummaryResolver
field :alert_management_payload_fields,
[::Types::AlertManagement::PayloadAlertFieldType],
null: true,
description: 'Extract alert fields from payload for custom mapping',
resolver: ::Resolvers::AlertManagement::PayloadAlertFieldResolver
field :incident_management_oncall_schedules,
::Types::IncidentManagement::OncallScheduleType.connection_type,
null: true,
......
# frozen_string_literal: true
module Resolvers
module AlertManagement
class PayloadAlertFieldResolver < BaseResolver
argument :payload_example, GraphQL::STRING_TYPE,
required: true,
description: 'Sample payload for extracting alert fields for custom mappings.'
type ::Types::AlertManagement::PayloadAlertFieldType, null: true
alias_method :project, :object
def resolve(payload_example:)
params = { payload: payload_example }
result = ::AlertManagement::ExtractAlertPayloadFieldsService
.new(container: project, current_user: current_user, params: params)
.execute
raise GraphQL::ExecutionError, result.message if result.error?
result.payload[:payload_alert_fields]
end
end
end
end
# frozen_string_literal: true
module Types
module AlertManagement
class PayloadAlertFieldType < BaseObject
graphql_name 'AlertManagementPayloadAlertField'
description 'Parsed field from an alert used for custom mappings'
authorize :read_alert_management_alert
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
# frozen_string_literal: true
module AlertManagement
class AlertPayloadFieldPolicy < ::BasePolicy
delegate { @subject.project }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'parse alert payload fields' do
include GraphqlHelpers
let_it_be_with_refind(:project) { create(:project) }
let_it_be(:maintainer) { create(:user) }
let_it_be(:developer) { create(:user) }
let(:current_user) { maintainer }
let(:license) { true }
let(:feature_flag) { true }
let(:payload) do
{
'title' => 'value',
'started_at' => '2020-01-02 04:05:06',
'nested' => {
'key' => 'string'
},
'arr' => %w[one two]
}
end
let(:payload_json) { Gitlab::Json.generate(payload) }
let(:arguments) { { payloadExample: payload_json } }
let(:fields) { all_graphql_fields_for('AlertManagementPayloadAlertField') }
let(:query) do
graphql_query_for(
'project',
{ 'fullPath' => project.full_path },
query_graphql_field('alertManagementPayloadFields', arguments, fields)
)
end
let(:parsed_fields) do
graphql_data.dig('project', 'alertManagementPayloadFields')
end
before_all do
project.add_developer(developer)
project.add_maintainer(maintainer)
end
before do
stub_licensed_features(multiple_alert_http_integrations: license)
stub_feature_flags(multiple_http_integrations_custom_mapping: feature_flag)
post_graphql(query, current_user: current_user)
end
it_behaves_like 'a working graphql query'
specify do
expect(parsed_fields).to eq([
{ 'path' => %w[title], 'label' => 'Title', 'type' => 'STRING' },
{ 'path' => %w[started_at], 'label' => 'Started at', 'type' => 'DATETIME' },
{ 'path' => %w[nested key], 'label' => 'Key', 'type' => 'STRING' },
{ 'path' => %w[arr], 'label' => 'Arr', 'type' => 'ARRAY' }
])
end
shared_examples 'query with error' do |error_message|
it 'returns an error', :aggregate_failures do
expect(parsed_fields).to be_nil
expect(graphql_errors).to include(a_hash_including('message' => error_message))
end
end
context 'without user permission' do
let(:current_user) { developer }
it_behaves_like 'query with error', 'Insufficient permissions'
end
context 'without license' do
let(:license) { false }
it_behaves_like 'query with error', 'Feature not available'
end
context 'with invalid payload JSON' do
let(:payload_json) { 'invalid json' }
it_behaves_like 'query with error', 'Failed to parse payload'
end
context 'with non-Hash JSON' do
let(:payload_json) { '1' }
it_behaves_like 'query with error', 'Failed to parse payload'
end
context 'without feature flag' do
let(:feature_flag) { false }
it_behaves_like 'query with error', 'Feature not available'
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