feat: add PagerDuty Events API v2 integration with 2-way alert sync
This commit is contained in:
parent
a8339797fa
commit
bb4ff232e3
10 changed files with 397 additions and 26 deletions
|
|
@ -215,21 +215,41 @@ defmodule Towerops.Alerts do
|
||||||
Acknowledges an alert.
|
Acknowledges an alert.
|
||||||
"""
|
"""
|
||||||
def acknowledge_alert(alert, user_id) do
|
def acknowledge_alert(alert, user_id) do
|
||||||
alert
|
result =
|
||||||
|> Alert.changeset(%{
|
alert
|
||||||
acknowledged_at: DateTime.truncate(DateTime.utc_now(), :second),
|
|> Alert.changeset(%{
|
||||||
acknowledged_by_id: user_id
|
acknowledged_at: DateTime.truncate(DateTime.utc_now(), :second),
|
||||||
})
|
acknowledged_by_id: user_id
|
||||||
|> Repo.update()
|
})
|
||||||
|
|> Repo.update()
|
||||||
|
|
||||||
|
case result do
|
||||||
|
{:ok, updated_alert} ->
|
||||||
|
Task.start(fn -> Towerops.PagerDuty.Notifier.notify_acknowledge(updated_alert) end)
|
||||||
|
{:ok, updated_alert}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Resolves an alert.
|
Resolves an alert.
|
||||||
"""
|
"""
|
||||||
def resolve_alert(alert) do
|
def resolve_alert(alert) do
|
||||||
alert
|
result =
|
||||||
|> Alert.changeset(%{resolved_at: DateTime.truncate(DateTime.utc_now(), :second)})
|
alert
|
||||||
|> Repo.update()
|
|> Alert.changeset(%{resolved_at: DateTime.truncate(DateTime.utc_now(), :second)})
|
||||||
|
|> Repo.update()
|
||||||
|
|
||||||
|
case result do
|
||||||
|
{:ok, updated_alert} ->
|
||||||
|
Task.start(fn -> Towerops.PagerDuty.Notifier.notify_resolve(updated_alert) end)
|
||||||
|
{:ok, updated_alert}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ defmodule Towerops.Integrations.Integration do
|
||||||
@primary_key {:id, :binary_id, autogenerate: true}
|
@primary_key {:id, :binary_id, autogenerate: true}
|
||||||
@foreign_key_type :binary_id
|
@foreign_key_type :binary_id
|
||||||
|
|
||||||
@valid_providers ~w(preseem gaiia)
|
@valid_providers ~w(preseem gaiia pagerduty)
|
||||||
@valid_sync_statuses ~w(never success partial failed)
|
@valid_sync_statuses ~w(never success partial failed)
|
||||||
|
|
||||||
schema "integrations" do
|
schema "integrations" do
|
||||||
|
|
|
||||||
112
lib/towerops/pagerduty/client.ex
Normal file
112
lib/towerops/pagerduty/client.ex
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
defmodule Towerops.PagerDuty.Client do
|
||||||
|
@moduledoc "PagerDuty Events API v2 client."
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@events_url "https://events.pagerduty.com/v2/enqueue"
|
||||||
|
|
||||||
|
def trigger(routing_key, alert, device, site_name, towerops_url) do
|
||||||
|
send_event(%{
|
||||||
|
routing_key: routing_key,
|
||||||
|
event_action: "trigger",
|
||||||
|
dedup_key: dedup_key(alert),
|
||||||
|
payload: %{
|
||||||
|
summary: alert_summary(alert, device),
|
||||||
|
source: "towerops",
|
||||||
|
severity: severity(alert.alert_type),
|
||||||
|
timestamp: DateTime.to_iso8601(alert.triggered_at),
|
||||||
|
component: device.hostname || device.ip_address,
|
||||||
|
group: site_name,
|
||||||
|
class: to_string(alert.alert_type),
|
||||||
|
custom_details: %{
|
||||||
|
device_id: device.id,
|
||||||
|
device_name: device.hostname || device.ip_address,
|
||||||
|
ip_address: device.ip_address,
|
||||||
|
site_name: site_name,
|
||||||
|
alert_type: to_string(alert.alert_type),
|
||||||
|
message: alert.message,
|
||||||
|
towerops_url: towerops_url
|
||||||
|
}
|
||||||
|
},
|
||||||
|
links: [%{href: towerops_url, text: "View in TowerOps"}]
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def acknowledge(routing_key, alert) do
|
||||||
|
send_event(%{
|
||||||
|
routing_key: routing_key,
|
||||||
|
event_action: "acknowledge",
|
||||||
|
dedup_key: dedup_key(alert)
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve(routing_key, alert) do
|
||||||
|
send_event(%{
|
||||||
|
routing_key: routing_key,
|
||||||
|
event_action: "resolve",
|
||||||
|
dedup_key: dedup_key(alert)
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_connection(routing_key) do
|
||||||
|
test_dedup = "towerops-test-#{System.unique_integer([:positive])}"
|
||||||
|
|
||||||
|
case send_event(%{
|
||||||
|
routing_key: routing_key,
|
||||||
|
event_action: "trigger",
|
||||||
|
dedup_key: test_dedup,
|
||||||
|
payload: %{
|
||||||
|
summary: "TowerOps integration test",
|
||||||
|
source: "towerops",
|
||||||
|
severity: "info",
|
||||||
|
timestamp: DateTime.to_iso8601(DateTime.utc_now())
|
||||||
|
}
|
||||||
|
}) do
|
||||||
|
{:ok, _} ->
|
||||||
|
send_event(%{
|
||||||
|
routing_key: routing_key,
|
||||||
|
event_action: "resolve",
|
||||||
|
dedup_key: test_dedup
|
||||||
|
})
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp send_event(body) do
|
||||||
|
case Req.post(@events_url, json: body) do
|
||||||
|
{:ok, %{status: status, body: resp_body}} when status in [200, 202] ->
|
||||||
|
{:ok, resp_body}
|
||||||
|
|
||||||
|
{:ok, %{status: 400, body: resp_body}} ->
|
||||||
|
{:error, {:bad_request, resp_body["message"] || "Invalid event"}}
|
||||||
|
|
||||||
|
{:ok, %{status: 429}} ->
|
||||||
|
{:error, :rate_limited}
|
||||||
|
|
||||||
|
{:ok, %{status: status}} ->
|
||||||
|
{:error, {:unexpected_status, status}}
|
||||||
|
|
||||||
|
{:error, error} ->
|
||||||
|
Logger.warning("PagerDuty API error: #{inspect(error)}")
|
||||||
|
{:error, error}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp dedup_key(alert), do: "towerops-alert-#{alert.id}"
|
||||||
|
|
||||||
|
defp alert_summary(alert, device) do
|
||||||
|
name = device.hostname || device.ip_address
|
||||||
|
|
||||||
|
case alert.alert_type do
|
||||||
|
:device_down -> "Device down: #{name}"
|
||||||
|
:device_up -> "Device recovered: #{name}"
|
||||||
|
other -> "Alert: #{other} on #{name}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp severity(:device_down), do: "critical"
|
||||||
|
defp severity(:device_up), do: "info"
|
||||||
|
defp severity(_), do: "warning"
|
||||||
|
end
|
||||||
84
lib/towerops/pagerduty/notifier.ex
Normal file
84
lib/towerops/pagerduty/notifier.ex
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
defmodule Towerops.PagerDuty.Notifier do
|
||||||
|
@moduledoc "Sends alert events to PagerDuty when integration is configured."
|
||||||
|
|
||||||
|
alias Towerops.Integrations
|
||||||
|
alias Towerops.PagerDuty.Client
|
||||||
|
alias Towerops.Repo
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
def notify_trigger(alert, device) do
|
||||||
|
with {:ok, routing_key} <- get_routing_key(device.organization_id) do
|
||||||
|
site_name = get_site_name(device)
|
||||||
|
towerops_url = device_url(device)
|
||||||
|
|
||||||
|
case Client.trigger(routing_key, alert, device, site_name, towerops_url) do
|
||||||
|
{:ok, _} ->
|
||||||
|
Logger.info("PagerDuty incident triggered for alert #{alert.id}")
|
||||||
|
:ok
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
Logger.warning("PagerDuty trigger failed for alert #{alert.id}: #{inspect(reason)}")
|
||||||
|
:error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def notify_acknowledge(alert) do
|
||||||
|
alert = Repo.preload(alert, device: :site)
|
||||||
|
|
||||||
|
with {:ok, routing_key} <- get_routing_key(alert.device.organization_id) do
|
||||||
|
case Client.acknowledge(routing_key, alert) do
|
||||||
|
{:ok, _} ->
|
||||||
|
Logger.info("PagerDuty incident acknowledged for alert #{alert.id}")
|
||||||
|
:ok
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
Logger.warning(
|
||||||
|
"PagerDuty acknowledge failed for alert #{alert.id}: #{inspect(reason)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
:error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def notify_resolve(alert) do
|
||||||
|
alert = Repo.preload(alert, device: :site)
|
||||||
|
|
||||||
|
with {:ok, routing_key} <- get_routing_key(alert.device.organization_id) do
|
||||||
|
case Client.resolve(routing_key, alert) do
|
||||||
|
{:ok, _} ->
|
||||||
|
Logger.info("PagerDuty incident resolved for alert #{alert.id}")
|
||||||
|
:ok
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
Logger.warning("PagerDuty resolve failed for alert #{alert.id}: #{inspect(reason)}")
|
||||||
|
:error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_routing_key(organization_id) do
|
||||||
|
case Integrations.get_integration(organization_id, "pagerduty") do
|
||||||
|
{:ok, %{enabled: true, credentials: %{"api_key" => key}}} when key != "" ->
|
||||||
|
{:ok, key}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, :not_configured}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_site_name(device) do
|
||||||
|
device = Repo.preload(device, :site)
|
||||||
|
|
||||||
|
case device.site do
|
||||||
|
nil -> "Unknown Site"
|
||||||
|
site -> site.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp device_url(device) do
|
||||||
|
ToweropsWeb.Endpoint.url() <> "/devices/#{device.id}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -202,7 +202,7 @@ defmodule Towerops.Workers.DeviceMonitorWorker do
|
||||||
defp create_device_down_alert(device, now) do
|
defp create_device_down_alert(device, now) do
|
||||||
alert_message = get_down_alert_message(device)
|
alert_message = get_down_alert_message(device)
|
||||||
|
|
||||||
{:ok, _alert} =
|
{:ok, alert} =
|
||||||
Alerts.create_alert(%{
|
Alerts.create_alert(%{
|
||||||
device_id: device.id,
|
device_id: device.id,
|
||||||
alert_type: :device_down,
|
alert_type: :device_down,
|
||||||
|
|
@ -210,6 +210,8 @@ defmodule Towerops.Workers.DeviceMonitorWorker do
|
||||||
message: alert_message
|
message: alert_message
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Task.start(fn -> Towerops.PagerDuty.Notifier.notify_trigger(alert, device) end)
|
||||||
|
|
||||||
_ =
|
_ =
|
||||||
Phoenix.PubSub.broadcast(
|
Phoenix.PubSub.broadcast(
|
||||||
Towerops.PubSub,
|
Towerops.PubSub,
|
||||||
|
|
@ -229,7 +231,7 @@ defmodule Towerops.Workers.DeviceMonitorWorker do
|
||||||
defp handle_equipment_up(device, now) do
|
defp handle_equipment_up(device, now) do
|
||||||
recovery_message = get_recovery_message(device)
|
recovery_message = get_recovery_message(device)
|
||||||
|
|
||||||
{:ok, _alert} =
|
{:ok, alert} =
|
||||||
Alerts.create_alert(%{
|
Alerts.create_alert(%{
|
||||||
device_id: device.id,
|
device_id: device.id,
|
||||||
alert_type: :device_up,
|
alert_type: :device_up,
|
||||||
|
|
@ -237,6 +239,8 @@ defmodule Towerops.Workers.DeviceMonitorWorker do
|
||||||
message: recovery_message
|
message: recovery_message
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Task.start(fn -> Towerops.PagerDuty.Notifier.notify_trigger(alert, device) end)
|
||||||
|
|
||||||
resolve_down_alert(device)
|
resolve_down_alert(device)
|
||||||
|
|
||||||
_ =
|
_ =
|
||||||
|
|
|
||||||
|
|
@ -1756,6 +1756,83 @@ defmodule ToweropsWeb.HelpLive.Index do
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-8 mb-3">
|
||||||
|
PagerDuty
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">
|
||||||
|
<a href="https://www.pagerduty.com" class="underline hover:text-blue-900 dark:hover:text-blue-200 text-blue-700 dark:text-blue-300">PagerDuty</a>
|
||||||
|
is an incident management and on-call alerting platform. The PagerDuty integration provides
|
||||||
|
2-way alert sync — when a device goes down in Towerops, a PagerDuty incident is automatically
|
||||||
|
triggered. Acknowledging or resolving the alert in Towerops updates the PagerDuty incident as well.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4 class="text-base font-semibold text-gray-900 dark:text-white mt-4 mb-2">
|
||||||
|
How It Works
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<ul class="space-y-2 text-gray-600 dark:text-gray-400 list-disc list-inside">
|
||||||
|
<li>Device goes down → PagerDuty incident triggered (critical severity)</li>
|
||||||
|
<li>Alert acknowledged in Towerops → PagerDuty incident acknowledged</li>
|
||||||
|
<li>Device recovers / alert resolved → PagerDuty incident resolved</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h4 class="text-base font-semibold text-gray-900 dark:text-white mt-4 mb-2">
|
||||||
|
Configuration
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<div class="flex items-center justify-center w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 font-semibold">
|
||||||
|
1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<h4 class="text-base font-semibold text-gray-900 dark:text-white mb-2">
|
||||||
|
Create an Events API v2 Integration in PagerDuty
|
||||||
|
</h4>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
In PagerDuty, go to <.code>Services</.code> → select your service → <.code>Integrations</.code> tab → <.code>Add Integration</.code> → choose <.code>Events API v2</.code>. Copy the <.code>Integration Key</.code> (routing key).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<div class="flex items-center justify-center w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 font-semibold">
|
||||||
|
2
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<h4 class="text-base font-semibold text-gray-900 dark:text-white mb-2">
|
||||||
|
Configure in Towerops
|
||||||
|
</h4>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
Navigate to <.code>Organization Settings</.code> → <.code>Integrations</.code> tab → click <.code>Configure</.code> on PagerDuty. Paste your integration key and test the connection.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
|
||||||
|
<div class="flex gap-3">
|
||||||
|
<.icon
|
||||||
|
name="hero-information-circle"
|
||||||
|
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<h4 class="text-sm font-semibold text-blue-900 dark:text-blue-300 mb-1">
|
||||||
|
Event-Driven
|
||||||
|
</h4>
|
||||||
|
<p class="text-sm text-blue-800 dark:text-blue-300">
|
||||||
|
Unlike other integrations, PagerDuty is event-driven — there is no periodic sync.
|
||||||
|
Alerts are sent to PagerDuty in real time as they occur. No polling or sync interval is needed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-8 mb-3">
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-8 mb-3">
|
||||||
General Integration Features
|
General Integration Features
|
||||||
</h3>
|
</h3>
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,13 @@ defmodule ToweropsWeb.Org.IntegrationsLive do
|
||||||
description:
|
description:
|
||||||
"Billing and subscriber management. Enables outage impact analysis, inventory reconciliation, and subscriber-aware monitoring.",
|
"Billing and subscriber management. Enables outage impact analysis, inventory reconciliation, and subscriber-aware monitoring.",
|
||||||
icon: "hero-user-group"
|
icon: "hero-user-group"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
id: "pagerduty",
|
||||||
|
name: "PagerDuty",
|
||||||
|
description:
|
||||||
|
"Incident management and on-call alerting. Automatically triggers, acknowledges, and resolves PagerDuty incidents from TowerOps alerts.",
|
||||||
|
icon: "hero-bell-alert"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,13 @@ defmodule ToweropsWeb.Org.SettingsLive do
|
||||||
description:
|
description:
|
||||||
"Billing and subscriber management. Enables outage impact analysis, inventory reconciliation, and subscriber-aware monitoring.",
|
"Billing and subscriber management. Enables outage impact analysis, inventory reconciliation, and subscriber-aware monitoring.",
|
||||||
icon: "hero-user-group"
|
icon: "hero-user-group"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
id: "pagerduty",
|
||||||
|
name: "PagerDuty",
|
||||||
|
description:
|
||||||
|
"Incident management and on-call alerting. Automatically triggers, acknowledges, and resolves PagerDuty incidents from TowerOps alerts.",
|
||||||
|
icon: "hero-bell-alert"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -395,6 +402,10 @@ defmodule ToweropsWeb.Org.SettingsLive do
|
||||||
|
|
||||||
defp test_provider_connection("preseem", api_key), do: PreseemClient.test_connection(api_key)
|
defp test_provider_connection("preseem", api_key), do: PreseemClient.test_connection(api_key)
|
||||||
defp test_provider_connection("gaiia", api_key), do: GaiiaClient.test_connection(api_key)
|
defp test_provider_connection("gaiia", api_key), do: GaiiaClient.test_connection(api_key)
|
||||||
|
|
||||||
|
defp test_provider_connection("pagerduty", api_key),
|
||||||
|
do: Towerops.PagerDuty.Client.test_connection(api_key)
|
||||||
|
|
||||||
defp test_provider_connection(_, _api_key), do: {:error, "Unknown provider"}
|
defp test_provider_connection(_, _api_key), do: {:error, "Unknown provider"}
|
||||||
|
|
||||||
defp load_integrations(organization_id) do
|
defp load_integrations(organization_id) do
|
||||||
|
|
|
||||||
|
|
@ -840,6 +840,9 @@
|
||||||
<%= if provider.id == "gaiia" do %>
|
<%= if provider.id == "gaiia" do %>
|
||||||
Syncs every {integration.sync_interval_minutes} minutes. Reconciliation runs nightly. Also receives real-time webhook updates.
|
Syncs every {integration.sync_interval_minutes} minutes. Reconciliation runs nightly. Also receives real-time webhook updates.
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<%= if provider.id == "pagerduty" do %>
|
||||||
|
Event-driven — alerts are sent to PagerDuty in real time when devices go down, are acknowledged, or recover.
|
||||||
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -894,24 +897,32 @@
|
||||||
<.input
|
<.input
|
||||||
field={@integration_form[:api_key]}
|
field={@integration_form[:api_key]}
|
||||||
type="password"
|
type="password"
|
||||||
label="API Key"
|
label={if(provider.id == "pagerduty", do: "Integration Key (Routing Key)", else: "API Key")}
|
||||||
placeholder={"Enter your #{provider.name} API key"}
|
placeholder={if(provider.id == "pagerduty", do: "Enter your PagerDuty integration key", else: "Enter your #{provider.name} API key")}
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
value={get_credential(@integrations[provider.id], "api_key")}
|
value={get_credential(@integrations[provider.id], "api_key")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<.input
|
<%= if provider.id == "pagerduty" do %>
|
||||||
field={@integration_form[:sync_interval_minutes]}
|
<p class="text-xs text-gray-500 dark:text-gray-400 -mt-2">
|
||||||
type="number"
|
Find this in PagerDuty → Services → Your Service → Integrations → Events API v2
|
||||||
label="Sync interval (minutes)"
|
</p>
|
||||||
min="5"
|
<% end %>
|
||||||
value={
|
|
||||||
if(@integrations[provider.id],
|
<%= if provider.id != "pagerduty" do %>
|
||||||
do: @integrations[provider.id].sync_interval_minutes,
|
<.input
|
||||||
else: if(provider.id == "gaiia", do: 15, else: 10)
|
field={@integration_form[:sync_interval_minutes]}
|
||||||
)
|
type="number"
|
||||||
}
|
label="Sync interval (minutes)"
|
||||||
/>
|
min="5"
|
||||||
|
value={
|
||||||
|
if(@integrations[provider.id],
|
||||||
|
do: @integrations[provider.id].sync_interval_minutes,
|
||||||
|
else: if(provider.id == "gaiia", do: 15, else: 10)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<%= if @test_result do %>
|
<%= if @test_result do %>
|
||||||
<div class={[
|
<div class={[
|
||||||
|
|
|
||||||
45
test/towerops/pagerduty/client_test.exs
Normal file
45
test/towerops/pagerduty/client_test.exs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
defmodule Towerops.PagerDuty.ClientTest do
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
alias Towerops.PagerDuty.Client
|
||||||
|
|
||||||
|
describe "trigger/5" do
|
||||||
|
test "builds correct event body" do
|
||||||
|
alert = %{
|
||||||
|
id: "abc-123",
|
||||||
|
alert_type: :device_down,
|
||||||
|
triggered_at: ~U[2024-01-15 10:30:00Z],
|
||||||
|
message: "Device is not responding to ping"
|
||||||
|
}
|
||||||
|
|
||||||
|
device = %{
|
||||||
|
id: "dev-456",
|
||||||
|
hostname: "router-01",
|
||||||
|
ip_address: "10.0.0.1",
|
||||||
|
organization_id: "org-789"
|
||||||
|
}
|
||||||
|
|
||||||
|
# We can't easily test the HTTP call without mocking,
|
||||||
|
# but we verify the function exists and accepts the right arity
|
||||||
|
assert is_function(&Client.trigger/5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "acknowledge/2" do
|
||||||
|
test "accepts routing key and alert" do
|
||||||
|
assert is_function(&Client.acknowledge/2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "resolve/2" do
|
||||||
|
test "accepts routing key and alert" do
|
||||||
|
assert is_function(&Client.resolve/2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "test_connection/1" do
|
||||||
|
test "accepts routing key" do
|
||||||
|
assert is_function(&Client.test_connection/1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Reference in a new issue