pager_tree-integrations 1.1.6 → 1.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/pager_tree/integrations/live_call_routing/twilio/v3_controller.rb +20 -0
  3. data/app/jobs/pager_tree/integrations/live_call_routing/twilio/v3/max_wait_time_job.rb +13 -0
  4. data/app/models/pager_tree/integrations/additional_datum.rb +2 -2
  5. data/app/models/pager_tree/integrations/alert.rb +2 -2
  6. data/app/models/pager_tree/integrations/aws_cloudwatch/v3.rb +15 -5
  7. data/app/models/pager_tree/integrations/boolean_store_accessor.rb +2 -2
  8. data/app/models/pager_tree/integrations/channel/hangouts/v3.rb +23 -2
  9. data/app/models/pager_tree/integrations/channel/mattermost/v3.rb +4 -0
  10. data/app/models/pager_tree/integrations/channel/microsoft_teams/v3.rb +74 -29
  11. data/app/models/pager_tree/integrations/channel/microsoft_teams/v4.rb +261 -0
  12. data/app/models/pager_tree/integrations/channel/slack/v3.rb +30 -3
  13. data/app/models/pager_tree/integrations/cloudflare/v3.rb +75 -0
  14. data/app/models/pager_tree/integrations/custom_webhook/v3.rb +206 -0
  15. data/app/models/pager_tree/integrations/custom_webhook/v3_examples/datadog.json +12 -0
  16. data/app/models/pager_tree/integrations/custom_webhook/v3_examples/datadog.yaml +30 -0
  17. data/app/models/pager_tree/integrations/custom_webhook/v3_examples/generic_rules_definition.json +9 -0
  18. data/app/models/pager_tree/integrations/custom_webhook/v3_examples/generic_rules_example.yaml +27 -0
  19. data/app/models/pager_tree/integrations/custom_webhook/v3_examples/grafana.json +25 -0
  20. data/app/models/pager_tree/integrations/custom_webhook/v3_examples/grafana.yaml +33 -0
  21. data/app/models/pager_tree/integrations/custom_webhook/v3_examples/new_relic.json +40 -0
  22. data/app/models/pager_tree/integrations/custom_webhook/v3_examples/new_relic.yaml +32 -0
  23. data/app/models/pager_tree/integrations/custom_webhook/v3_examples/pingdom.json +30 -0
  24. data/app/models/pager_tree/integrations/custom_webhook/v3_examples/pingdom.yaml +23 -0
  25. data/app/models/pager_tree/integrations/custom_webhook/v3_examples/sns_cloudwatch.json +12 -0
  26. data/app/models/pager_tree/integrations/custom_webhook/v3_examples/sns_cloudwatch.yaml +48 -0
  27. data/app/models/pager_tree/integrations/custom_webhook/v3_examples/sns_health_event.json +21 -0
  28. data/app/models/pager_tree/integrations/custom_webhook/v3_examples/sns_health_event.yaml +43 -0
  29. data/app/models/pager_tree/integrations/custom_webhook/v3_examples/sns_lambda_monitor.json +31 -0
  30. data/app/models/pager_tree/integrations/custom_webhook/v3_examples/sns_lambda_monitor.yaml +24 -0
  31. data/app/models/pager_tree/integrations/datadog/v3.rb +10 -3
  32. data/app/models/pager_tree/integrations/dynatrace/v3.rb +67 -0
  33. data/app/models/pager_tree/integrations/echoes_hq/v3.rb +138 -0
  34. data/app/models/pager_tree/integrations/elast_alert/v3.rb +24 -0
  35. data/app/models/pager_tree/integrations/email/v3.rb +272 -22
  36. data/app/models/pager_tree/integrations/email/v3_examples/example.yml +24 -0
  37. data/app/models/pager_tree/integrations/form/v3.rb +15 -3
  38. data/app/models/pager_tree/integrations/freshdesk/v3.rb +7 -5
  39. data/app/models/pager_tree/integrations/freshservice/v3.rb +7 -5
  40. data/app/models/pager_tree/integrations/grafana/v3.rb +2 -2
  41. data/app/models/pager_tree/integrations/hetrix_tools/v3.rb +160 -0
  42. data/app/models/pager_tree/integrations/honeybadger/v3.rb +1 -1
  43. data/app/models/pager_tree/integrations/hydrozen/v3.rb +6 -0
  44. data/app/models/pager_tree/integrations/integration.rb +52 -9
  45. data/app/models/pager_tree/integrations/jira_server/v3.rb +12 -2
  46. data/app/models/pager_tree/integrations/kapacitor/v3.rb +1 -1
  47. data/app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb +257 -22
  48. data/app/models/pager_tree/integrations/logic_monitor/v3.rb +193 -3
  49. data/app/models/pager_tree/integrations/mattermost/outgoing_webhook/v3.rb +1 -1
  50. data/app/models/pager_tree/integrations/meta/workplace/v3.rb +230 -0
  51. data/app/models/pager_tree/integrations/new_relic/v3.rb +9 -5
  52. data/app/models/pager_tree/integrations/outgoing_event.rb +8 -0
  53. data/app/models/pager_tree/integrations/outgoing_webhook/v3.rb +108 -13
  54. data/app/models/pager_tree/integrations/outgoing_webhook_delivery/hook_relay.rb +43 -33
  55. data/app/models/pager_tree/integrations/outgoing_webhook_delivery.rb +6 -5
  56. data/app/models/pager_tree/integrations/prtg/v3.rb +2 -1
  57. data/app/models/pager_tree/integrations/pulsetic/v3.rb +70 -0
  58. data/app/models/pager_tree/integrations/scom/pagertree.ps1 +153 -0
  59. data/app/models/pager_tree/integrations/scom/v3.rb +58 -0
  60. data/app/models/pager_tree/integrations/sentry/v3.rb +477 -0
  61. data/app/models/pager_tree/integrations/server_guard24/v3.rb +1 -1
  62. data/app/models/pager_tree/integrations/sixty_six_uptime/v3.rb +118 -0
  63. data/app/models/pager_tree/integrations/slack/webhook/v3.rb +1 -1
  64. data/app/models/pager_tree/integrations/solar_winds/v3.rb +66 -2
  65. data/app/models/pager_tree/integrations/status_gator/v3.rb +69 -0
  66. data/app/models/pager_tree/integrations/uptime_kuma/v3.rb +76 -0
  67. data/app/models/pager_tree/integrations/uptime_robot/v3.rb +6 -0
  68. data/app/models/pager_tree/integrations/webhook/v3.rb +25 -7
  69. data/app/models/pager_tree/integrations/zabbix/v3.rb +95 -0
  70. data/app/models/pager_tree/integrations/zendesk/v3.rb +2 -0
  71. data/app/views/pager_tree/integrations/channel/hangouts/v3/_form_options.html.erb +7 -0
  72. data/app/views/pager_tree/integrations/channel/hangouts/v3/_show_options.html.erb +10 -0
  73. data/app/views/pager_tree/integrations/channel/microsoft_teams/v3/_form_options.html.erb +7 -0
  74. data/app/views/pager_tree/integrations/channel/microsoft_teams/v3/_show_options.html.erb +14 -0
  75. data/app/views/pager_tree/integrations/channel/microsoft_teams/v4/_form_options.html.erb +41 -0
  76. data/app/views/pager_tree/integrations/channel/microsoft_teams/v4/_show_options.html.erb +54 -0
  77. data/app/views/pager_tree/integrations/channel/slack/v3/_form_options.html.erb +6 -3
  78. data/app/views/pager_tree/integrations/channel/slack/v3/_show_options.html.erb +1 -1
  79. data/app/views/pager_tree/integrations/cloudflare/v3/_form_options.html.erb +7 -0
  80. data/app/views/pager_tree/integrations/cloudflare/v3/_show_options.html.erb +12 -0
  81. data/app/views/pager_tree/integrations/custom_webhook/v3/_form_options.html.erb +8 -0
  82. data/app/views/pager_tree/integrations/custom_webhook/v3/_show_options.html.erb +0 -0
  83. data/app/views/pager_tree/integrations/datadog/v3/_form_options.html.erb +7 -0
  84. data/app/views/pager_tree/integrations/datadog/v3/_show_options.html.erb +12 -0
  85. data/app/views/pager_tree/integrations/dynatrace/v3/_form_options.html.erb +0 -0
  86. data/app/views/pager_tree/integrations/dynatrace/v3/_show_options.html.erb +0 -0
  87. data/app/views/pager_tree/integrations/echoes_hq/v3/_form_options.html.erb +10 -0
  88. data/app/views/pager_tree/integrations/echoes_hq/v3/_show_options.html.erb +12 -0
  89. data/app/views/pager_tree/integrations/email/v3/_form_options.html.erb +41 -11
  90. data/app/views/pager_tree/integrations/email/v3/_show_options.html.erb +18 -0
  91. data/app/views/pager_tree/integrations/form/v3/_form_options.html.erb +1 -5
  92. data/app/views/pager_tree/integrations/hetrix_tools/v3/_form_options.html.erb +7 -0
  93. data/app/views/pager_tree/integrations/hetrix_tools/v3/_show_options.html.erb +12 -0
  94. data/app/views/pager_tree/integrations/hydrozen/v3/_form_options.html.erb +0 -0
  95. data/app/views/pager_tree/integrations/hydrozen/v3/_show_options.html.erb +0 -0
  96. data/app/views/pager_tree/integrations/live_call_routing/twilio/v3/_form_options.html.erb +44 -31
  97. data/app/views/pager_tree/integrations/live_call_routing/twilio/v3/_show_options.html.erb +93 -8
  98. data/app/views/pager_tree/integrations/logic_monitor/v3/_form_options.html.erb +31 -0
  99. data/app/views/pager_tree/integrations/logic_monitor/v3/_show_options.html.erb +60 -0
  100. data/app/views/pager_tree/integrations/meta/workplace/v3/_form_options.html.erb +44 -0
  101. data/app/views/pager_tree/integrations/meta/workplace/v3/_show_options.html.erb +68 -0
  102. data/app/views/pager_tree/integrations/outgoing_webhook/v3/_form_options.html.erb +17 -3
  103. data/app/views/pager_tree/integrations/outgoing_webhook/v3/_show_options.html.erb +20 -0
  104. data/app/views/pager_tree/integrations/pulsetic/v3/_form_options.html.erb +0 -0
  105. data/app/views/pager_tree/integrations/pulsetic/v3/_show_options.html.erb +0 -0
  106. data/app/views/pager_tree/integrations/scom/v3/_form_options.html.erb +0 -0
  107. data/app/views/pager_tree/integrations/scom/v3/_show_options.html.erb +0 -0
  108. data/app/views/pager_tree/integrations/sentry/v3/_form_options.html.erb +9 -0
  109. data/app/views/pager_tree/integrations/sentry/v3/_show_options.html.erb +15 -0
  110. data/app/views/pager_tree/integrations/sixty_six_uptime/v3/_form_options.html.erb +0 -0
  111. data/app/views/pager_tree/integrations/sixty_six_uptime/v3/_show_options.html.erb +0 -0
  112. data/app/views/pager_tree/integrations/solar_winds/v3/_form_options.html.erb +40 -0
  113. data/app/views/pager_tree/integrations/solar_winds/v3/_show_options.html.erb +66 -0
  114. data/app/views/pager_tree/integrations/status_gator/v3/_form_options.html.erb +13 -0
  115. data/app/views/pager_tree/integrations/status_gator/v3/_show_options.html.erb +24 -0
  116. data/app/views/pager_tree/integrations/uptime_kuma/v3/_form_options.html.erb +0 -0
  117. data/app/views/pager_tree/integrations/uptime_kuma/v3/_show_options.html.erb +0 -0
  118. data/app/views/pager_tree/integrations/webhook/v3/_form_options.html.erb +8 -0
  119. data/app/views/pager_tree/integrations/webhook/v3/_show_options.html.erb +15 -0
  120. data/app/views/pager_tree/integrations/zabbix/v3/_form_options.html.erb +7 -0
  121. data/app/views/pager_tree/integrations/zabbix/v3/_show_options.html.erb +12 -0
  122. data/config/locales/en.yml +158 -10
  123. data/config/routes.rb +1 -0
  124. data/lib/pager_tree/integrations/format_converters/yaml_json_converter.rb +67 -0
  125. data/lib/pager_tree/integrations/version.rb +1 -1
  126. data/lib/pager_tree/integrations.rb +11 -3
  127. metadata +84 -8
@@ -1,17 +1,37 @@
1
1
  module PagerTree::Integrations
2
2
  class Email::V3 < Integration
3
+ extend ::PagerTree::Integrations::Env
4
+
5
+ # the source log (if created) - Its what shows on the integration page (different from deferred request)
6
+ attribute :adapter_source_log
7
+
3
8
  OPTIONS = [
4
9
  {key: :allow_spam, type: :boolean, default: false},
5
- {key: :dedup_threads, type: :boolean, default: true}
10
+ {key: :dedup_threads, type: :boolean, default: true},
11
+ {key: :sanitize_level, type: :string, default: "relaxed"},
12
+ {key: :custom_definition, type: :string, default: nil},
13
+ {key: :custom_definition_enabled, type: :boolean, default: false}
6
14
  ]
15
+
7
16
  store_accessor :options, *OPTIONS.map { |x| x[:key] }.map(&:to_s), prefix: "option"
8
17
 
18
+ SANITIZE_LEVELS = ["basic", "default", "relaxed", "relaxed_2", "restricted"]
19
+
9
20
  validates :option_allow_spam, inclusion: {in: [true, false]}
10
21
  validates :option_dedup_threads, inclusion: {in: [true, false]}
22
+ validates :option_sanitize_level, inclusion: {in: SANITIZE_LEVELS}
23
+ validates :option_custom_definition, presence: true, if: ->(record) { record.option_custom_definition_enabled == true }
24
+
25
+ def self.custom_webhook_v3_service_url
26
+ ::PagerTree::Integrations.integration_custom_webhook_v3_service_url.presence ||
27
+ find_value_by_name("integration_custom_webhook_v3", "service_url")
28
+ end
11
29
 
12
30
  after_initialize do
13
31
  self.option_allow_spam = false if option_allow_spam.nil?
14
32
  self.option_dedup_threads = true if option_dedup_threads.nil?
33
+ self.option_sanitize_level = "relaxed" if option_sanitize_level.nil?
34
+ self.option_custom_definition_enabled = false if option_custom_definition_enabled.nil?
15
35
  end
16
36
 
17
37
  # SPECIAL: override integration endpoint
@@ -23,7 +43,24 @@ module PagerTree::Integrations
23
43
  postfix = "_tst" if Rails.env.test?
24
44
  postfix = "_dev" if Rails.env.development?
25
45
 
26
- "#{inbox}#{postfix}+#{id}@#{domain}"
46
+ if Rails.env.production?
47
+ if created_at&.after?(DateTime.parse("2023-09-01"))
48
+ # new emails
49
+ # int_abc123@domain.com
50
+ "#{(v == 3) ? prefix_id : id}@#{domain}"
51
+ else
52
+ # legacy emails
53
+ # a+int_abc123@domain.com
54
+ "#{inbox}#{postfix}+#{(v == 3) ? prefix_id : id}@#{domain}"
55
+ end
56
+ else
57
+ # staging, test, development
58
+ "#{inbox}#{postfix}+#{(v == 3) ? prefix_id : id}@#{domain}"
59
+ end
60
+ end
61
+
62
+ def custom_definition?
63
+ option_custom_definition_enabled && option_custom_definition.present?
27
64
  end
28
65
 
29
66
  def adapter_should_block?
@@ -47,23 +84,120 @@ module PagerTree::Integrations
47
84
  end
48
85
 
49
86
  def adapter_action
50
- :create
87
+ if custom_definition?
88
+ case custom_response_result.dig("type")&.downcase
89
+ when "create"
90
+ :create
91
+ when "acknowledge"
92
+ :acknowledge
93
+ when "resolve"
94
+ :resolve
95
+ else
96
+ :other
97
+ end
98
+ else
99
+ :create
100
+ end
51
101
  end
52
102
 
53
103
  def adapter_process_create
54
- Alert.new(
55
- title: _title,
56
- description: _description,
57
- urgency: urgency,
58
- thirdparty_id: _thirdparty_id,
59
- dedup_keys: _dedup_keys,
60
- additional_data: _additional_datums,
61
- attachments: _attachments
62
- )
104
+ if custom_definition?
105
+ Alert.new(
106
+ title: _title,
107
+ description: _description,
108
+ urgency: _urgency,
109
+ thirdparty_id: _thirdparty_id,
110
+ dedup_keys: _dedup_keys,
111
+ incident: _incident,
112
+ incident_severity: _incident_severity,
113
+ incident_message: _incident_message,
114
+ tags: _tags,
115
+ meta: _meta,
116
+ additional_data: _additional_datums,
117
+ attachments: _attachments
118
+ )
119
+ else
120
+ Alert.new(
121
+ title: _title,
122
+ description: _description,
123
+ urgency: urgency,
124
+ thirdparty_id: _thirdparty_id,
125
+ dedup_keys: _dedup_keys,
126
+ additional_data: _additional_datums,
127
+ attachments: _attachments
128
+ )
129
+ end
63
130
  end
64
131
 
65
132
  private
66
133
 
134
+ def _custom_response
135
+ return @_custom_response ||= {} unless custom_definition?
136
+
137
+ @_custom_response ||= begin
138
+ log_hash = {
139
+ subject: _mail.subject,
140
+ body: _body,
141
+ from: _mail.from,
142
+ to: _mail.to
143
+ }
144
+
145
+ body_hash = {
146
+ log: log_hash,
147
+ config: JSON.parse(PagerTree::Integrations::FormatConverters::YamlJsonConverter.convert_to_json(option_custom_definition))
148
+ }
149
+
150
+ response = HTTParty.post(
151
+ self.class.custom_webhook_v3_service_url,
152
+ body: body_hash.to_json,
153
+ headers: {"Content-Type" => "application/json"},
154
+ timeout: 2
155
+ )
156
+
157
+ unless response.success?
158
+ if response.parsed_response.dig("error").present?
159
+ adapter_source_log&.sublog({
160
+ message: "Custom Webhook Service Error:",
161
+ parsed_response: response.parsed_response
162
+ })
163
+ adapter_source_log&.save
164
+ end
165
+ raise "Custom Webhook Service HTTP error: #{response.code} - #{response.message} - #{response.body}"
166
+ end
167
+
168
+ adapter_source_log&.sublog({
169
+ message: "Custom Webhook Service Response:",
170
+ parsed_response: response.parsed_response
171
+ })
172
+ adapter_source_log&.save
173
+
174
+ response.parsed_response
175
+ rescue JSON::ParserError => e
176
+ Rails.logger.error("Custom Webhook YAML to JSON conversion error: #{e.message}")
177
+ adapter_source_log&.sublog("Custom Webhook YAML to JSON conversion error: #{e.message}")
178
+ adapter_source_log&.save
179
+ raise "Invalid YAML configuration: #{e.message}"
180
+ rescue HTTParty::Error, SocketError, Net::OpenTimeout, Net::ReadTimeout => e
181
+ Rails.logger.error("Custom Webhook Service error: #{e.message}")
182
+ adapter_source_log&.sublog("Custom Webhook Service error: #{e.message}")
183
+ adapter_source_log&.save
184
+ raise "Custom Webhook Service error: #{e.message}"
185
+ rescue => e
186
+ Rails.logger.error("Unexpected error in Custom Webhook: #{e.message}")
187
+ adapter_source_log&.sublog("Unexpected error in Custom Webhook: #{e.message}")
188
+ adapter_source_log&.save
189
+ raise e
190
+ end
191
+ end
192
+
193
+ def _custom_response_status
194
+ @_custom_response_status ||= _custom_response.dig("status")
195
+ end
196
+
197
+ def custom_response_result
198
+ @_custom_response_result ||= _custom_response.dig("results")&.first || {}
199
+ end
200
+
67
201
  def _mail
68
202
  @_mail ||= adapter_incoming_request_params.dig("mail")
69
203
  end
@@ -73,28 +207,83 @@ module PagerTree::Integrations
73
207
  end
74
208
 
75
209
  def _thirdparty_id
76
- _mail.message_id
210
+ if custom_definition?
211
+ @_thirdparty_id ||= custom_response_result.dig("thirdparty_id").to_s.presence
212
+ end
213
+
214
+ @_thirdparty_id ||= _mail.message_id || SecureRandom.uuid
77
215
  end
78
216
 
79
217
  def _dedup_keys
80
- keys = [_thirdparty_id]
81
- keys.concat(Array(_mail.references)) if option_dedup_threads
82
- keys
218
+ return @_dedup_keys if @_dedup_keys
219
+
220
+ @_dedup_keys ||= []
221
+
222
+ if option_dedup_threads
223
+ @_dedup_keys.concat(Array(_thirdparty_id))
224
+ @_dedup_keys.concat(Array(_mail.references))
225
+ end
226
+
227
+ if custom_definition?
228
+ @_dedup_keys.concat(Array(custom_response_result.dig("dedup_keys")))
229
+ end
230
+
231
+ # only dedup the references per integration. Customer like sending one email to multiple integration inboxes
232
+ @_dedup_keys = @_dedup_keys.compact_blank.uniq.map { |x| "#{prefix_id}_#{x}" }
83
233
  end
84
234
 
85
235
  def _title
86
- _mail.subject
236
+ if custom_definition?
237
+ @_title ||= custom_response_result.dig("title")&.to_s&.presence
238
+ end
239
+
240
+ @_title ||= _mail.subject.to_s.presence || "Incoming Email - Untitled Alert"
87
241
  end
88
242
 
89
243
  def _description
90
- _body
244
+ if custom_definition?
245
+ @_description ||= custom_response_result.dig("description")&.to_s&.presence
246
+ end
247
+
248
+ @_description ||= _body.to_s
249
+ end
250
+
251
+ def _urgency
252
+ if custom_definition?
253
+ @_urgency ||= custom_response_result.dig("urgency")&.to_s&.presence
254
+ end
255
+
256
+ @_urgency ||= urgency
257
+ end
258
+
259
+ def _incident
260
+ @_incident ||= ActiveModel::Type::Boolean.new.cast(custom_response_result.dig("incident"))
261
+ end
262
+
263
+ def _incident_severity
264
+ custom_response_result.dig("incident_severity")&.to_s&.presence
265
+ end
266
+
267
+ def _incident_message
268
+ custom_response_result.dig("incident_message")&.to_s&.presence
269
+ end
270
+
271
+ def _tags
272
+ tags = custom_response_result.dig("tags")
273
+ tags = tags.split(",") if tags.is_a?(String)
274
+ Array(tags).compact_blank.map(&:to_s).uniq
275
+ end
276
+
277
+ def _meta
278
+ meta = custom_response_result.dig("meta")
279
+ meta.is_a?(Hash) ? meta : {}
91
280
  end
92
281
 
93
282
  def _body
94
283
  return @_body if @_body
95
284
 
96
285
  if _mail.multipart? && _mail.html_part
97
- document = Nokogiri::HTML(_mail.html_part.body.decoded)
286
+ document = Nokogiri::HTML(_mail_body_part_to_utf8(_mail.html_part))
98
287
 
99
288
  _attachments_hash.map do |attachment_hash|
100
289
  attachment = attachment_hash[:original]
@@ -109,9 +298,11 @@ module PagerTree::Integrations
109
298
  end
110
299
  end
111
300
 
112
- @_body = ::Sanitize.document(document, Sanitize::Config::RELAXED)
301
+ @_body = custom_definition? ?
302
+ (document.at_css("body")&.inner_html || document.to_html) :
303
+ ::Sanitize.fragment(document, _sanitize_config)
113
304
  elsif _mail.multipart? && _mail.text_part
114
- @_body = _mail.text_part.body.decoded
305
+ @_body = _mail_body_part_to_utf8(_mail.text_part)
115
306
  else
116
307
  @_body = _mail.decoded
117
308
  end
@@ -119,6 +310,43 @@ module PagerTree::Integrations
119
310
  @_body
120
311
  end
121
312
 
313
+ def _sanitize_config
314
+ case option_sanitize_level
315
+ when "basic" then Sanitize::Config::BASIC
316
+ when "default" then Sanitize::Config::DEFAULT
317
+ when "relaxed" then Sanitize::Config::RELAXED
318
+ when "restricted" then Sanitize::Config::RESTRICTED
319
+ when "relaxed_2"
320
+ Sanitize::Config.merge(Sanitize::Config::RELAXED, elements: Sanitize::Config::RELAXED[:elements].excluding("style"))
321
+ end
322
+ end
323
+
324
+ # Encodings can cause lots of issues, so we try to convert to UTF-8
325
+ # https://github.com/mikel/mail#encodings
326
+ # https://stackoverflow.com/a/15818886/2903189
327
+ def _mail_body_part_to_utf8(part_to_use)
328
+ # get the message body without the header information
329
+ body = part_to_use.body.decoded
330
+
331
+ if body.encoding.name != "UTF-8"
332
+ # readout the encoding (charset) of the part
333
+ encoding = part_to_use.content_type_parameters["charset"] if part_to_use.content_type_parameters
334
+
335
+ # and convert it to UTF-8
336
+ if encoding
337
+ body = body.force_encoding(encoding).encode("UTF-8")
338
+ else
339
+ raise StandardError.new "Unknown encoding for mail body part"
340
+ end
341
+ end
342
+
343
+ # return the body
344
+ body
345
+ rescue
346
+ # if something goes wrong, just return the original body with characters replaced
347
+ body.encode("UTF-8", "binary", invalid: :replace, undef: :replace, replace: "?")
348
+ end
349
+
122
350
  def _attachments_hash
123
351
  @_attachments_hash ||= _mail.attachments.map do |attachment|
124
352
  blob = ActiveStorage::Blob.create_and_upload!(
@@ -136,7 +364,29 @@ module PagerTree::Integrations
136
364
 
137
365
  # TODO: Implement any additional data that should be shown in the alert with high priority (be picky as to 'very important' information)
138
366
  def _additional_datums
139
- [
367
+ return @_additional_datums if @_additional_datums
368
+
369
+ if custom_definition?
370
+ @_additional_datums ||= begin
371
+ items = custom_response_result.dig("additional_data") || []
372
+ items = [items] unless items.is_a?(Array)
373
+
374
+ items.each_with_object([]) do |ad, result|
375
+ next unless ad.is_a?(Hash)
376
+
377
+ format = ad["format"].to_s
378
+ next unless PagerTree::Integrations::AdditionalDatum::FORMATS.include?(format)
379
+
380
+ result << AdditionalDatum.new(
381
+ format: format,
382
+ label: ad["label"].to_s.presence || "Untitled",
383
+ value: ad["value"]
384
+ )
385
+ end
386
+ end
387
+ end
388
+
389
+ @_additional_datums ||= [
140
390
  AdditionalDatum.new(format: "email", label: "From", value: _mail.from),
141
391
  AdditionalDatum.new(format: "email", label: "To", value: _mail.to),
142
392
  AdditionalDatum.new(format: "email", label: "CCs", value: _mail.cc)
@@ -0,0 +1,24 @@
1
+ ---
2
+ rules:
3
+ - match:
4
+ log.body:
5
+ "$regex": "event_type=Automatic"
6
+ "$options": "i"
7
+ actions:
8
+ - type: "create"
9
+ title: "{{log.subject}}"
10
+ description: "{{{log.body}}}"
11
+ # Works for:
12
+ # event_url=https://...
13
+ # event_url=<a href="https://...">
14
+ thirdparty_id: >-
15
+ {{{itemAt (regexMatch log.body 'event_url=(?:<a\s+href=")?(https?://[^"\r\n>]+)') 1}}}
16
+
17
+ - match:
18
+ log.body:
19
+ "$regex": "event_type=Manual"
20
+ "$options": "i"
21
+ actions:
22
+ - type: "resolve"
23
+ thirdparty_id: >-
24
+ {{{itemAt (regexMatch log.body 'event_url=(?:<a\s+href=")?(https?://[^"\r\n>]+)') 1}}}
@@ -50,15 +50,27 @@ module PagerTree::Integrations
50
50
  end
51
51
 
52
52
  def adapter_response_inactive_subscription
53
- adapter_controller&.render(status: :payment_required, json: {code: Rack::Utils.status_code(:payment_required), errors: ["Customer must subscribe service plan to use this integration"]})
53
+ adapter_controller&.render(status: adapter_response_inactive_subscription_code, json: {code: Rack::Utils.status_code(adapter_response_inactive_subscription_code), errors: ["Customer must subscribe service plan to use this integration"]})
54
+ end
55
+
56
+ def adapter_response_inactive_subscription_code
57
+ :payment_required
54
58
  end
55
59
 
56
60
  def adapter_response_upgrade
57
- adapter_controller&.render(status: :payment_required, json: {code: Rack::Utils.status_code(:payment_required), errors: ["Customer must upgrade service plan to use this integration"]})
61
+ adapter_controller&.render(status: adapter_response_upgrade_code, json: {code: Rack::Utils.status_code(adapter_response_upgrade_code), errors: ["Customer must upgrade service plan to use this integration"]})
62
+ end
63
+
64
+ def adapter_response_upgrade_code
65
+ :payment_required
58
66
  end
59
67
 
60
68
  def adapter_response_maintenance_mode
61
- adapter_controller&.render(status: :service_unavailable, json: {code: Rack::Utils.status_code(:service_unavailable), errors: ["Integration currently in maintenance mode"]})
69
+ adapter_controller&.render(status: adapter_response_maintenance_mode_code, json: {code: Rack::Utils.status_code(adapter_response_maintenance_mode_code), errors: ["Integration currently in maintenance mode"]})
70
+ end
71
+
72
+ def adapter_response_maintenance_mode_code
73
+ :service_unavailable
62
74
  end
63
75
 
64
76
  def adapter_thirdparty_id
@@ -39,8 +39,10 @@ module PagerTree::Integrations
39
39
  def adapter_action
40
40
  status = _freshdesk_webhook.dig("ticket_status")
41
41
  status_i = status&.to_i
42
- if status_i == FD_TICKET_STATUS[:open] || status == "Open" || status_i == FD_TICKET_STATUS[:pending] || status == "Pending"
42
+ if !adapter_alert.present? || status_i == FD_TICKET_STATUS[:open] || status == "Open"
43
43
  :create
44
+ elsif status_i == FD_TICKET_STATUS[:pending] || status == "Pending"
45
+ :acknowledge
44
46
  elsif status_i == FD_TICKET_STATUS[:resolved] || status == "Resolved" || status_i == FD_TICKET_STATUS[:closed] || status == "Closed"
45
47
  :resolve
46
48
  else
@@ -78,13 +80,13 @@ module PagerTree::Integrations
78
80
  priority_i = priority&.to_i
79
81
 
80
82
  if priority_i == FD_TICKET_PRIORITY[:low] || priority == "Low"
81
- :low
83
+ "low"
82
84
  elsif priority_i == FD_TICKET_PRIORITY[:medium] || priority == "Normal"
83
- :medium
85
+ "medium"
84
86
  elsif priority_i == FD_TICKET_PRIORITY[:high] || priority == "High"
85
- :high
87
+ "high"
86
88
  elsif priority_i == FD_TICKET_PRIORITY[:urgent] || priority == "Urgent"
87
- :critical
89
+ "critical"
88
90
  end
89
91
  end
90
92
 
@@ -40,8 +40,10 @@ module PagerTree::Integrations
40
40
  status = _freshservice_webhook.dig("ticket_status")
41
41
  status_i = status&.to_i
42
42
 
43
- if status_i == FS_TICKET_STATUS[:open] || status == "Open" || status_i == FS_TICKET_STATUS[:pending] || status == "Pending"
43
+ if status_i == FS_TICKET_STATUS[:open] || status == "Open" || status_i == FS_TICKET_STATUS[:pending]
44
44
  :create
45
+ elsif status_i == FS_TICKET_STATUS[:pending] || status == "Pending"
46
+ :acknowledge
45
47
  elsif status_i == FS_TICKET_STATUS[:resolved] || status == "Resolved" || status_i == FS_TICKET_STATUS[:closed] || status == "Closed"
46
48
  :resolve
47
49
  else
@@ -80,13 +82,13 @@ module PagerTree::Integrations
80
82
  priority_i = priority&.to_i
81
83
 
82
84
  if priority_i == FS_TICKET_PRIORITY[:low] || priority == "Low"
83
- :low
85
+ "low"
84
86
  elsif priority_i == FS_TICKET_PRIORITY[:medium] || priority == "Normal" || priority == "Medium"
85
- :medium
87
+ "medium"
86
88
  elsif priority_i == FS_TICKET_PRIORITY[:high] || priority == "High"
87
- :high
89
+ "high"
88
90
  elsif priority_i == FS_TICKET_PRIORITY[:urgent] || priority == "Urgent"
89
- :critical
91
+ "critical"
90
92
  end
91
93
  end
92
94
 
@@ -19,7 +19,7 @@ module PagerTree::Integrations
19
19
  end
20
20
 
21
21
  def adapter_thirdparty_id
22
- adapter_incoming_request_params.dig("ruleId")
22
+ adapter_incoming_request_params.dig("ruleId") || adapter_incoming_request_params.dig("groupKey") || SecureRandom.uuid
23
23
  end
24
24
 
25
25
  def adapter_action
@@ -46,7 +46,7 @@ module PagerTree::Integrations
46
46
  private
47
47
 
48
48
  def _title
49
- adapter_incoming_request_params.dig("ruleName")
49
+ adapter_incoming_request_params.dig("ruleName") || adapter_incoming_request_params.dig("title")
50
50
  end
51
51
 
52
52
  def _description
@@ -0,0 +1,160 @@
1
+ module PagerTree::Integrations
2
+ class HetrixTools::V3 < Integration
3
+ OPTIONS = [
4
+ {key: :authentication_token, type: :string, default: nil}
5
+ ]
6
+ store_accessor :options, *OPTIONS.map { |x| x[:key] }.map(&:to_s), prefix: "option"
7
+
8
+ after_initialize do
9
+ self.option_authentication_token ||= nil
10
+ end
11
+
12
+ def adapter_should_block_incoming?(request)
13
+ option_authentication_token.present? && (request.headers["Authorization"] != "Bearer #{option_authentication_token}")
14
+ end
15
+
16
+ def adapter_supports_incoming?
17
+ true
18
+ end
19
+
20
+ def adapter_supports_outgoing?
21
+ false
22
+ end
23
+
24
+ def adapter_incoming_can_defer?
25
+ true
26
+ end
27
+
28
+ def adapter_thirdparty_id
29
+ try("_adapter_thirdparty_id_#{_webhook_type}") || SecureRandom.hex(16)
30
+ end
31
+
32
+ def adapter_action
33
+ try("_adapter_action_#{_webhook_type}") || :other
34
+ end
35
+
36
+ def _adapter_action_uptime
37
+ case adapter_incoming_request_params.dig("monitor_status")
38
+ when "online" then :resolve
39
+ when "offline" then :create
40
+ else
41
+ :other
42
+ end
43
+ end
44
+
45
+ def _adapter_action_blacklist
46
+ :create
47
+ end
48
+
49
+ def _adapter_action_resource_usage
50
+ :create
51
+ end
52
+
53
+ def adapter_process_create
54
+ Alert.new(
55
+ title: _title,
56
+ description: _description,
57
+ thirdparty_id: adapter_thirdparty_id,
58
+ dedup_keys: [],
59
+ additional_data: _additional_datums
60
+ )
61
+ end
62
+
63
+ def _adapter_thirdparty_id_uptime
64
+ adapter_incoming_request_params.dig("monitor_id")
65
+ end
66
+
67
+ def _adapter_thirdparty_id_blacklist
68
+ SecureRandom.hex(16)
69
+ end
70
+
71
+ def _adapter_thirdparty_id_resource_usage
72
+ adapter_incoming_request_params.dig("monitor_id")
73
+ end
74
+
75
+ def _webhook_type
76
+ @_webhook_type ||= if _webhook_type_uptime?
77
+ :uptime
78
+ elsif _webhook_type_resource_usage?
79
+ :resource_usage
80
+ elsif _webhook_type_blacklist?
81
+ :blacklist
82
+ else
83
+ :unknown
84
+ end
85
+ end
86
+
87
+ def _webhook_type_uptime?
88
+ adapter_incoming_request_params.dig("monitor_errors").present?
89
+ end
90
+
91
+ def _webhook_type_blacklist?
92
+ json = adapter_incoming_request_params.dig("_json")
93
+ json.present? && json.is_a?(Array)
94
+ end
95
+
96
+ def _webhook_type_resource_usage?
97
+ adapter_incoming_request_params.dig("resource_usage").present?
98
+ end
99
+
100
+ def _title
101
+ try("_title_#{_webhook_type}") || "HetrixTools Alert"
102
+ end
103
+
104
+ def _title_uptime
105
+ "#{adapter_incoming_request_params.dig("monitor_name")} is #{adapter_incoming_request_params.dig("monitor_status")}"
106
+ end
107
+
108
+ def _title_blacklist
109
+ "Blacklist Alert"
110
+ end
111
+
112
+ def _title_resource_usage
113
+ "#{adapter_incoming_request_params.dig("monitor_name")} usage alert"
114
+ end
115
+
116
+ def _description
117
+ try("_description_#{_webhook_type}") || "No description provided"
118
+ end
119
+
120
+ def _description_uptime
121
+ "<p>#{adapter_incoming_request_params.dig("monitor_target")} is #{adapter_incoming_request_params.dig("monitor_status")}</p>" +
122
+ adapter_incoming_request_params.dig("monitor_errors").map { |k, v| "<p>#{k}: #{v}</p>" }.join("")
123
+ end
124
+
125
+ def _description_blacklist
126
+ adapter_incoming_request_params.dig("_json").map { |x| "#{x["monitor"]} (#{x["blacklisted_now"]})" }.join("<br/>")
127
+ end
128
+
129
+ def _description_resource_usage
130
+ [
131
+ "<p>Resource Type: #{adapter_incoming_request_params.dig("resource_usage", "resource_type")}</p>",
132
+ "<p>Current Usage: #{adapter_incoming_request_params.dig("resource_usage", "current_usage")}</p>",
133
+ "<p>Average Usage: #{adapter_incoming_request_params.dig("resource_usage", "average_usage")} / #{adapter_incoming_request_params.dig("resource_usage", "average_minutes")}m</p>"
134
+ ].join("")
135
+ end
136
+
137
+ def _additional_datums
138
+ try("_additional_datums_#{_webhook_type}") || []
139
+ end
140
+
141
+ def _additional_datums_uptime
142
+ [
143
+ AdditionalDatum.new(format: "datetime", label: "Timestamp", value: Time.at(adapter_incoming_request_params.dig("timestamp"))),
144
+ AdditionalDatum.new(format: "text", label: "Monitor Type", value: adapter_incoming_request_params.dig("monitor_type")),
145
+ AdditionalDatum.new(format: "link", label: "Monitor Target", value: adapter_incoming_request_params.dig("monitor_target"))
146
+ ]
147
+ end
148
+
149
+ def _additional_datums_blacklist
150
+ []
151
+ end
152
+
153
+ def _additional_datums_resource_usage
154
+ [
155
+ AdditionalDatum.new(format: "text", label: "Resource Type", value: adapter_incoming_request_params.dig("resource_usage", "resource_type")),
156
+ AdditionalDatum.new(format: "text", label: "Current Usage", value: adapter_incoming_request_params.dig("resource_usage", "current_usage"))
157
+ ]
158
+ end
159
+ end
160
+ end
@@ -11,7 +11,7 @@ module PagerTree::Integrations
11
11
  end
12
12
 
13
13
  def adapter_should_block_incoming?(request)
14
- self.option_token.present? && (request.headers["honeybadger-token"] != self.option_token)
14
+ option_token.present? && (request.headers["honeybadger-token"] != option_token)
15
15
  end
16
16
 
17
17
  def adapter_supports_incoming?
@@ -0,0 +1,6 @@
1
+ module PagerTree::Integrations
2
+ class Hydrozen::V3 < SixtySixUptime::V3
3
+ # we can just piggy back the SixtySixUptime::V3 since Hydrozen is a clone of SixtySixUptime
4
+ # both integrations have the same exact functionality
5
+ end
6
+ end