Skip to content

Commit 71f81e3

Browse files
authored
Meta Workplace Integration (#114)
* Adding Meta Workplace Integration (implementation) * TODO: Tests for Meta Workplace * Adding Meta Workplace Integration (implementation) * TODO: Tests for Meta Workplace * Add the option for outgoing rules for the meta workplace integration * Simplifies the Meta Workplace integration and adds the comment created feature * Adding tests for meta workplace integration * Segregate the meta workplace data by integration. * Add some better error handling in the Workplace integration * Trigger off the alert assigned event, not the created event.
1 parent af7eccd commit 71f81e3

File tree

7 files changed

+488
-3
lines changed

7 files changed

+488
-3
lines changed
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
module PagerTree::Integrations
2+
class Meta::Workplace::V3 < Integration
3+
OPTIONS = [
4+
{key: :incoming_enabled, type: :boolean, default: false},
5+
{key: :outgoing_enabled, type: :boolean, default: true},
6+
{key: :access_token, type: :string, default: nil},
7+
{key: :app_secret, type: :string, default: nil},
8+
{key: :group_id, type: :string, default: nil},
9+
{key: :outgoing_rules, type: :string, default: nil}
10+
]
11+
store_accessor :options, *OPTIONS.map { |x| x[:key] }.map(&:to_s), prefix: "option"
12+
13+
validates :option_incoming_enabled, inclusion: {in: [true, false]}
14+
validates :option_outgoing_enabled, inclusion: {in: [true, false]}
15+
validates :option_access_token, presence: true
16+
# validates :option_app_secret, presence: true
17+
validates :option_group_id, presence: true
18+
19+
after_initialize do
20+
self.option_incoming_enabled = false if option_incoming_enabled.nil?
21+
self.option_outgoing_enabled = true if option_outgoing_enabled.nil?
22+
self.option_outgoing_rules ||= ""
23+
end
24+
25+
def endpoint
26+
super + "/g"
27+
end
28+
29+
def adapter_should_block_incoming?(request)
30+
should_block = false
31+
32+
if option_app_secret.present? && request.headers["x-hub-signature-256"].present?
33+
signature = request.headers["x-hub-signature-256"].delete_prefix("sha256=")
34+
data = request.body.read
35+
digest = OpenSSL::HMAC.hexdigest("SHA256", option_app_secret, data)
36+
should_block = signature != digest
37+
end
38+
39+
should_block
40+
end
41+
42+
def adapter_supports_incoming?
43+
# TODO - change this to true once we are ready to support this. The code currently works, but we don't want to confuse our users.
44+
# Let's work with them to see what they want the functionality to look like
45+
false
46+
end
47+
48+
def adapter_supports_outgoing?
49+
true
50+
end
51+
52+
def adapter_show_outgoing_webhook_delivery?
53+
true
54+
end
55+
56+
def adapter_incoming_can_defer?
57+
false
58+
end
59+
60+
def adapter_supports_title_template?
61+
false
62+
end
63+
64+
def adapter_supports_description_template?
65+
false
66+
end
67+
68+
def adapter_thirdparty_id
69+
[_entry&.dig("id"), _entry&.dig("time")].compact_blank.join("-")
70+
end
71+
72+
def adapter_action
73+
if option_incoming_enabled && adapter_incoming_request_params.dig("object") == "page" && _entry_changes&.dig("field") == "mention"
74+
:create
75+
else
76+
:other
77+
end
78+
end
79+
80+
def adapter_response_incoming
81+
if adapter_incoming_request_params["hub.mode"] == "subscribe"
82+
adapter_controller&.render(plain: adapter_incoming_request_params["hub.challenge"])
83+
else
84+
adapter_controller&.head(:ok)
85+
end
86+
end
87+
88+
def adapter_process_create
89+
# send back an ok
90+
post_id = _entry_changes&.dig("value", "post_id")
91+
_post_comment(post_id, "Ok") if post_id.present?
92+
93+
Alert.new(
94+
title: _title,
95+
thirdparty_id: adapter_thirdparty_id
96+
)
97+
end
98+
99+
def adapter_process_other
100+
end
101+
102+
def adapter_outgoing_interest?(event_name)
103+
option_outgoing_enabled && [
104+
"alert_assigned",
105+
"alert_acknowledged",
106+
"alert_rejected",
107+
"alert_resolved",
108+
"alert_dropped",
109+
"alert_handoff",
110+
"comment_created"
111+
].include?(event_name.to_s)
112+
end
113+
114+
def adapter_process_outgoing
115+
event_type = adapter_outgoing_event.event_name.to_s
116+
message = _generate_message(event_type)
117+
118+
return unless message.present?
119+
120+
if event_type == "alert_assigned"
121+
_post_message(option_group_id, message)
122+
else
123+
post_id = _alert.meta["#{id}_meta_workplace_post_id"]
124+
125+
if post_id.blank?
126+
outgoing_webhook_delivery_id = _alert.meta["#{id}_meta_workplace_outgoing_webhook_delivery_id"]
127+
outgoing_webhook_delivery = OutgoingWebhookDelivery.find_by(id: outgoing_webhook_delivery_id)
128+
post_id = begin
129+
JSON.parse(outgoing_webhook_delivery.responses.first.dig("body"))["id"]
130+
rescue
131+
nil
132+
end
133+
134+
if post_id.present?
135+
_alert.meta["#{id}_meta_workplace_post_id"] = post_id
136+
_alert.save!
137+
end
138+
end
139+
140+
_post_comment(post_id, message) if post_id.present?
141+
end
142+
rescue => e
143+
Rails.logger.error "[Workplace] Error processing outgoing event: #{e.message}"
144+
end
145+
146+
private
147+
148+
# INCOMING
149+
def _entry
150+
@_entry ||= adapter_incoming_request_params&.dig("entry", 0)
151+
end
152+
153+
def _entry_changes
154+
@_entry_changes ||= _entry&.dig("changes", 0)
155+
end
156+
157+
def _title
158+
_entry_changes&.dig("value", "message")
159+
end
160+
161+
def _additional_datums
162+
[]
163+
end
164+
165+
# OUTGOING
166+
def _alert
167+
@_alert ||= adapter_outgoing_event.alert
168+
end
169+
170+
def _post_message(group_id, message)
171+
# This operation needs to happen immediately because we need to attach the post_id to the alert
172+
url = "https://graph.facebook.com/#{group_id}/feed?message=#{CGI.escape(message)}&formatting=MARKDOWN"
173+
outgoing_webhook_delivery = _send_outgoing(url)
174+
_alert.meta["#{id}_meta_workplace_outgoing_webhook_delivery_id"] = outgoing_webhook_delivery.id
175+
_alert.save!
176+
177+
outgoing_webhook_delivery
178+
end
179+
180+
def _post_comment(post_id, message)
181+
url = "https://graph.facebook.com/#{post_id}/comments?message=#{CGI.escape(message)}"
182+
_send_outgoing(url)
183+
end
184+
185+
def _send_outgoing(url)
186+
outgoing_webhook_delivery = OutgoingWebhookDelivery.factory(
187+
resource: self,
188+
url: url,
189+
options: {
190+
headers: {
191+
"Authorization" => "Bearer #{option_access_token}"
192+
}
193+
}
194+
)
195+
outgoing_webhook_delivery.save!
196+
outgoing_webhook_delivery.deliver_later
197+
198+
outgoing_webhook_delivery
199+
end
200+
201+
def _generate_message(event_name)
202+
case event_name
203+
when "alert_assigned"
204+
if _alert.incident?
205+
"[Incident ##{_alert.tiny_id}](#{Rails.application.routes.url_helpers.try(:alert_url, _alert, script_name: "/#{_alert.account_id}")}) [#{_alert.incident_severity.upcase.dasherize}] #{_alert.incident_message} - #{_alert.title}"
206+
else
207+
"[Alert ##{_alert.tiny_id}](#{Rails.application.routes.url_helpers.try(:alert_url, _alert, script_name: "/#{_alert.account_id}")}) #{_alert.title}"
208+
end
209+
when "alert_acknowledged"
210+
acknowledger = adapter_outgoing_event.account_user || adapter_outgoing_event.team
211+
acknowledger&.name&.present? ? "Acknowledged by #{acknowledger.name}" : "Acknowledged"
212+
when "alert_rejected"
213+
rejecter = adapter_outgoing_event.account_user
214+
rejecter&.name&.present? ? "Rejected by #{rejecter.name}" : "Rejected"
215+
when "alert_resolved"
216+
resolver = adapter_outgoing_event.account_user || adapter_outgoing_event.team
217+
resolver&.name&.present? ? "Resolved by #{resolver.name}" : "Resolved"
218+
when "alert_dropped"
219+
"Dropped"
220+
when "alert_handoff"
221+
handoff = adapter_outgoing_event.handoff
222+
"Handed off from #{handoff.source.name} to #{handoff.destination.name}"
223+
when "comment_created"
224+
comment = adapter_outgoing_event.comment
225+
commenter = comment.account_user&.name || "PagerTree"
226+
"#{commenter} commented: \"#{comment.body.to_plain_text}\""
227+
end
228+
end
229+
end
230+
end

app/models/pager_tree/integrations/outgoing_event.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class OutgoingEvent
1212
attr_accessor :handoff
1313
attr_accessor :team
1414
attr_accessor :account_user
15+
attr_accessor :comment
1516

1617
define_model_callbacks :initialize
1718

@@ -31,6 +32,7 @@ def initialize(params = {})
3132
self.handoff ||= nil
3233
self.team ||= nil
3334
self.account_user ||= nil
35+
self.comment ||= nil
3436
end
3537
end
3638
end
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
2+
<div class="form-group group">
3+
<%= form.label :option_group_id %>
4+
<%= form.text_field :option_group_id, class: "form-control" %>
5+
<p class="form-hint"><%== t(".option_group_id_hint_html") %></p>
6+
</div>
7+
<div class="form-group group">
8+
<%= form.label :option_access_token %>
9+
<%= form.text_field :option_access_token, class: "form-control" %>
10+
<p class="form-hint"><%== t(".option_access_token_hint_html") %></p>
11+
</div>
12+
<%
13+
=begin%>
14+
<div class="form-group">
15+
<%= form.label :option_app_secret %>
16+
<%= form.text_field :option_app_secret, class: "form-control" %>
17+
<p class="form-hint"><%== t(".option_app_secret_hint_html") %></p>
18+
</div>
19+
<%
20+
=end%>
21+
22+
<%
23+
opts = [
24+
# :outgoing_enabled,
25+
# :incoming_enabled
26+
]
27+
%>
28+
<% opts.each do |opt| %>
29+
<div class="form-group group">
30+
<%= form.check_box "option_#{opt.to_s}".to_sym, class: "form-checkbox" %>
31+
<%= form.label "option_#{opt.to_s}".to_sym, class: "inline-block" %>
32+
<p class="form-hint md:inline-block"><%== t(".option_#{opt.to_s}_hint_html") %></p>
33+
</div>
34+
<% end %>
35+
</div>
36+
37+
<div class="grid grid-cols-1 gap-4">
38+
<%= tag.div class: "form-group group", data: {controller: "code-editor", code_editor_language_value: "yaml", code_editor_read_only_value: false } do %>
39+
<%= form.label :option_outgoing_rules, t("pager_tree.integrations.common.option_outgoing_rules") %>
40+
<%= form.hidden_field :option_outgoing_rules, class: "form-control", data: {code_editor_target: "form"} %>
41+
<%= tag.div class: "h-96", data: {code_editor_target: "editor"} do %><%= form.object.option_outgoing_rules %><% end %>
42+
<p class="form-hint"><%== t("pager_tree.integrations.common.option_outgoing_rules_hint_html") %></p>
43+
<% end %>
44+
</div>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<div class="sm:col-span-2">
2+
<dt class="text-sm font-medium text-gray-500">
3+
<%= t("activerecord.attributes.pager_tree/integrations/meta/workplace/v3.option_group_id") %>
4+
</dt>
5+
<dd class="mt-1 text-sm text-gray-900">
6+
<div class="flex items-center gap-2">
7+
<p class="text-sm truncate">
8+
<%= integration.option_group_id %>
9+
</p>
10+
</div>
11+
</dd>
12+
</div>
13+
14+
<div class="sm:col-span-2">
15+
<dt class="text-sm font-medium text-gray-500">
16+
<%= t("activerecord.attributes.pager_tree/integrations/meta/workplace/v3.option_access_token") %>
17+
</dt>
18+
<dd class="mt-1 text-sm text-gray-900">
19+
<div class="flex items-center gap-2">
20+
<p class="text-sm truncate">
21+
<%= mask integration.option_access_token %>
22+
</p>
23+
</div>
24+
</dd>
25+
</div>
26+
27+
<%
28+
=begin%>
29+
<div class="sm:col-span-2">
30+
<dt class="text-sm font-medium text-gray-500">
31+
<%= t("activerecord.attributes.pager_tree/integrations/meta/workplace/v3.option_app_secret") %>
32+
</dt>
33+
<dd class="mt-1 text-sm text-gray-900">
34+
<div class="flex items-center gap-2">
35+
<p class="text-sm truncate">
36+
<%= mask integration.option_app_secret %>
37+
</p>
38+
</div>
39+
</dd>
40+
</div>
41+
<%
42+
=end%>
43+
44+
<%
45+
opts = [
46+
# :outgoing_enabled,
47+
# :incoming_enabled
48+
]
49+
%>
50+
<% opts.each do |opt| %>
51+
<div class="sm:col-span-1">
52+
<dt class="text-sm font-medium text-gray-500">
53+
<%= t("activerecord.attributes.pager_tree/integrations/meta/workplace/v3.option_#{opt.to_s}") %>
54+
</dt>
55+
<dd class="mt-1 text-sm text-gray-900">
56+
<%= render partial: "shared/components/badge_enabled", locals: { enabled: integration.send("option_#{opt.to_s}") } %>
57+
</dd>
58+
</div>
59+
<% end %>
60+
61+
<div class="sm:col-span-1">
62+
<dt class="text-sm font-medium text-gray-500">
63+
<%= t("pager_tree.integrations.common.option_outgoing_rules") %>
64+
</dt>
65+
<dd class="mt-1 text-sm text-gray-900">
66+
<%= render partial: "shared/components/badge_enabled", locals: { enabled: integration.option_outgoing_rules.present? } %>
67+
</dd>
68+
</div>

0 commit comments

Comments
 (0)