-
-
Notifications
You must be signed in to change notification settings - Fork 1
Adds Custom Rules ability for our email integration #149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
463265a
e000729
9b40b0e
c612b19
4ffd847
46d63a0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,9 +1,16 @@ | ||||||||||||||||||||||
| module PagerTree::Integrations | ||||||||||||||||||||||
| class Email::V3 < Integration | ||||||||||||||||||||||
| extend ::PagerTree::Integrations::Env | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # the source log (if created) - Its what shows on the integration page (different from deferred request) | ||||||||||||||||||||||
| attribute :adapter_source_log | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| OPTIONS = [ | ||||||||||||||||||||||
| {key: :allow_spam, type: :boolean, default: false}, | ||||||||||||||||||||||
| {key: :dedup_threads, type: :boolean, default: true}, | ||||||||||||||||||||||
| {key: :sanitize_level, type: :string, default: "relaxed"} | ||||||||||||||||||||||
| {key: :sanitize_level, type: :string, default: "relaxed"}, | ||||||||||||||||||||||
| {key: :custom_definition, type: :string, default: nil}, | ||||||||||||||||||||||
| {key: :custom_definition_enabled, type: :boolean, default: false} | ||||||||||||||||||||||
| ] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| store_accessor :options, *OPTIONS.map { |x| x[:key] }.map(&:to_s), prefix: "option" | ||||||||||||||||||||||
|
|
@@ -13,11 +20,18 @@ class Email::V3 < Integration | |||||||||||||||||||||
| validates :option_allow_spam, inclusion: {in: [true, false]} | ||||||||||||||||||||||
| validates :option_dedup_threads, inclusion: {in: [true, false]} | ||||||||||||||||||||||
| validates :option_sanitize_level, inclusion: {in: SANITIZE_LEVELS} | ||||||||||||||||||||||
| validates :option_custom_definition, presence: true, if: ->(record) { record.option_custom_definition_enabled == true } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def self.custom_webhook_v3_service_url | ||||||||||||||||||||||
| ::PagerTree::Integrations.integration_custom_webhook_v3_service_url.presence || | ||||||||||||||||||||||
| find_value_by_name("integration_custom_webhook_v3", "service_url") | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| after_initialize do | ||||||||||||||||||||||
| self.option_allow_spam = false if option_allow_spam.nil? | ||||||||||||||||||||||
| self.option_dedup_threads = true if option_dedup_threads.nil? | ||||||||||||||||||||||
| self.option_sanitize_level = "relaxed" if option_sanitize_level.nil? | ||||||||||||||||||||||
| self.option_custom_definition_enabled = false if option_custom_definition_enabled.nil? | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # SPECIAL: override integration endpoint | ||||||||||||||||||||||
|
|
@@ -45,6 +59,10 @@ def endpoint | |||||||||||||||||||||
| end | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def custom_definition? | ||||||||||||||||||||||
| option_custom_definition_enabled && option_custom_definition.present? | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def adapter_should_block? | ||||||||||||||||||||||
| return false if option_allow_spam == true | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -66,23 +84,120 @@ def adapter_thirdparty_id | |||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def adapter_action | ||||||||||||||||||||||
| :create | ||||||||||||||||||||||
| if custom_definition? | ||||||||||||||||||||||
| case custom_response_result.dig("type")&.downcase | ||||||||||||||||||||||
| when "create" | ||||||||||||||||||||||
| :create | ||||||||||||||||||||||
| when "acknowledge" | ||||||||||||||||||||||
| :acknowledge | ||||||||||||||||||||||
| when "resolve" | ||||||||||||||||||||||
| :resolve | ||||||||||||||||||||||
| else | ||||||||||||||||||||||
| :other | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
| else | ||||||||||||||||||||||
| :create | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def adapter_process_create | ||||||||||||||||||||||
| Alert.new( | ||||||||||||||||||||||
| title: _title, | ||||||||||||||||||||||
| description: _description, | ||||||||||||||||||||||
| urgency: urgency, | ||||||||||||||||||||||
| thirdparty_id: _thirdparty_id, | ||||||||||||||||||||||
| dedup_keys: _dedup_keys, | ||||||||||||||||||||||
| additional_data: _additional_datums, | ||||||||||||||||||||||
| attachments: _attachments | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| if custom_definition? | ||||||||||||||||||||||
| Alert.new( | ||||||||||||||||||||||
| title: _title, | ||||||||||||||||||||||
| description: _description, | ||||||||||||||||||||||
| urgency: _urgency, | ||||||||||||||||||||||
| thirdparty_id: _thirdparty_id, | ||||||||||||||||||||||
| dedup_keys: _dedup_keys, | ||||||||||||||||||||||
| incident: _incident, | ||||||||||||||||||||||
| incident_severity: _incident_severity, | ||||||||||||||||||||||
| incident_message: _incident_message, | ||||||||||||||||||||||
| tags: _tags, | ||||||||||||||||||||||
| meta: _meta, | ||||||||||||||||||||||
| additional_data: _additional_datums, | ||||||||||||||||||||||
| attachments: _attachments | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| else | ||||||||||||||||||||||
| Alert.new( | ||||||||||||||||||||||
| title: _title, | ||||||||||||||||||||||
| description: _description, | ||||||||||||||||||||||
| urgency: urgency, | ||||||||||||||||||||||
| thirdparty_id: _thirdparty_id, | ||||||||||||||||||||||
| dedup_keys: _dedup_keys, | ||||||||||||||||||||||
| additional_data: _additional_datums, | ||||||||||||||||||||||
| attachments: _attachments | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def _custom_response | ||||||||||||||||||||||
| return @_custom_response ||= {} unless custom_definition? | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @_custom_response ||= begin | ||||||||||||||||||||||
| log_hash = { | ||||||||||||||||||||||
| subject: _mail.subject, | ||||||||||||||||||||||
| body: _body, | ||||||||||||||||||||||
| from: _mail.from, | ||||||||||||||||||||||
| to: _mail.to | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| body_hash = { | ||||||||||||||||||||||
| log: log_hash, | ||||||||||||||||||||||
| config: JSON.parse(PagerTree::Integrations::FormatConverters::YamlJsonConverter.convert_to_json(option_custom_definition)) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| response = HTTParty.post( | ||||||||||||||||||||||
| self.class.custom_webhook_v3_service_url, | ||||||||||||||||||||||
| body: body_hash.to_json, | ||||||||||||||||||||||
| headers: {"Content-Type" => "application/json"}, | ||||||||||||||||||||||
| timeout: 2 | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| unless response.success? | ||||||||||||||||||||||
| if response.parsed_response.dig("error").present? | ||||||||||||||||||||||
| adapter_source_log&.sublog({ | ||||||||||||||||||||||
| message: "Custom Webhook Service Error:", | ||||||||||||||||||||||
| parsed_response: response.parsed_response | ||||||||||||||||||||||
| }) | ||||||||||||||||||||||
| adapter_source_log&.save | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
| raise "Custom Webhook Service HTTP error: #{response.code} - #{response.message} - #{response.body}" | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| adapter_source_log&.sublog({ | ||||||||||||||||||||||
| message: "Custom Webhook Service Response:", | ||||||||||||||||||||||
| parsed_response: response.parsed_response | ||||||||||||||||||||||
| }) | ||||||||||||||||||||||
| adapter_source_log&.save | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| response.parsed_response | ||||||||||||||||||||||
| rescue JSON::ParserError => e | ||||||||||||||||||||||
| Rails.logger.error("Custom Webhook YAML to JSON conversion error: #{e.message}") | ||||||||||||||||||||||
| adapter_source_log&.sublog("Custom Webhook YAML to JSON conversion error: #{e.message}") | ||||||||||||||||||||||
| adapter_source_log&.save | ||||||||||||||||||||||
| raise "Invalid YAML configuration: #{e.message}" | ||||||||||||||||||||||
| rescue HTTParty::Error, SocketError, Net::OpenTimeout, Net::ReadTimeout => e | ||||||||||||||||||||||
| Rails.logger.error("Custom Webhook Service error: #{e.message}") | ||||||||||||||||||||||
| adapter_source_log&.sublog("Custom Webhook Service error: #{e.message}") | ||||||||||||||||||||||
| adapter_source_log&.save | ||||||||||||||||||||||
| raise "Custom Webhook Service error: #{e.message}" | ||||||||||||||||||||||
| rescue => e | ||||||||||||||||||||||
| Rails.logger.error("Unexpected error in Custom Webhook: #{e.message}") | ||||||||||||||||||||||
| adapter_source_log&.sublog("Unexpected error in Custom Webhook: #{e.message}") | ||||||||||||||||||||||
| adapter_source_log&.save | ||||||||||||||||||||||
| raise e | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def _custom_response_status | ||||||||||||||||||||||
| @_custom_response_status ||= _custom_response.dig("status") | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def custom_response_result | ||||||||||||||||||||||
| @_custom_response_result ||= _custom_response.dig("results")&.first || {} | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def _mail | ||||||||||||||||||||||
| @_mail ||= adapter_incoming_request_params.dig("mail") | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
@@ -92,27 +207,76 @@ def _inbound_email | |||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def _thirdparty_id | ||||||||||||||||||||||
| if custom_definition? | ||||||||||||||||||||||
| @_thirdparty_id ||= custom_response_result.dig("thirdparty_id").to_s.presence | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @_thirdparty_id ||= _mail.message_id || SecureRandom.uuid | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def _dedup_keys | ||||||||||||||||||||||
| keys = [] | ||||||||||||||||||||||
| return @_dedup_keys if @_dedup_keys | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @_dedup_keys ||= [] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if option_dedup_threads | ||||||||||||||||||||||
| keys.concat(Array(_thirdparty_id)) | ||||||||||||||||||||||
| keys.concat(Array(_mail.references)) | ||||||||||||||||||||||
| @_dedup_keys.concat(Array(_thirdparty_id)) | ||||||||||||||||||||||
| @_dedup_keys.concat(Array(_mail.references)) | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if custom_definition? | ||||||||||||||||||||||
| @_dedup_keys.concat(Array(custom_response_result.dig("dedup_keys"))) | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # only dedup the references per integration. Customer like sending one email to multiple integration inboxes | ||||||||||||||||||||||
| keys.compact_blank.uniq.map { |x| "#{prefix_id}_#{x}" } | ||||||||||||||||||||||
| @_dedup_keys = @_dedup_keys.compact_blank.uniq.map { |x| "#{prefix_id}_#{x}" } | ||||||||||||||||||||||
armiiller marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def _title | ||||||||||||||||||||||
| _mail.subject | ||||||||||||||||||||||
| if custom_definition? | ||||||||||||||||||||||
| @_title ||= custom_response_result.dig("title")&.to_s&.presence | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @_title ||= _mail.subject.to_s.presence || "Incoming Email - Untitled Alert" | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def _description | ||||||||||||||||||||||
| _body | ||||||||||||||||||||||
| if custom_definition? | ||||||||||||||||||||||
| @_description ||= custom_response_result.dig("description")&.to_s&.presence | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @_description ||= _body.to_s | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def _urgency | ||||||||||||||||||||||
| if custom_definition? | ||||||||||||||||||||||
| @_urgency ||= custom_response_result.dig("urgency")&.to_s&.presence | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @_urgency ||= urgency | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def _incident | ||||||||||||||||||||||
| @_incident ||= ActiveModel::Type::Boolean.new.cast(custom_response_result.dig("incident")) | ||||||||||||||||||||||
armiiller marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def _incident_severity | ||||||||||||||||||||||
| custom_response_result.dig("incident_severity")&.to_s&.presence | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def _incident_message | ||||||||||||||||||||||
| custom_response_result.dig("incident_message")&.to_s&.presence | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def _tags | ||||||||||||||||||||||
| tags = custom_response_result.dig("tags") | ||||||||||||||||||||||
| tags = tags.split(",") if tags.is_a?(String) | ||||||||||||||||||||||
| Array(tags).compact_blank.map(&:to_s).uniq | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def _meta | ||||||||||||||||||||||
armiiller marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
| meta = custom_response_result.dig("meta") | ||||||||||||||||||||||
| meta.is_a?(Hash) ? meta : {} | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def _body | ||||||||||||||||||||||
|
|
@@ -134,7 +298,9 @@ def _body | |||||||||||||||||||||
| end | ||||||||||||||||||||||
| end | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @_body = ::Sanitize.fragment(document, _sanitize_config) | ||||||||||||||||||||||
| @_body = custom_definition? ? | ||||||||||||||||||||||
| (document.at_css("body")&.inner_html || document.to_html) : | ||||||||||||||||||||||
| ::Sanitize.fragment(document, _sanitize_config) | ||||||||||||||||||||||
|
Comment on lines
+301
to
+303
|
||||||||||||||||||||||
| @_body = custom_definition? ? | |
| (document.at_css("body")&.inner_html || document.to_html) : | |
| ::Sanitize.fragment(document, _sanitize_config) | |
| html_body = document.at_css("body")&.inner_html || document.to_html | |
| @_body = if custom_definition? | |
| ::Sanitize.fragment(html_body, _sanitize_config) | |
| else | |
| ::Sanitize.fragment(document, _sanitize_config) | |
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| --- | ||
| rules: | ||
| - match: | ||
| log.body: | ||
| "$regex": "event_type=Automatic" | ||
| "$options": "i" | ||
| actions: | ||
| - type: "create" | ||
| title: "{{log.subject}}" | ||
| description: "{{{log.body}}}" | ||
| # Works for: | ||
| # event_url=https://... | ||
| # event_url=<a href="https://..."> | ||
| thirdparty_id: >- | ||
| {{{itemAt (regexMatch log.body 'event_url=(?:<a\s+href=")?(https?://[^"\r\n>]+)') 1}}} | ||
|
|
||
| - match: | ||
| log.body: | ||
| "$regex": "event_type=Manual" | ||
| "$options": "i" | ||
| actions: | ||
| - type: "resolve" | ||
| thirdparty_id: >- | ||
| {{{itemAt (regexMatch log.body 'event_url=(?:<a\s+href=")?(https?://[^"\r\n>]+)') 1}}} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,41 @@ | ||
| <div class="form-group group"> | ||
| <%= form.check_box :option_allow_spam, class: "form-checkbox" %> | ||
| <%= form.label :option_allow_spam %> | ||
| <p class="form-hint md:inline-block"><%== t(".option_allow_spam_hint_html") %></p> | ||
| </div> | ||
| <% | ||
| x_data = { | ||
| option_custom_definition_enabled: form.object.option_custom_definition_enabled, | ||
| } | ||
| %> | ||
| <div x-data=<%= x_data.to_json.html_safe %>> | ||
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> | ||
| <div class="form-group group"> | ||
| <%= form.check_box :option_allow_spam, class: "form-checkbox" %> | ||
| <%= form.label :option_allow_spam, class: "inline-block" %> | ||
| <p class="form-hint md:inline-block"><%== t(".option_allow_spam_hint_html") %></p> | ||
| </div> | ||
|
|
||
| <div class="form-group group"> | ||
| <%= form.check_box :option_dedup_threads, class: "form-checkbox" %> | ||
| <%= form.label :option_dedup_threads %> | ||
| <p class="form-hint"><%== t(".option_dedup_threads_hint_html") %></p> | ||
| </div> | ||
| <div class="form-group group"> | ||
| <%= form.check_box :option_dedup_threads, class: "form-checkbox" %> | ||
| <%= form.label :option_dedup_threads, class: "inline-block" %> | ||
| <p class="form-hint md:inline-block"><%== t(".option_dedup_threads_hint_html") %></p> | ||
| </div> | ||
|
|
||
| <div class="form-group group"> | ||
| <%= form.label :option_sanitize_level %> | ||
| <%= form.select :option_sanitize_level, PagerTree::Integrations::Email::V3::SANITIZE_LEVELS.map{|x| [x.humanize, x]}, {}, class:'form-control' %> | ||
| <p class="form-hint"><%== t(".option_sanitize_level_hint_html") %></p> | ||
| </div> | ||
| <div class="form-group group"> | ||
| <%= form.label :option_sanitize_level %> | ||
| <%= form.select :option_sanitize_level, PagerTree::Integrations::Email::V3::SANITIZE_LEVELS.map{|x| [x.humanize, x]}, {}, class:'form-control' %> | ||
| <p class="form-hint"><%== t(".option_sanitize_level_hint_html") %></p> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div class="form-group group"> | ||
| <%= form.check_box :option_custom_definition_enabled, class: "form-checkbox", "x-model": "option_custom_definition_enabled" %> | ||
| <%= form.label :option_custom_definition_enabled, class: "inline-block" %> | ||
| <p class="form-hint md:inline-block"><%== t(".option_custom_definition_enabled_hint_html") %></p> | ||
| </div> | ||
|
|
||
| <div class="form-group group" x-show="option_custom_definition_enabled" x-cloak x-transition> | ||
| <%= tag.div data: {controller: "code-editor", code_editor_language_value: "yaml", code_editor_read_only_value: false } do %> | ||
| <%= form.label :option_custom_definition %> | ||
| <%= form.hidden_field :option_custom_definition, class: "form-control", data: {code_editor_target: "form"} %> | ||
| <%= tag.div class: "h-96", data: {code_editor_target: "editor"} do %><%= form.object.option_custom_definition %><% end %> | ||
| <p class="form-hint"><%== t(".option_custom_definition_hint_html") %></p> | ||
| <% end %> | ||
| </div> | ||
| </div> |
Uh oh!
There was an error while loading. Please reload this page.