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
@@ -2,7 +2,7 @@ module PagerTree::Integrations
2
2
  class Integration < PagerTree::Integrations.integration_parent_class.constantize
3
3
  extend PagerTree::Integrations::BooleanStoreAccessor
4
4
 
5
- serialize :options, JSON
5
+ serialize :options, coder: JSON
6
6
  encrypts :options
7
7
 
8
8
  GENERIC_OPTIONS = [
@@ -29,6 +29,9 @@ module PagerTree::Integrations
29
29
  # the outgoing event
30
30
  attribute :adapter_outgoing_event
31
31
 
32
+ def converts_to
33
+ end
34
+
32
35
  # START basic incoming functions
33
36
  def adapter_supports_incoming?
34
37
  false
@@ -91,6 +94,14 @@ module PagerTree::Integrations
91
94
  false
92
95
  end
93
96
 
97
+ def adapter_supports_auto_aggregate?
98
+ true
99
+ end
100
+
101
+ def adapter_supports_auto_resolve?
102
+ true
103
+ end
104
+
94
105
  # START basic show functions
95
106
  def adapter_show_alerts?
96
107
  adapter_supports_incoming?
@@ -106,35 +117,67 @@ module PagerTree::Integrations
106
117
  # END basic show functions
107
118
 
108
119
  def adapter_response_rate_limit
109
- adapter_controller&.head(:too_many_requests)
120
+ adapter_controller&.head(adapter_response_rate_limit_code)
121
+ end
122
+
123
+ def adapter_response_rate_limit_code
124
+ :too_many_requests
110
125
  end
111
126
 
112
127
  def adapter_response_disabled
113
- adapter_controller&.head(:method_not_allowed)
128
+ adapter_controller&.head(adapter_response_disabled_code)
129
+ end
130
+
131
+ def adapter_response_disabled_code
132
+ :method_not_allowed
114
133
  end
115
134
 
116
135
  def adapter_response_inactive_subscription
117
- adapter_controller&.head(:payment_required)
136
+ adapter_controller&.head(adapter_response_inactive_subscription_code)
137
+ end
138
+
139
+ def adapter_response_inactive_subscription_code
140
+ :payment_required
118
141
  end
119
142
 
120
143
  def adapter_response_upgrade
121
- adapter_controller&.head(:payment_required)
144
+ adapter_controller&.head(adapter_response_upgrade_code)
145
+ end
146
+
147
+ def adapter_response_upgrade_code
148
+ :payment_required
122
149
  end
123
150
 
124
151
  def adapter_response_maintenance_mode
125
- adapter_controller&.head(:ok)
152
+ adapter_controller&.head(adapter_response_maintenance_mode_code)
153
+ end
154
+
155
+ def adapter_response_maintenance_mode_code
156
+ :ok
126
157
  end
127
158
 
128
159
  def adapter_response_blocked
129
- adapter_controller&.head(:forbidden)
160
+ adapter_controller&.head(adapter_response_blocked_code)
161
+ end
162
+
163
+ def adapter_response_blocked_code
164
+ :forbidden
130
165
  end
131
166
 
132
167
  def adapter_response_deferred
133
- adapter_controller&.head(:ok)
168
+ adapter_controller&.head(adapter_response_deferred_code)
169
+ end
170
+
171
+ def adapter_response_deferred_code
172
+ :ok
134
173
  end
135
174
 
136
175
  def adapter_response_incoming
137
- adapter_controller&.head(:ok)
176
+ adapter_controller&.head(adapter_response_incoming_code)
177
+ end
178
+
179
+ def adapter_response_incoming_code
180
+ :ok
138
181
  end
139
182
 
140
183
  def cast_types
@@ -29,7 +29,7 @@ module PagerTree::Integrations
29
29
 
30
30
  def adapter_action
31
31
  event = adapter_incoming_request_params.dig("webhookEvent")
32
- event == "jira:issue_created" || (self.option_issue_updated == true && event == "jira:issue_updated") ? :create : :other
32
+ (event == "jira:issue_created" || (option_issue_updated == true && event == "jira:issue_updated")) ? :create : :other
33
33
  end
34
34
 
35
35
  def adapter_process_create
@@ -53,8 +53,18 @@ module PagerTree::Integrations
53
53
  end
54
54
 
55
55
  def _additional_datums
56
+ # links look like this: "https://jira.atlassian.com/rest/api/2/issue/99291"
57
+ # we want them to look like thish https://jira.atlassian.com/browse/CS-6157
58
+ jira_id = adapter_incoming_request_params.dig("issue", "id")
59
+ jira_link = adapter_incoming_request_params.dig("issue", "self")
60
+ jira_key = adapter_incoming_request_params.dig("issue", "key")
61
+
62
+ if jira_link.present? && jira_key.present? && jira_id.present?
63
+ jira_link = jira_link.gsub("/rest/api/2/issue/#{jira_id}", "/browse/#{jira_key}")
64
+ end
65
+
56
66
  [
57
- AdditionalDatum.new(format: "link", label: "Issue URL", value: adapter_incoming_request_params.dig("issue", "self"))
67
+ AdditionalDatum.new(format: "link", label: "Issue URL", value: jira_link)
58
68
  ]
59
69
  end
60
70
  end
@@ -23,7 +23,7 @@ module PagerTree::Integrations
23
23
  end
24
24
 
25
25
  def adapter_action
26
- adapter_incoming_request_params.dig("level") == "OK" ? :resolve : :create
26
+ (adapter_incoming_request_params.dig("level") == "OK") ? :resolve : :create
27
27
  end
28
28
 
29
29
  def adapter_process_create
@@ -4,33 +4,50 @@ module PagerTree::Integrations
4
4
  {key: :account_sid, type: :string, default: nil},
5
5
  {key: :api_key, type: :string, default: nil},
6
6
  {key: :api_secret, type: :string, default: nil},
7
+ {key: :api_region, type: :string, default: "ashburn.us1"},
7
8
  {key: :force_input, type: :boolean, default: false},
8
9
  {key: :record, type: :boolean, default: false},
9
- {key: :record_email, type: :string, default: ""}
10
+ {key: :send_straight_to_voicemail, type: :boolean, default: false},
11
+ {key: :record_email, type: :string, default: ""},
12
+ {key: :banned_phone, type: :string, default: ""},
13
+ {key: :dial_pause, type: :integer},
14
+ {key: :max_wait_time, type: :integer, default: nil},
15
+ {key: :skip_resolve_on_hangup, type: :boolean, default: false}
10
16
  ]
11
17
  store_accessor :options, *OPTIONS.map { |x| x[:key] }.map(&:to_s), prefix: "option"
12
18
 
19
+ API_REGIONS = ["ashburn.us1", "dublin.ie1", "sydney.au1"]
20
+
13
21
  has_one_attached :option_connect_now_media
14
22
  has_one_attached :option_music_media
15
23
  has_one_attached :option_no_answer_media
16
24
  has_one_attached :option_no_answer_thank_you_media
25
+ has_one_attached :option_no_answer_no_record_media
17
26
  has_one_attached :option_please_wait_media
18
27
  has_one_attached :option_welcome_media
19
28
 
20
29
  validates :option_account_sid, presence: true
21
30
  validates :option_api_key, presence: true
22
31
  validates :option_api_secret, presence: true
32
+ validates :option_api_region, inclusion: {in: API_REGIONS}
23
33
  validates :option_force_input, inclusion: {in: [true, false]}
24
34
  validates :option_record, inclusion: {in: [true, false]}
35
+ validates :option_send_straight_to_voicemail, inclusion: {in: [true, false]}
36
+ validates :option_skip_resolve_on_hangup, inclusion: {in: [true, false]}
37
+ validates :option_max_wait_time, numericality: {greater_than_or_equal_to: 30, less_than_or_equal_to: 3600}, allow_nil: true
25
38
  validate :validate_record_emails
26
39
 
27
40
  after_initialize do
28
41
  self.option_account_sid ||= nil
29
42
  self.option_api_key ||= nil
30
43
  self.option_api_secret ||= nil
44
+ self.option_api_region ||= "ashburn.us1"
31
45
  self.option_force_input ||= false
32
46
  self.option_record ||= false
47
+ self.option_send_straight_to_voicemail ||= false
33
48
  self.option_record_email ||= ""
49
+ self.option_banned_phone ||= ""
50
+ self.option_skip_resolve_on_hangup ||= false
34
51
  end
35
52
 
36
53
  SPEAK_OPTIONS = {
@@ -58,12 +75,24 @@ module PagerTree::Integrations
58
75
  option_no_answer_thank_you_media.present? ? option_no_answer_thank_you_media.url : URI.join(Rails.application.routes.url_helpers.root_url, "audios/thanks-for-message.mp3").to_s
59
76
  end
60
77
 
78
+ def option_no_answer_no_record_media_url
79
+ option_no_answer_no_record_media.present? ? option_no_answer_no_record_media.url : nil
80
+ end
81
+
61
82
  def option_record_emails=(x)
62
83
  self.option_record_email = Array(x).join(",")
63
84
  end
64
85
 
65
86
  def option_record_emails
66
- self.option_record_email.split(",")
87
+ option_record_email.split(",")
88
+ end
89
+
90
+ def option_banned_phones=(x)
91
+ self.option_banned_phone = Array(x).join(",")
92
+ end
93
+
94
+ def option_banned_phones
95
+ option_banned_phone.split(",")
67
96
  end
68
97
 
69
98
  def option_record_emails_list=(x)
@@ -82,6 +111,22 @@ module PagerTree::Integrations
82
111
  option_record_emails
83
112
  end
84
113
 
114
+ def option_banned_phones_list=(x)
115
+ # what comes in as json, via tagify
116
+ uniq_array = []
117
+ begin
118
+ uniq_array = JSON.parse(x).map { |y| y["value"] }.uniq
119
+ rescue JSON::ParserError => exception
120
+ Rails.logger.debug(exception)
121
+ end
122
+
123
+ self.option_banned_phones = uniq_array
124
+ end
125
+
126
+ def option_banned_phones_list
127
+ option_banned_phones
128
+ end
129
+
85
130
  def validate_record_emails
86
131
  errors.add(:record_emails, "must be a valid email") if option_record_emails.any? { |x| !(x.match(URI::MailTo::EMAIL_REGEXP) || ["team", "team-admin", "on-call"].include?(x)) }
87
132
  end
@@ -94,8 +139,12 @@ module PagerTree::Integrations
94
139
  true
95
140
  end
96
141
 
142
+ def adapter_supports_auto_aggregate?
143
+ false
144
+ end
145
+
97
146
  def adapter_outgoing_interest?(event_name)
98
- ["alert_acknowledged", "alert_dropped"].include?(event_name) && adapter_alert.source_id == id
147
+ ["alert_acknowledged", "alert_dropped"].include?(event_name)
99
148
  end
100
149
 
101
150
  def adapter_incoming_can_defer?
@@ -106,8 +155,20 @@ module PagerTree::Integrations
106
155
  true
107
156
  end
108
157
 
158
+ def is_banned?
159
+ from_number = adapter_incoming_request_params.dig("From")
160
+ return false unless from_number.present?
161
+ option_banned_phones.any? { |x| from_number.include?(x) }
162
+ rescue
163
+ false
164
+ end
165
+
109
166
  def adapter_action
110
- :create
167
+ if is_banned?
168
+ :other
169
+ else
170
+ :create
171
+ end
111
172
  end
112
173
 
113
174
  def adapter_thirdparty_id
@@ -126,12 +187,93 @@ module PagerTree::Integrations
126
187
  end
127
188
 
128
189
  def adapter_response_incoming
190
+ if is_banned?
191
+ _twiml.reject
192
+
193
+ adapter_source_log&.sublog("Caller #{adapter_incoming_request_params.dig("From")} on blocked list. Rejected call.")
194
+ adapter_source_log&.save
195
+
196
+ return adapter_controller&.render(xml: _twiml.to_xml)
197
+ end
198
+
199
+ if adapter_alert.meta["live_call_status_callback_set"] != true
200
+ begin
201
+ # give us status updates on the call, so we can clean up if they hang up before leaving a message
202
+ _call.update(
203
+ status_callback: PagerTree::Integrations::Engine.routes.url_helpers.call_status_live_call_routing_twilio_v3_url(id, thirdparty_id: _thirdparty_id),
204
+ status_callback_method: "POST",
205
+ url: adapter_controller&.url_for || endpoint
206
+ )
207
+
208
+ adapter_alert.meta["live_call_status_callback_set"] = true
209
+ adapter_alert.save!
210
+ rescue ::Twilio::REST::RestError => e
211
+ if e.code == 21220
212
+ # 21220 - Unable to update record. Call is not in-progress. Cannot redirect.
213
+ adapter_alert.logs.create!(message: "Updating the call for status callbacks failed. The caller has already hung up.")
214
+ else
215
+ adapter_alert.logs.create!(message: "Updating the call for status callbacks failed. #{e.message}")
216
+ end
217
+
218
+ adapter_alert.logs.create!(message: "Marking alert as resolved due to call update failure.")
219
+ adapter_alert.resolve!(self, force: true)
220
+ end
221
+
222
+ return adapter_controller&.render(xml: _twiml.to_xml)
223
+ end
224
+
225
+ # if this was attached to a router
226
+ if !adapter_alert.meta["live_call_router_team_prefix_ids"].present? && routers.size > 0 && account.subscription_feature_routers?
227
+ adapter_alert.logs.create!(message: "Routed to router. Attempting to get a list of teams...")
228
+ team_ids = []
229
+ default_receiver_team_ids = []
230
+ v3 = adapter_alert.v3_format
231
+ routers.each do |router|
232
+ if router.enabled? && router.kept?
233
+ actions = router.rules_eval({
234
+ always: true,
235
+ alert: v3
236
+ })
237
+
238
+ actions.flatten!
239
+
240
+ actions.each do |action|
241
+ team_ids << Array(action["receiver"]) if action["type"] == "assign"
242
+ end
243
+
244
+ # hold on to the default destination team ids in case we need them later
245
+ default_receiver_team_ids |= [router.default_receiver.prefix_id] if router.default_receiver.present?
246
+ end
247
+ end # end routers.each
248
+
249
+ team_ids.flatten!
250
+ team_ids.uniq!
251
+
252
+ # if the router didn't return any teams, use the default receiver teams
253
+ team_ids = default_receiver_team_ids if team_ids.size == 0
254
+
255
+ if team_ids.size > 0
256
+ adapter_alert.logs.create!(message: "Router provided #{team_ids.size} teams: #{team_ids}")
257
+ adapter_alert.meta["live_call_router_team_prefix_ids"] = team_ids
258
+ adapter_alert.save!
259
+ else
260
+ adapter_alert.logs.create!(message: "Router provided no teams.")
261
+ end
262
+ end # end if routers
263
+
129
264
  if _teams_size == 0
265
+ adapter_alert.logs.create!(message: "This integration is not configured to route to any teams. Hang up.")
130
266
  _twiml.say(message: "This integration is not configured to route to any teams. Goodbye", **SPEAK_OPTIONS)
131
267
  _twiml.hangup
132
268
  return adapter_controller&.render(xml: _twiml.to_xml)
133
269
  end
134
270
 
271
+ if option_record && option_send_straight_to_voicemail && adapter_alert.meta["live_call_send_straight_to_voicemail"].nil?
272
+ # flag this call to send straight to voicemail
273
+ adapter_alert.meta["live_call_send_straight_to_voicemail"] = true
274
+ adapter_alert.save!
275
+ end
276
+
135
277
  if !adapter_alert.meta["live_call_welcome"] && option_welcome_media.present?
136
278
  adapter_alert.logs.create!(message: "Play welcome media to caller.")
137
279
  _twiml.play(url: option_welcome_media.url)
@@ -142,6 +284,17 @@ module PagerTree::Integrations
142
284
  if selected_team
143
285
  adapter_alert.logs.create!(message: "Caller selected team '#{selected_team.name}'.") if _teams_size > 1 || option_force_input
144
286
 
287
+ if adapter_alert.meta["live_call_send_straight_to_voicemail"] == true
288
+ adapter_alert.destination_teams = [selected_team]
289
+ adapter_alert.save!
290
+
291
+ adapter_alert.logs.create!(message: "Send caller straight to voicemail (integration option).")
292
+
293
+ _twiml.redirect(PagerTree::Integrations::Engine.routes.url_helpers.dropped_live_call_routing_twilio_v3_url(id, thirdparty_id: _thirdparty_id), method: "POST")
294
+
295
+ return adapter_controller&.render(xml: _twiml.to_xml)
296
+ end
297
+
145
298
  adapter_alert.logs.create!(message: "Play please wait media to caller.")
146
299
  _twiml.play(url: option_please_wait_media_url)
147
300
  friendly_name = adapter_alert.id
@@ -167,6 +320,12 @@ module PagerTree::Integrations
167
320
  # kick off the alert workflow
168
321
  adapter_alert.route_later
169
322
  adapter_alert.logs.create!(message: "Successfully enqueued alert team workflow.")
323
+
324
+ if option_max_wait_time.present?
325
+ # set the max wait time for the queue
326
+ PagerTree::Integrations::LiveCallRouting::Twilio::V3::MaxWaitTimeJob.set(wait: option_max_wait_time.seconds).perform_later(id, adapter_alert.id)
327
+ adapter_alert.logs.create!(message: "Max wait time set to #{option_max_wait_time} seconds.")
328
+ end
170
329
  else
171
330
  adapter_alert.meta["live_call_repeat_count"] ||= 0
172
331
  adapter_alert.meta["live_call_repeat_count"] += 1
@@ -181,7 +340,7 @@ module PagerTree::Integrations
181
340
  end
182
341
  end
183
342
  else
184
- adapter_alert.logs.create!(message: "Caller input bad input (too many times). Hangup.")
343
+ adapter_alert.logs.create!(message: "Caller input: bad input (too many times). Hangup.")
185
344
  adapter_alert.resolve!(self)
186
345
  _twiml.say(message: "Too much invalid input. Goodbye.", **SPEAK_OPTIONS)
187
346
  _twiml.hangup
@@ -227,7 +386,7 @@ module PagerTree::Integrations
227
386
  adapter_alert.additional_data.push(AdditionalDatum.new(format: "link", label: "Voicemail", value: recording_url).to_h)
228
387
  adapter_alert.save!
229
388
 
230
- adapter_alert.logs.create!(message: "Caller left a <a href='#{recording_url}' target='_blank'>voicemail</a>.")
389
+ adapter_alert.logs.create!(message: "Caller left a voicemail.")
231
390
 
232
391
  if option_record_emails.any?
233
392
  emails = option_record_emails.map do |x|
@@ -244,15 +403,32 @@ module PagerTree::Integrations
244
403
  end
245
404
  end.flatten.compact_blank.uniq
246
405
 
406
+ adapter_alert.logs.create!(message: "Sending voicemail recording to #{emails.size} emails.")
247
407
  emails.each do |email|
248
408
  LiveCallRouting::Twilio::V3Mailer.with(email: email, alert: adapter_alert, from: adapter_incoming_request_params.dig("From"), recording_url: recording_url).call_recording.deliver_later
249
409
  end
250
410
  end
411
+
412
+ if adapter_alert.meta["live_call_send_straight_to_voicemail"] == true
413
+ adapter_alert.logs.create!(message: "Call was sent straight to voicemail and caller left a message. Routing the alert.")
414
+
415
+ # kick off the alert workflow
416
+ adapter_alert.route_later
417
+ adapter_alert.logs.create!(message: "Successfully enqueued alert team workflow.")
418
+ end
251
419
  elsif option_record
420
+ adapter_alert.logs.create!(message: "No one is available to answer this call. Requesting voicemail recording.")
252
421
  _twiml.play(url: option_no_answer_media_url)
253
422
  _twiml.record(max_length: 60)
254
423
  else
255
- _twiml.say(message: "No one is available to answer this call. Goodbye.", **SPEAK_OPTIONS)
424
+ # A friendly goodbye (when the integration has been configured to not record)
425
+ if option_no_answer_no_record_media_url.present?
426
+ adapter_alert.logs.create!(message: "No one is available to answer this call. Play media. Hangup on caller.")
427
+ _twiml.play(url: option_no_answer_no_record_media_url)
428
+ else
429
+ adapter_alert.logs.create!(message: "No one is available to answer this call. Say message. Hangup on caller.")
430
+ _twiml.say(message: "No one is available to answer this call. Goodbye.", **SPEAK_OPTIONS)
431
+ end
256
432
  _twiml.hangup
257
433
  end
258
434
 
@@ -261,19 +437,43 @@ module PagerTree::Integrations
261
437
 
262
438
  def adapter_process_queue_status_deferred
263
439
  queue_result = adapter_incoming_request_params.dig("QueueResult")
264
- adapter_source_log&.sublog("Processing queus status #{queue_result}")
440
+ adapter_source_log&.sublog("Processing queue status #{queue_result}")
265
441
 
266
442
  if queue_result == "hangup"
267
443
  self.adapter_alert = alerts.find_by(thirdparty_id: _thirdparty_id)
268
444
  adapter_alert.logs.create!(message: "Caller hungup while waiting in queue.")
269
- adapter_alert.resolve!(self)
445
+ adapter_alert.resolve!(self, force: true) unless option_skip_resolve_on_hangup
270
446
  queue_destroy
271
447
  end
272
448
 
273
449
  adapter_source_log&.save!
274
450
  end
275
451
 
452
+ def adapter_process_call_status_deferred
453
+ call_status = adapter_incoming_request_params.dig("CallStatus")
454
+ adapter_source_log&.sublog("Processing call status #{call_status}")
455
+
456
+ if ["completed"].include?(call_status)
457
+ self.adapter_alert = alerts.find_by(thirdparty_id: _thirdparty_id)
458
+
459
+ if adapter_alert.present? && adapter_alert.meta["live_call_send_straight_to_voicemail"] == true && !adapter_alert.additional_data.any? { |x| x["label"] == "Voicemail" }
460
+ if option_skip_resolve_on_hangup
461
+ adapter_alert.logs.create!(message: "Caller hung up without leaving a message.")
462
+ else
463
+ adapter_alert.logs.create!(message: "Caller hung up without leaving a message. Marking alert as resolved.")
464
+ adapter_alert.resolve!(self, force: true)
465
+ end
466
+ elsif adapter_alert.present? && adapter_alert.meta["live_call_send_straight_to_voicemail"] != true && !adapter_alert.meta["live_call_queue_sid"].present?
467
+ adapter_alert.logs.create!(message: "Caller hungup before being put in a queue. Marking alert as resolved.")
468
+ adapter_alert.resolve!(self, force: true)
469
+ end
470
+ end
471
+ end
472
+
276
473
  def adapter_process_outgoing
474
+ return unless adapter_alert.source_id == id
475
+ return if adapter_alert.meta["live_call_send_straight_to_voicemail"] == true
476
+
277
477
  event = adapter_outgoing_event.event_name.to_s
278
478
  if event == "alert_acknowledged"
279
479
  _on_acknowledge
@@ -282,6 +482,14 @@ module PagerTree::Integrations
282
482
  end
283
483
  end
284
484
 
485
+ def max_wait_time_reached!(alert_id)
486
+ # Handle max wait time reached logic
487
+ self.adapter_alert = alerts.find(alert_id)
488
+ return unless adapter_alert&.status_open?
489
+
490
+ _on_max_wait_time_reached
491
+ end
492
+
285
493
  private
286
494
 
287
495
  def _thirdparty_id
@@ -289,7 +497,7 @@ module PagerTree::Integrations
289
497
  end
290
498
 
291
499
  def _title
292
- "Incoming call from #{adapter_incoming_request_params.dig("From")}"
500
+ "Incoming call from #{adapter_incoming_request_params.dig("From")&.chars&.join(" ")}"
293
501
  end
294
502
 
295
503
  def _additional_datums
@@ -302,8 +510,12 @@ module PagerTree::Integrations
302
510
  ]
303
511
  end
304
512
 
513
+ def _api_region
514
+ API_REGIONS.include?(option_api_region) ? option_api_region : "ashburn.us1"
515
+ end
516
+
305
517
  def _client
306
- @_client ||= ::Twilio::REST::Client.new(self.option_api_key, self.option_api_secret, self.option_account_sid)
518
+ @_client ||= ::Twilio::REST::Client.new(option_api_key, option_api_secret, option_account_sid, _api_region)
307
519
  end
308
520
 
309
521
  def _call
@@ -314,12 +526,20 @@ module PagerTree::Integrations
314
526
  @_twiml ||= ::Twilio::TwiML::VoiceResponse.new
315
527
  end
316
528
 
529
+ def _teams
530
+ @_teams ||= adapter_alert.meta["live_call_router_team_prefix_ids"].present? ? Team.where(account_id: account_id, prefix_id: adapter_alert.meta["live_call_router_team_prefix_ids"]) : teams
531
+ end
532
+
317
533
  def _teams_size
318
- @_teams_size ||= teams.size
534
+ @_teams_size ||= _teams.size
319
535
  end
320
536
 
321
537
  def _teams_sorted
322
- @_teams_sorted ||= teams.order(name: :desc)
538
+ @_teams_sorted ||= adapter_alert.meta["live_call_router_team_prefix_ids"].present? ?
539
+ # sorts by the order the user gave us in the router
540
+ _teams.sort_by { |t| adapter_alert.meta["live_call_router_team_prefix_ids"].index(t.prefix_id) } :
541
+ # sorts in alpha order (not ascii, but dictionary style order)
542
+ _teams.order(name: :asc)
323
543
  end
324
544
 
325
545
  def _teams_message
@@ -349,21 +569,23 @@ module PagerTree::Integrations
349
569
 
350
570
  if number.present?
351
571
  _twiml.play(url: option_connect_now_media_url)
352
- _twiml.pause(length: 1)
572
+ _twiml.pause(length: option_dial_pause.to_i.clamp(1, 15))
353
573
  _twiml.dial(number: number, caller_id: _call.to, answer_on_bridge: true)
354
574
  _call.update(twiml: _twiml.to_xml)
355
575
  # log if we successfully transfered or failed
356
- adapter_alert.logs.create!(message: "Tranferring the call succeeded.")
576
+ adapter_alert.logs.create!(message: "Transferring the call succeeded.")
357
577
  else
358
578
  _twiml.say(message: "Someone has acknowledged this call, but they do not have a phone number on file. Goodbye.")
359
579
  _twiml.hangup
360
580
  _call.update(twiml: _twiml.to_xml)
361
- adapter_alert.logs.create!(message: "Tranferring the call failed. #{account_user.user.name} has no phone number on file.")
581
+ adapter_alert.logs.create!(message: "Transferring the call failed. #{account_user.user.name} has no phone number on file.")
362
582
  end
363
583
  rescue ::Twilio::REST::RestError => e
364
- # 21220 - Unable to update record. Call is not in-progress. Cannot redirect.
365
- if e.code != 21220
366
- adapter_alert.logs.create!(message: "Tranferring the call failed. #{e.message}")
584
+ if e.code == 21220
585
+ # 21220 - Unable to update record. Call is not in-progress. Cannot redirect.
586
+ adapter_alert.logs.create!(message: "[on_acknowledge] Transferring the call failed. The caller has already hung up.")
587
+ else
588
+ adapter_alert.logs.create!(message: "[on_acknowledge] Transferring the call failed. #{e.message}")
367
589
  end
368
590
  end
369
591
 
@@ -371,10 +593,23 @@ module PagerTree::Integrations
371
593
  # log that we are going to transer
372
594
  adapter_alert.logs.create!(message: "The alert was dropped. Attempting to transfer the call...")
373
595
  _call.update(url: PagerTree::Integrations::Engine.routes.url_helpers.dropped_live_call_routing_twilio_v3_url(id, thirdparty_id: adapter_alert.thirdparty_id))
374
- music_live_call_routing_twilio_v3_path
375
- adapter_alert.logs.create!(message: "Tranferring the call succeeded.")
596
+ adapter_alert.logs.create!(message: "Transferring the call succeeded.")
597
+ rescue ::Twilio::REST::RestError => e
598
+ if e.code == 21220
599
+ # 21220 - Unable to update record. Call is not in-progress. Cannot redirect
600
+ adapter_alert.logs.create!(message: "[on_drop] Transferring the call failed. The caller has already hung up.")
601
+ else
602
+ adapter_alert.logs.create!(message: "[on_drop] Transferring the call failed. #{e.message}")
603
+ end
604
+ end
605
+
606
+ def _on_max_wait_time_reached
607
+ # log that we are going to transfer
608
+ adapter_alert.logs.create!(message: "The max wait time was reached. Attempting to transfer the caller (via max wait time, to voicemail or hangup)...")
609
+ _call.update(url: PagerTree::Integrations::Engine.routes.url_helpers.dropped_live_call_routing_twilio_v3_url(id, thirdparty_id: adapter_alert.thirdparty_id))
610
+ adapter_alert.logs.create!(message: "Transferring the caller (via max wait time, to voicemail or hangup) succeeded.")
376
611
  rescue ::Twilio::REST::RestError => e
377
- adapter_alert.logs.create!(message: "Tranferring the call failed. #{e.message}")
612
+ adapter_alert.logs.create!(message: "Transferring the caller (via max wait time, to voicemail or hangup) failed. #{e.message}")
378
613
  end
379
614
 
380
615
  def queue_destroy