Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions app/models/pager_tree/integrations/custom_webhook/v3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ def adapter_action
case custom_response_result.dig("type")&.downcase
when "create"
:create
when "update"
:update
when "acknowledge"
:acknowledge
when "resolve"
Expand All @@ -62,12 +64,46 @@ def adapter_process_create
)
end

# like adapter_process_create, but title/description/incident fall back to
# nil instead of a placeholder, so unset fields don't overwrite the existing alert -
# and no thirdparty_id, since update must never change which alert this is
def adapter_process_update
Alert.new(
title: _update_title,
description: _update_description,
urgency: _urgency,
dedup_keys: _dedup_keys,
incident_severity: _incident_severity,
incident_message: _incident_message,
tags: _tags,
meta: _update_meta,
additional_data: _additional_datums
)
end

def custom_response_processed_log_data
_custom_response_processed_log_data || {}
end

private

def _update_title
custom_response_result.dig("title")&.to_s&.presence
end

def _update_description
custom_response_result.dig("description")&.to_s&.presence
end

# PagerTree::Integrations::Alert#incident always defaults to false, so
# "incident" is carried in meta instead - the only way to tell "not provided"
# (leave alert.incident untouched) apart from "explicitly false"
# (_incident is already nil when "incident" is absent - ActiveModel::Type::Boolean casts nil to nil)
def _update_meta
incident = _incident
incident.nil? ? _meta : _meta.merge("incident" => incident)
end

def _custom_response
return {} unless adapter_incoming_deferred_request.present?

Expand Down
68 changes: 68 additions & 0 deletions app/models/pager_tree/integrations/email/v3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ def adapter_action
case custom_response_result.dig("type")&.downcase
when "create"
:create
when "update"
:update
when "acknowledge"
:acknowledge
when "resolve"
Expand Down Expand Up @@ -129,8 +131,74 @@ def adapter_process_create
end
end

# only reachable via the custom template (adapter_action only returns :update
# there) - fields the rule doesn't return come back nil/empty so an update
# payload can't clobber alert data it didn't mean to touch, and there's no
# thirdparty_id, since update must never change which alert this is
def adapter_process_update
Alert.new(
title: _update_title,
description: _update_description,
urgency: _update_urgency,
dedup_keys: _update_dedup_keys,
incident_severity: _incident_severity,
incident_message: _incident_message,
tags: _tags,
meta: _update_meta,
additional_data: _update_additional_datums
)
end

private

def _update_title
custom_response_result.dig("title")&.to_s&.presence
end

def _update_description
custom_response_result.dig("description")&.to_s&.presence
end

# unlike _urgency, doesn't fall back to the integration's default urgency
def _update_urgency
custom_response_result.dig("urgency")&.to_s&.presence
end

# unlike _dedup_keys, doesn't fall back to thread references - those were
# already applied when the alert was created
def _update_dedup_keys
Array(custom_response_result.dig("dedup_keys")).compact_blank.map(&:to_s).uniq.map { |x| "#{prefix_id}_#{x}" }
end

# PagerTree::Integrations::Alert#incident always defaults to false, so
# "incident" is carried in meta instead - the only way to tell "not provided"
# (leave alert.incident untouched) apart from "explicitly false"
# (_incident is already nil when "incident" is absent - ActiveModel::Type::Boolean casts nil to nil)
def _update_meta
incident = _incident
incident.nil? ? _meta : _meta.merge("incident" => incident)
end

# unlike _additional_datums, doesn't fall back to the From/To/CCs default -
# those were already applied when the alert was created
def _update_additional_datums
items = custom_response_result.dig("additional_data") || []
items = [items] unless items.is_a?(Array)

items.each_with_object([]) do |ad, result|
next unless ad.is_a?(Hash)

format = ad["format"].to_s
next unless PagerTree::Integrations::AdditionalDatum::FORMATS.include?(format)

result << AdditionalDatum.new(
format: format,
label: ad["label"].to_s.presence || "Untitled",
value: ad["value"]
)
end
end

def _custom_response
return @_custom_response ||= {} unless custom_definition?

Expand Down
3 changes: 3 additions & 0 deletions app/models/pager_tree/integrations/integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ def adapter_action
def adapter_process_create
end

def adapter_process_update
end
Comment thread
armiiller marked this conversation as resolved.

def adapter_process_other
end

Expand Down
33 changes: 33 additions & 0 deletions app/models/pager_tree/integrations/webhook/v3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ def adapter_action
case _adapter_incoming_request_params.dig("event_type").to_s.downcase.strip
when "create"
:create
when "update"
:update
when "acknowledge"
:acknowledge
when "resolve"
Expand All @@ -64,8 +66,39 @@ def adapter_process_create
)
end

# Like adapter_process_create, but omits thirdparty_id and does not apply adapter-level
# fallbacks (so missing scalar fields remain nil). Note: array fields still default to
# [] in PagerTree::Integrations::Alert.
def adapter_process_update
Alert.new(
title: _title,
description: _description,
urgency: _urgency,
dedup_keys: _dedup_keys,
incident_severity: _incident_severity,
incident_message: _incident_message,
tags: _tags,
meta: _update_meta,
additional_data: _additional_datums
)
end

private

# PagerTree::Integrations::Alert#incident always defaults to false, so
# "incident" is carried in meta instead - the only way to tell "not provided"
# (leave alert.incident untouched) apart from "explicitly false"
def _incident_update
meta = _adapter_incoming_request_params.dig("meta")
return nil unless meta.is_a?(Hash) && meta.key?("incident")
ActiveModel::Type::Boolean.new.cast(meta["incident"])
end
Comment thread
armiiller marked this conversation as resolved.

def _update_meta
incident = _incident_update
incident.nil? ? _meta : _meta.merge("incident" => incident)
end

def _title
_adapter_incoming_request_params.dig("title")
end
Expand Down
35 changes: 35 additions & 0 deletions test/models/pager_tree/integrations/custom_webhook/v3_test.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "test_helper"
require "ostruct"

module PagerTree::Integrations
class CustomWebhook::V3Test < ActiveSupport::TestCase
Expand Down Expand Up @@ -36,6 +37,13 @@ class CustomWebhook::V3Test < ActiveSupport::TestCase
label: Triggered At
value: "{{log.data.alertDateTime}}"

- match:
log.data.alertTypeFriendlyName: { $regex: "^degraded$", $options: "i" }
actions:
- type: update
urgency: "{{log.data.urgency}}"
thirdparty_id: "{{log.data.monitorID}}"

- match:
log.data.alertTypeFriendlyName: { $regex: "^pending$", $options: "i" }
actions:
Expand Down Expand Up @@ -63,6 +71,9 @@ class CustomWebhook::V3Test < ActiveSupport::TestCase
"alertDateTime" => 1733126400
}.with_indifferent_access

@update_request = @create_request.deep_dup
@update_request["alertTypeFriendlyName"] = "Degraded"

@acknowledge_request = @create_request.deep_dup
@acknowledge_request["alertTypeFriendlyName"] = "Pending"

Expand Down Expand Up @@ -111,6 +122,13 @@ class CustomWebhook::V3Test < ActiveSupport::TestCase
assert_equal :acknowledge, @integration.adapter_action
end
end
test "adapter_action_update" do
VCR.use_cassette("custom_webhook_v3_adapter_action_update") do
@integration.adapter_incoming_request_params = @update_request
@integration.adapter_incoming_deferred_request.body = @update_request
assert_equal :update, @integration.adapter_action
end
end
test "adapter_action_resolve" do
VCR.use_cassette("custom_webhook_v3_adapter_action_resolve") do
@integration.adapter_incoming_request_params = @resolve_request
Expand Down Expand Up @@ -163,5 +181,22 @@ class CustomWebhook::V3Test < ActiveSupport::TestCase
assert_equal true_alert.as_json, @integration.adapter_process_create.as_json
end
end

test "adapter_process_update only carries fields the service actually returned" do
VCR.use_cassette("custom_webhook_v3_adapter_process_update") do
@integration.adapter_incoming_request_params = @update_request

true_alert = Alert.new(
urgency: "high",
meta: {
"incident" => false
}
)

result = @integration.adapter_process_update
assert_equal true_alert.as_json, result.as_json
assert_nil result.thirdparty_id
end
end
end
end
34 changes: 34 additions & 0 deletions test/models/pager_tree/integrations/email/v3_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class Email::V3Test < ActiveSupport::TestCase
message_id "down-98765@server.com"
end

@update_mail = @down_mail.dup.tap { |m| m.subject = "Server is DEGRADED" }
@ack_mail = @down_mail.dup.tap { |m| m.subject = "Server is PENDING maintenance" }
@resolve_mail = @down_mail.dup.tap { |m| m.subject = "Server is UP again" }
@other_mail = @down_mail.dup.tap { |m| m.subject = "Paused" }
Expand Down Expand Up @@ -107,6 +108,15 @@ class Email::V3Test < ActiveSupport::TestCase
end
end

test "adapter_action_update with custom definition" do
setup_custom_definition

VCR.use_cassette("email_v3_custom_adapter_action_update") do
@integration.adapter_incoming_request_params = {"mail" => @update_mail}
assert_equal :update, @integration.adapter_action
end
end

test "adapter_action_acknowledge with custom definition" do
setup_custom_definition

Expand Down Expand Up @@ -182,6 +192,23 @@ class Email::V3Test < ActiveSupport::TestCase
end
end

test "adapter_process_update only carries fields the rule actually returned" do
setup_custom_definition

VCR.use_cassette("email_v3_custom_adapter_process_update") do
@integration.adapter_incoming_request_params = {"mail" => @update_mail}

expected_alert = Alert.new(
urgency: "high",
meta: {"incident" => false}
)

result = @integration.adapter_process_update
assert_equal expected_alert.as_json, result.as_json
assert_nil result.thirdparty_id
end
end

private

def setup_custom_definition
Expand Down Expand Up @@ -209,6 +236,13 @@ def setup_custom_definition
label: From
value: "{{log.from}}"

- match:
log.subject: { $regex: "degraded", $options: "i" }
actions:
- type: update
urgency: "high"
thirdparty_id: "email-test-123"

- match:
log.subject: { $regex: "pending", $options: "i" }
actions:
Expand Down
Loading
Loading