Commit 1e1214fe authored by Vitali Tatarintev's avatar Vitali Tatarintev Committed by Peter Leitzen

Add custom mapping args to update HTTP integration

Add `payload_example` and `payload_attribute_mapping` arguments
to the update HTTP integration GraphQL mutation.

The feature is available with the GitLab Premium license.
parent f13713e6
...@@ -21,27 +21,20 @@ module Mutations ...@@ -21,27 +21,20 @@ module Mutations
description: 'Whether the integration is receiving alerts.' description: 'Whether the integration is receiving alerts.'
def resolve(args) def resolve(args)
@project = authorized_find!(full_path: args[:project_path]) project = authorized_find!(full_path: args[:project_path])
response ::AlertManagement::HttpIntegrations::CreateService.new( response ::AlertManagement::HttpIntegrations::CreateService.new(
project, project,
current_user, current_user,
http_integration_params(args) http_integration_params(project, args)
).execute ).execute
end end
private private
attr_reader :project
def find_object(full_path:) def find_object(full_path:)
resolve_project(full_path: full_path) resolve_project(full_path: full_path)
end end
# overriden in EE
def http_integration_params(args)
args.slice(:name, :active)
end
end end
end end
end end
......
...@@ -23,7 +23,14 @@ module Mutations ...@@ -23,7 +23,14 @@ module Mutations
errors: result.errors errors: result.errors
} }
end end
# overriden in EE
def http_integration_params(_project, args)
args.slice(:name, :active)
end
end end
end end
end end
end end
Mutations::AlertManagement::HttpIntegration::HttpIntegrationBase.prepend_if_ee('::EE::Mutations::AlertManagement::HttpIntegration::HttpIntegrationBase')
...@@ -24,10 +24,12 @@ module Mutations ...@@ -24,10 +24,12 @@ module Mutations
response ::AlertManagement::HttpIntegrations::UpdateService.new( response ::AlertManagement::HttpIntegrations::UpdateService.new(
integration, integration,
current_user, current_user,
args.slice(:name, :active) http_integration_params(integration.project, args)
).execute ).execute
end end
end end
end end
end end
end end
Mutations::AlertManagement::HttpIntegration::Update.prepend_if_ee('::EE::Mutations::AlertManagement::HttpIntegration::Update')
...@@ -9,7 +9,7 @@ module AlertManagement ...@@ -9,7 +9,7 @@ module AlertManagement
def initialize(integration, current_user, params) def initialize(integration, current_user, params)
@integration = integration @integration = integration
@current_user = current_user @current_user = current_user
@params = params @params = params.with_indifferent_access
end end
def execute def execute
...@@ -17,7 +17,7 @@ module AlertManagement ...@@ -17,7 +17,7 @@ module AlertManagement
params[:token] = nil if params.delete(:regenerate_token) params[:token] = nil if params.delete(:regenerate_token)
if integration.update(params) if integration.update(permitted_params)
success success
else else
error(integration.errors.full_messages.to_sentence) error(integration.errors.full_messages.to_sentence)
...@@ -32,6 +32,15 @@ module AlertManagement ...@@ -32,6 +32,15 @@ module AlertManagement
current_user&.can?(:admin_operations, integration) current_user&.can?(:admin_operations, integration)
end end
def permitted_params
params.slice(*permitted_params_keys)
end
# overriden in EE
def permitted_params_keys
%i[name active token]
end
def error(message) def error(message)
ServiceResponse.error(message: message) ServiceResponse.error(message: message)
end end
...@@ -46,3 +55,5 @@ module AlertManagement ...@@ -46,3 +55,5 @@ module AlertManagement
end end
end end
end end
::AlertManagement::HttpIntegrations::UpdateService.prepend_if_ee('::EE::AlertManagement::HttpIntegrations::UpdateService')
...@@ -11622,6 +11622,16 @@ input HttpIntegrationUpdateInput { ...@@ -11622,6 +11622,16 @@ input HttpIntegrationUpdateInput {
The name of the integration. The name of the integration.
""" """
name: String name: String
"""
The custom mapping of GitLab alert attributes to fields from the payload_example.
"""
payloadAttributeMappings: [AlertManagementPayloadAlertFieldInput!]
"""
The example of an alert payload.
"""
payloadExample: JsonString
} }
""" """
......
...@@ -31574,6 +31574,34 @@ ...@@ -31574,6 +31574,34 @@
}, },
"defaultValue": null "defaultValue": null
}, },
{
"name": "payloadExample",
"description": "The example of an alert payload.",
"type": {
"kind": "SCALAR",
"name": "JsonString",
"ofType": null
},
"defaultValue": null
},
{
"name": "payloadAttributeMappings",
"description": "The custom mapping of GitLab alert attributes to fields from the payload_example.",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "AlertManagementPayloadAlertFieldInput",
"ofType": null
}
}
},
"defaultValue": null
},
{ {
"name": "clientMutationId", "name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.", "description": "A unique identifier for the client performing the mutation.",
...@@ -6,7 +6,6 @@ module EE ...@@ -6,7 +6,6 @@ module EE
module HttpIntegration module HttpIntegration
module Create module Create
extend ActiveSupport::Concern extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
prepended do prepended do
argument :payload_example, ::Types::JsonStringType, argument :payload_example, ::Types::JsonStringType,
...@@ -17,33 +16,6 @@ module EE ...@@ -17,33 +16,6 @@ module EE
required: false, required: false,
description: 'The custom mapping of GitLab alert attributes to fields from the payload_example.' description: 'The custom mapping of GitLab alert attributes to fields from the payload_example.'
end end
private
override :http_integration_params
def http_integration_params(args)
base_args = super(args)
return base_args unless ::Gitlab::AlertManagement.custom_mapping_available?(project)
validate_payload_example!(args[:payload_example])
args.slice(*base_args.keys, :payload_example).merge(
payload_attribute_mapping: payload_attribute_mapping(args[:payload_attribute_mappings])
)
end
def payload_attribute_mapping(mappings)
mappings.each_with_object({}) do |m, h|
h[m.field_name] = { path: m.path, type: m.type, label: m.label }
end
end
def validate_payload_example!(payload_example)
return if ::Gitlab::Utils::DeepSize.new(payload_example).valid?
raise ::Gitlab::Graphql::Errors::ArgumentError, 'payloadExample JSON is too big'
end
end end
end end
end end
......
# frozen_string_literal: true
module EE
module Mutations
module AlertManagement
module HttpIntegration
module HttpIntegrationBase
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
private
def payload_attribute_mapping(mappings)
Array(mappings).each_with_object({}) do |m, h|
h[m.field_name] = { path: m.path, type: m.type, label: m.label }
end
end
def validate_payload_example!(payload_example)
return if ::Gitlab::Utils::DeepSize.new(payload_example).valid?
raise ::Gitlab::Graphql::Errors::ArgumentError, 'payloadExample JSON is too big'
end
override :http_integration_params
def http_integration_params(project, args)
base_args = super(project, args)
return base_args unless ::Gitlab::AlertManagement.custom_mapping_available?(project)
validate_payload_example!(args[:payload_example])
args.slice(*base_args.keys, :payload_example).merge(
payload_attribute_mapping: payload_attribute_mapping(args[:payload_attribute_mappings])
)
end
end
end
end
end
end
# frozen_string_literal: true
module EE
module Mutations
module AlertManagement
module HttpIntegration
module Update
extend ActiveSupport::Concern
prepended do
argument :payload_example, ::Types::JsonStringType,
required: false,
description: 'The example of an alert payload.'
argument :payload_attribute_mappings, [::Types::AlertManagement::PayloadAlertFieldInputType],
required: false,
description: 'The custom mapping of GitLab alert attributes to fields from the payload_example.'
end
end
end
end
end
end
# frozen_string_literal: true
module EE
module AlertManagement
module HttpIntegrations
module UpdateService
extend ::Gitlab::Utils::Override
private
override :permitted_params_keys
def permitted_params_keys
return super unless ::Gitlab::AlertManagement.custom_mapping_available?(integration.project)
super + %i[payload_example payload_attribute_mapping]
end
end
end
end
end
...@@ -116,60 +116,6 @@ RSpec.describe 'Creating a new HTTP Integration' do ...@@ -116,60 +116,6 @@ RSpec.describe 'Creating a new HTTP Integration' do
it_behaves_like 'ignoring the custom mapping' it_behaves_like 'ignoring the custom mapping'
end end
context 'with invalid payloadExample attribute' do it_behaves_like 'validating the payload_example'
let(:payload_example) { 'not a JSON' } it_behaves_like 'validating the payload_attribute_mappings'
it 'responds with errors' do
post_graphql_mutation(mutation, current_user: current_user)
expect_graphql_errors_to_include(/was provided invalid value for payloadExample \(Invalid JSON string/)
end
end
context 'with invalid payloadAttributeMapping attribute does not contain fieldName' do
let(:payload_attribute_mappings) do
[{ path: %w[alert name], type: 'STRING' }]
end
it 'responds with errors' do
post_graphql_mutation(mutation, current_user: current_user)
expect_graphql_errors_to_include(/was provided invalid value for payloadAttributeMappings\.0\.fieldName \(Expected value to not be null/)
end
end
context 'with invalid payloadAttributeMapping attribute does not contain path' do
let(:payload_attribute_mappings) do
[{ fieldName: 'TITLE', type: 'STRING' }]
end
it 'responds with errors' do
post_graphql_mutation(mutation, current_user: current_user)
expect_graphql_errors_to_include(/was provided invalid value for payloadAttributeMappings\.0\.path \(Expected value to not be null/)
end
end
context 'with invalid payloadAttributeMapping attribute does not contain type' do
let(:payload_attribute_mappings) do
[{ fieldName: 'TITLE', path: %w[alert name] }]
end
it 'responds with errors' do
post_graphql_mutation(mutation, current_user: current_user)
expect_graphql_errors_to_include(/was provided invalid value for payloadAttributeMappings\.0\.type \(Expected value to not be null/)
end
end
it 'validates the payload_example size' do
allow(::Gitlab::Utils::DeepSize)
.to receive(:new)
.with(Gitlab::Json.parse(payload_example))
.and_return(double(valid?: false))
post_graphql_mutation(mutation, current_user: current_user)
expect_graphql_errors_to_include(/payloadExample JSON is too big/)
end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Updating an existing HTTP Integration' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:integration) { create(:alert_management_http_integration, project: project) }
let(:payload_example) do
{
'alert' => { 'name' => 'Test alert' },
'started_at' => Time.current.strftime('%d %B %Y, %-l:%M%p (%Z)')
}.to_json
end
let(:payload_attribute_mappings) do
[
{ fieldName: 'TITLE', path: %w[alert name], type: 'STRING' },
{ fieldName: 'START_TIME', path: %w[started_at], type: 'DATETIME', label: 'Start time' }
]
end
let(:mutation) do
variables = {
id: GitlabSchema.id_from_object(integration).to_s,
name: 'Modified Name',
active: false,
payload_example: payload_example,
payload_attribute_mappings: payload_attribute_mappings
}
graphql_mutation(:http_integration_update, variables) do
<<~QL
clientMutationId
errors
integration {
id
name
active
url
}
QL
end
end
let(:mutation_response) { graphql_mutation_response(:http_integration_update) }
shared_examples 'ignoring the custom mapping' do
it 'updates integration without the custom mapping params' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(integration.payload_example).to eq({})
expect(integration.payload_attribute_mapping).to eq({})
end
end
before do
project.add_maintainer(current_user)
stub_licensed_features(multiple_alert_http_integrations: true)
stub_feature_flags(multiple_http_integrations_custom_mapping: project)
end
it_behaves_like 'updating an existing HTTP integration'
it_behaves_like 'validating the payload_example'
it_behaves_like 'validating the payload_attribute_mappings'
context 'with the custom mappings feature unavailable' do
before do
stub_licensed_features(multiple_alert_http_integrations: false)
end
it_behaves_like 'ignoring the custom mapping'
end
context 'with multiple_http_integrations_custom_mapping feature flag disabled' do
before do
stub_feature_flags(multiple_http_integrations_custom_mapping: false)
end
it_behaves_like 'ignoring the custom mapping'
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe AlertManagement::HttpIntegrations::UpdateService do
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:project) { create(:project) }
let_it_be_with_reload(:integration) { create(:alert_management_http_integration, :inactive, project: project, name: 'Old Name') }
let(:payload_example) do
{
'alert' => { 'name' => 'Test alert' },
'started_at' => Time.current.strftime('%d %B %Y, %-l:%M%p (%Z)')
}
end
let(:payload_attribute_mapping) do
{
'title' => { 'path' => %w[alert name], 'type' => 'string' },
'start_time' => { 'path' => %w[started_at], 'type' => 'datetime' }
}
end
let(:params) do
{
name: 'New name',
payload_example: payload_example,
payload_attribute_mapping: payload_attribute_mapping
}
end
let(:service) { described_class.new(integration, user, params) }
before do
project.add_maintainer(user)
end
describe '#execute' do
shared_examples 'ignoring the custom mapping' do
it 'creates integration without the custom mapping params' do
expect(response).to be_success
integration = response.payload[:integration]
expect(integration).to be_a(::AlertManagement::HttpIntegration)
expect(integration.payload_example).to eq({})
expect(integration.payload_attribute_mapping).to eq({})
end
end
subject(:response) { service.execute }
context 'with multiple HTTP integrations feature available' do
before do
stub_licensed_features(multiple_alert_http_integrations: true)
end
context 'with multiple_http_integrations_custom_mapping feature flag enabled' do
before do
stub_feature_flags(multiple_http_integrations_custom_mapping: project)
end
it 'successfully creates a new integration with the custom mappings' do
expect(response).to be_success
integration = response.payload[:integration]
expect(integration).to be_a(::AlertManagement::HttpIntegration)
expect(integration.name).to eq('New name')
expect(integration.payload_example).to eq(payload_example)
expect(integration.payload_attribute_mapping).to eq(payload_attribute_mapping)
end
end
context 'with multiple_http_integrations_custom_mapping feature flag disabled' do
before do
stub_feature_flags(multiple_http_integrations_custom_mapping: false)
end
it_behaves_like 'ignoring the custom mapping'
end
end
it_behaves_like 'ignoring the custom mapping'
end
end
...@@ -5,7 +5,7 @@ require 'spec_helper' ...@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'Updating an existing HTTP Integration' do RSpec.describe 'Updating an existing HTTP Integration' do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:integration) { create(:alert_management_http_integration, project: project) } let_it_be(:integration) { create(:alert_management_http_integration, project: project) }
...@@ -32,18 +32,8 @@ RSpec.describe 'Updating an existing HTTP Integration' do ...@@ -32,18 +32,8 @@ RSpec.describe 'Updating an existing HTTP Integration' do
let(:mutation_response) { graphql_mutation_response(:http_integration_update) } let(:mutation_response) { graphql_mutation_response(:http_integration_update) }
before do before do
project.add_maintainer(user) project.add_maintainer(current_user)
end end
it 'updates the integration' do it_behaves_like 'updating an existing HTTP integration'
post_graphql_mutation(mutation, current_user: user)
integration_response = mutation_response['integration']
expect(response).to have_gitlab_http_status(:success)
expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s)
expect(integration_response['name']).to eq('Modified Name')
expect(integration_response['active']).to be_falsey
expect(integration_response['url']).to include('modified-name')
end
end end
...@@ -17,3 +17,78 @@ RSpec.shared_examples 'creating a new HTTP integration' do ...@@ -17,3 +17,78 @@ RSpec.shared_examples 'creating a new HTTP integration' do
expect(integration_response['apiUrl']).to eq(nil) expect(integration_response['apiUrl']).to eq(nil)
end end
end end
RSpec.shared_examples 'updating an existing HTTP integration' do
it 'updates the integration' do
post_graphql_mutation(mutation, current_user: current_user)
integration_response = mutation_response['integration']
expect(response).to have_gitlab_http_status(:success)
expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s)
expect(integration_response['name']).to eq('Modified Name')
expect(integration_response['active']).to be_falsey
expect(integration_response['url']).to include('modified-name')
end
end
RSpec.shared_examples 'validating the payload_example' do
context 'with invalid payloadExample attribute' do
let(:payload_example) { 'not a JSON' }
it 'responds with errors' do
post_graphql_mutation(mutation, current_user: current_user)
expect_graphql_errors_to_include(/was provided invalid value for payloadExample \(Invalid JSON string/)
end
end
it 'validates the payload_example size' do
allow(::Gitlab::Utils::DeepSize)
.to receive(:new)
.with(Gitlab::Json.parse(payload_example))
.and_return(double(valid?: false))
post_graphql_mutation(mutation, current_user: current_user)
expect_graphql_errors_to_include(/payloadExample JSON is too big/)
end
end
RSpec.shared_examples 'validating the payload_attribute_mappings' do
context 'with invalid payloadAttributeMapping attribute does not contain fieldName' do
let(:payload_attribute_mappings) do
[{ path: %w[alert name], type: 'STRING' }]
end
it 'responds with errors' do
post_graphql_mutation(mutation, current_user: current_user)
expect_graphql_errors_to_include(/was provided invalid value for payloadAttributeMappings\.0\.fieldName \(Expected value to not be null/)
end
end
context 'with invalid payloadAttributeMapping attribute does not contain path' do
let(:payload_attribute_mappings) do
[{ fieldName: 'TITLE', type: 'STRING' }]
end
it 'responds with errors' do
post_graphql_mutation(mutation, current_user: current_user)
expect_graphql_errors_to_include(/was provided invalid value for payloadAttributeMappings\.0\.path \(Expected value to not be null/)
end
end
context 'with invalid payloadAttributeMapping attribute does not contain type' do
let(:payload_attribute_mappings) do
[{ fieldName: 'TITLE', path: %w[alert name] }]
end
it 'responds with errors' do
post_graphql_mutation(mutation, current_user: current_user)
expect_graphql_errors_to_include(/was provided invalid value for payloadAttributeMappings\.0\.type \(Expected value to not be null/)
end
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