pager_tree-integrations 1.1.5 → 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 +276 -25
  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,8 +111,24 @@ 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
- errors.add(:record_emails, "must be a valid email") if option_record_emails.any? { |x| !x.match(URI::MailTo::EMAIL_REGEXP) }
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
88
133
 
89
134
  def adapter_supports_incoming?
@@ -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,16 +386,49 @@ 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.")
390
+
391
+ if option_record_emails.any?
392
+ emails = option_record_emails.map do |x|
393
+ if x == "team"
394
+ Array(adapter_alert.destination_teams.map(&:admin_users).flatten&.map(&:email)) + Array(adapter_alert.destination_teams.map(&:member_users).flatten&.map(&:email))
395
+ elsif x == "team-admin"
396
+ Array(adapter_alert.destination_teams.map(&:admin_users).flatten&.map(&:email))
397
+ elsif x == "on-call"
398
+ adapter_alert.destination_teams.map do |t|
399
+ Array(t.schedule.current_oncall_event_occurrences.map(&:attendees).flatten.map(&:attendee).uniq.map(&:email))
400
+ end
401
+ else
402
+ x
403
+ end
404
+ end.flatten.compact_blank.uniq
231
405
 
232
- option_record_emails.each do |email|
233
- LiveCallRouting::Twilio::V3Mailer.with(email: email, alert: adapter_alert, from: adapter_incoming_request_params.dig("From"), recording_url: recording_url).call_recording.deliver_later
406
+ adapter_alert.logs.create!(message: "Sending voicemail recording to #{emails.size} emails.")
407
+ emails.each do |email|
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
409
+ end
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.")
234
418
  end
235
419
  elsif option_record
420
+ adapter_alert.logs.create!(message: "No one is available to answer this call. Requesting voicemail recording.")
236
421
  _twiml.play(url: option_no_answer_media_url)
237
422
  _twiml.record(max_length: 60)
238
423
  else
239
- _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
240
432
  _twiml.hangup
241
433
  end
242
434
 
@@ -245,19 +437,43 @@ module PagerTree::Integrations
245
437
 
246
438
  def adapter_process_queue_status_deferred
247
439
  queue_result = adapter_incoming_request_params.dig("QueueResult")
248
- adapter_source_log&.sublog("Processing queus status #{queue_result}")
440
+ adapter_source_log&.sublog("Processing queue status #{queue_result}")
249
441
 
250
442
  if queue_result == "hangup"
251
443
  self.adapter_alert = alerts.find_by(thirdparty_id: _thirdparty_id)
252
444
  adapter_alert.logs.create!(message: "Caller hungup while waiting in queue.")
253
- adapter_alert.resolve!(self)
445
+ adapter_alert.resolve!(self, force: true) unless option_skip_resolve_on_hangup
254
446
  queue_destroy
255
447
  end
256
448
 
257
449
  adapter_source_log&.save!
258
450
  end
259
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
+
260
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
+
261
477
  event = adapter_outgoing_event.event_name.to_s
262
478
  if event == "alert_acknowledged"
263
479
  _on_acknowledge
@@ -266,6 +482,14 @@ module PagerTree::Integrations
266
482
  end
267
483
  end
268
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
+
269
493
  private
270
494
 
271
495
  def _thirdparty_id
@@ -273,7 +497,7 @@ module PagerTree::Integrations
273
497
  end
274
498
 
275
499
  def _title
276
- "Incoming call from #{adapter_incoming_request_params.dig("From")}"
500
+ "Incoming call from #{adapter_incoming_request_params.dig("From")&.chars&.join(" ")}"
277
501
  end
278
502
 
279
503
  def _additional_datums
@@ -286,8 +510,12 @@ module PagerTree::Integrations
286
510
  ]
287
511
  end
288
512
 
513
+ def _api_region
514
+ API_REGIONS.include?(option_api_region) ? option_api_region : "ashburn.us1"
515
+ end
516
+
289
517
  def _client
290
- @_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)
291
519
  end
292
520
 
293
521
  def _call
@@ -298,12 +526,20 @@ module PagerTree::Integrations
298
526
  @_twiml ||= ::Twilio::TwiML::VoiceResponse.new
299
527
  end
300
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
+
301
533
  def _teams_size
302
- @_teams_size ||= teams.size
534
+ @_teams_size ||= _teams.size
303
535
  end
304
536
 
305
537
  def _teams_sorted
306
- @_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)
307
543
  end
308
544
 
309
545
  def _teams_message
@@ -333,21 +569,23 @@ module PagerTree::Integrations
333
569
 
334
570
  if number.present?
335
571
  _twiml.play(url: option_connect_now_media_url)
336
- _twiml.pause(length: 1)
572
+ _twiml.pause(length: option_dial_pause.to_i.clamp(1, 15))
337
573
  _twiml.dial(number: number, caller_id: _call.to, answer_on_bridge: true)
338
574
  _call.update(twiml: _twiml.to_xml)
339
575
  # log if we successfully transfered or failed
340
- adapter_alert.logs.create!(message: "Tranferring the call succeeded.")
576
+ adapter_alert.logs.create!(message: "Transferring the call succeeded.")
341
577
  else
342
578
  _twiml.say(message: "Someone has acknowledged this call, but they do not have a phone number on file. Goodbye.")
343
579
  _twiml.hangup
344
580
  _call.update(twiml: _twiml.to_xml)
345
- 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.")
346
582
  end
347
583
  rescue ::Twilio::REST::RestError => e
348
- # 21220 - Unable to update record. Call is not in-progress. Cannot redirect.
349
- if e.code != 21220
350
- 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}")
351
589
  end
352
590
  end
353
591
 
@@ -355,10 +593,23 @@ module PagerTree::Integrations
355
593
  # log that we are going to transer
356
594
  adapter_alert.logs.create!(message: "The alert was dropped. Attempting to transfer the call...")
357
595
  _call.update(url: PagerTree::Integrations::Engine.routes.url_helpers.dropped_live_call_routing_twilio_v3_url(id, thirdparty_id: adapter_alert.thirdparty_id))
358
- music_live_call_routing_twilio_v3_path
359
- 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.")
360
611
  rescue ::Twilio::REST::RestError => e
361
- 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}")
362
613
  end
363
614
 
364
615
  def queue_destroy