Transform the dashboard into a single-pane-of-glass command center that blends operational metrics with business context from Gaiia subscriber data. - Extend insight schema with multi-source support (snmp, gaiia, system) - Add Oban workers for automated insight generation (device health, system, gaiia) - New Dashboard context with health score computation and site summaries - Rewrite dashboard LiveView with health overview, alert/insight feeds, site grid - Add source filter pills for insights with URL state management - Add metrics bar to site detail pages (device count, alerts, subscribers, MRR) - Add subscriber/MRR context to alert list site links - Add subscriber badges to device list site headers - Real-time PubSub updates for alerts on dashboard
174 lines
5.2 KiB
Elixir
174 lines
5.2 KiB
Elixir
defmodule Towerops.DashboardTest do
|
|
use Towerops.DataCase, async: true
|
|
|
|
import Towerops.AccountsFixtures
|
|
import Towerops.DevicesFixtures
|
|
import Towerops.OrganizationsFixtures
|
|
|
|
alias Towerops.Dashboard
|
|
alias Towerops.Gaiia
|
|
|
|
setup do
|
|
user = user_fixture()
|
|
org = organization_fixture(user.id)
|
|
|
|
%{org: org, user: user}
|
|
end
|
|
|
|
describe "get_dashboard_summary/1" do
|
|
test "returns correct structure with empty org", %{org: org} do
|
|
summary = Dashboard.get_dashboard_summary(org.id)
|
|
|
|
assert is_map(summary)
|
|
assert Map.has_key?(summary, :health_score)
|
|
assert Map.has_key?(summary, :devices)
|
|
assert Map.has_key?(summary, :alerts)
|
|
assert Map.has_key?(summary, :subscribers)
|
|
assert Map.has_key?(summary, :insights)
|
|
end
|
|
|
|
test "returns device counts", %{org: org} do
|
|
device = device_fixture(%{organization_id: org.id})
|
|
Towerops.Devices.update_device_status(device, :up)
|
|
|
|
summary = Dashboard.get_dashboard_summary(org.id)
|
|
|
|
assert summary.devices.total == 1
|
|
assert summary.devices.up == 1
|
|
assert summary.devices.down == 0
|
|
end
|
|
|
|
test "returns alert count", %{org: org} do
|
|
device = device_fixture(%{organization_id: org.id})
|
|
|
|
{:ok, _alert} =
|
|
Towerops.Alerts.create_alert(%{
|
|
device_id: device.id,
|
|
alert_type: :device_down,
|
|
triggered_at: DateTime.utc_now(),
|
|
message: "Device is down"
|
|
})
|
|
|
|
summary = Dashboard.get_dashboard_summary(org.id)
|
|
assert summary.alerts.active_count == 1
|
|
end
|
|
|
|
test "returns subscriber totals from Gaiia", %{org: org} do
|
|
{:ok, _} =
|
|
Gaiia.upsert_network_site(org.id, %{
|
|
gaiia_id: "ns-dash",
|
|
name: "Tower",
|
|
account_count: 100,
|
|
total_mrr: Decimal.new("5000.00")
|
|
})
|
|
|
|
summary = Dashboard.get_dashboard_summary(org.id)
|
|
assert summary.subscribers.total == 100
|
|
assert Decimal.equal?(summary.subscribers.total_mrr, Decimal.new("5000.00"))
|
|
end
|
|
|
|
test "returns zero subscribers when no Gaiia data", %{org: org} do
|
|
summary = Dashboard.get_dashboard_summary(org.id)
|
|
assert summary.subscribers.total == 0
|
|
end
|
|
|
|
test "returns insight counts by urgency", %{org: org} do
|
|
summary = Dashboard.get_dashboard_summary(org.id)
|
|
assert summary.insights.critical == 0
|
|
assert summary.insights.warning == 0
|
|
assert summary.insights.info == 0
|
|
end
|
|
end
|
|
|
|
describe "compute_health_score/1" do
|
|
test "returns 100 when all devices up, no alerts", %{org: org} do
|
|
device = device_fixture(%{organization_id: org.id})
|
|
Towerops.Devices.update_device_status(device, :up)
|
|
|
|
score = Dashboard.compute_health_score(org.id)
|
|
assert score == 100
|
|
end
|
|
|
|
test "returns lower score when devices down", %{org: org} do
|
|
d1 = device_fixture(%{organization_id: org.id})
|
|
d2 = device_fixture(%{organization_id: org.id})
|
|
Towerops.Devices.update_device_status(d1, :up)
|
|
Towerops.Devices.update_device_status(d2, :down)
|
|
|
|
score = Dashboard.compute_health_score(org.id)
|
|
assert score < 100
|
|
assert score > 0
|
|
end
|
|
|
|
test "returns 100 when no devices exist", %{org: org} do
|
|
score = Dashboard.compute_health_score(org.id)
|
|
assert score == 100
|
|
end
|
|
|
|
test "penalizes for critical alerts", %{org: org} do
|
|
device = device_fixture(%{organization_id: org.id})
|
|
Towerops.Devices.update_device_status(device, :up)
|
|
|
|
{:ok, _} =
|
|
Towerops.Alerts.create_alert(%{
|
|
device_id: device.id,
|
|
alert_type: :device_down,
|
|
triggered_at: DateTime.utc_now(),
|
|
message: "Device down"
|
|
})
|
|
|
|
score = Dashboard.compute_health_score(org.id)
|
|
assert score < 100
|
|
end
|
|
end
|
|
|
|
describe "get_org_subscriber_summary/1" do
|
|
test "returns totals from Gaiia network sites", %{org: org} do
|
|
{:ok, _} =
|
|
Gaiia.upsert_network_site(org.id, %{
|
|
gaiia_id: "ns-sub1",
|
|
name: "Tower A",
|
|
account_count: 50,
|
|
total_mrr: Decimal.new("2500.00")
|
|
})
|
|
|
|
{:ok, _} =
|
|
Gaiia.upsert_network_site(org.id, %{
|
|
gaiia_id: "ns-sub2",
|
|
name: "Tower B",
|
|
account_count: 30,
|
|
total_mrr: Decimal.new("1500.00")
|
|
})
|
|
|
|
result = Dashboard.get_org_subscriber_summary(org.id)
|
|
assert result.total == 80
|
|
assert Decimal.equal?(result.total_mrr, Decimal.new("4000.00"))
|
|
end
|
|
|
|
test "returns zeros when no Gaiia data", %{org: org} do
|
|
result = Dashboard.get_org_subscriber_summary(org.id)
|
|
assert result.total == 0
|
|
assert Decimal.equal?(result.total_mrr, Decimal.new(0))
|
|
end
|
|
end
|
|
|
|
describe "list_site_summaries/1" do
|
|
test "returns enriched site data", %{org: org} do
|
|
{:ok, site} =
|
|
Towerops.Sites.create_site(%{
|
|
name: "Test Tower",
|
|
organization_id: org.id
|
|
})
|
|
|
|
device = device_fixture(%{organization_id: org.id, site_id: site.id})
|
|
Towerops.Devices.update_device_status(device, :up)
|
|
|
|
summaries = Dashboard.list_site_summaries(org.id)
|
|
assert summaries != []
|
|
|
|
summary = Enum.find(summaries, &(&1.site_id == site.id))
|
|
assert summary.name == "Test Tower"
|
|
assert summary.device_count >= 1
|
|
end
|
|
end
|
|
end
|