Promoted pure presentation and utility helpers from `defp` to `def @doc false` across ~20 LiveViews, Oban workers, and sync modules so they're reachable from unit tests. Refactored several `cond` blocks into idiomatic function heads with guards. Added ~250 new test cases in new files under test/towerops and test/towerops_web, including DB-backed tests for CnMaestro.Sync and AlertNotificationWorker, and removed dead LiveView tab components and CapacityLive (no callers anywhere in lib/test). Configured mix.exs test_coverage.ignore_modules to exclude vendored third-party code (SnmpKit, protobuf-generated Towerops.Agent.*, Absinthe GraphQL types, Phoenix HTML modules, Inspect protocol impls) from coverage calculations — these are not our project code. Coverage: 66.93% → 70.09%. Full suite: 10,127 tests, 0 failures.
142 lines
4.3 KiB
Elixir
142 lines
4.3 KiB
Elixir
defmodule ToweropsWeb.ReportsLive do
|
|
@moduledoc """
|
|
Scheduled Reports management page.
|
|
|
|
Lists existing reports and provides a form to create new ones.
|
|
"""
|
|
use ToweropsWeb, :live_view
|
|
|
|
alias Towerops.Reports
|
|
alias Towerops.Reports.Report
|
|
alias Towerops.Workers.ReportWorker
|
|
|
|
@impl true
|
|
def mount(_params, _session, socket) do
|
|
organization = socket.assigns.current_scope.organization
|
|
reports = Reports.list_reports(organization.id)
|
|
|
|
{:ok,
|
|
socket
|
|
|> assign(:page_title, "Reports")
|
|
|> assign(:organization_id, organization.id)
|
|
|> assign(:reports, reports)
|
|
|> assign(:show_form, false)
|
|
|> assign(:form, to_form(Report.changeset(%Report{}, %{})))}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("show_form", _, socket) do
|
|
{:noreply, assign(socket, show_form: true)}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("hide_form", _, socket) do
|
|
{:noreply, assign(socket, show_form: false)}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("save", %{"report" => params}, socket) do
|
|
recipients =
|
|
(params["recipients_text"] || "")
|
|
|> String.split([",", "\n", ";"])
|
|
|> Enum.map(&String.trim/1)
|
|
|> Enum.reject(&(&1 == ""))
|
|
|
|
attrs =
|
|
params
|
|
|> Map.put("recipients", recipients)
|
|
|> Map.put("organization_id", socket.assigns.organization_id)
|
|
|> Map.put("created_by_id", socket.assigns.current_scope.user.id)
|
|
|> Map.put("schedule", %{"type" => params["schedule_type"] || "weekly"})
|
|
|> Map.put("scope", %{
|
|
"target" => params["scope_target"] || "all",
|
|
"days" => String.to_integer(params["scope_days"] || "7")
|
|
})
|
|
|
|
case Reports.create_report(attrs) do
|
|
{:ok, _report} ->
|
|
reports = Reports.list_reports(socket.assigns.organization_id)
|
|
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:info, "Report created successfully")
|
|
|> assign(:reports, reports)
|
|
|> assign(:show_form, false)
|
|
|> assign(:form, to_form(Report.changeset(%Report{}, %{})))}
|
|
|
|
{:error, changeset} ->
|
|
{:noreply, assign(socket, form: to_form(changeset))}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("toggle", %{"id" => id}, socket) do
|
|
report = Reports.get_report!(id)
|
|
|
|
case Reports.toggle_report(report) do
|
|
{:ok, _} ->
|
|
reports = Reports.list_reports(socket.assigns.organization_id)
|
|
{:noreply, assign(socket, reports: reports)}
|
|
|
|
{:error, _} ->
|
|
{:noreply, put_flash(socket, :error, "Failed to toggle report")}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("delete", %{"id" => id}, socket) do
|
|
report = Reports.get_report!(id)
|
|
|
|
case Reports.delete_report(report) do
|
|
{:ok, _} ->
|
|
reports = Reports.list_reports(socket.assigns.organization_id)
|
|
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:info, "Report deleted")
|
|
|> assign(:reports, reports)}
|
|
|
|
{:error, _} ->
|
|
{:noreply, put_flash(socket, :error, "Failed to delete report")}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("run_now", %{"id" => id}, socket) do
|
|
case %{"report_id" => id}
|
|
|> ReportWorker.new()
|
|
|> Oban.insert() do
|
|
{:ok, _} ->
|
|
{:noreply, put_flash(socket, :info, "Report queued for delivery")}
|
|
|
|
{:error, _} ->
|
|
{:noreply, put_flash(socket, :error, "Failed to queue report")}
|
|
end
|
|
end
|
|
|
|
# -- Helpers --
|
|
|
|
@doc false
|
|
def humanize_type("uptime_summary"), do: "Uptime Summary"
|
|
def humanize_type("alert_history"), do: "Alert History"
|
|
def humanize_type("capacity_trends"), do: "Capacity Trends"
|
|
def humanize_type("rf_link_health"), do: "RF Link Health"
|
|
def humanize_type(type), do: type
|
|
|
|
@doc false
|
|
def humanize_schedule(%{"type" => type}), do: String.capitalize(type)
|
|
def humanize_schedule(_), do: "-"
|
|
|
|
@doc false
|
|
def format_recipients(recipients) when is_list(recipients), do: Enum.join(recipients, ", ")
|
|
def format_recipients(_), do: "-"
|
|
|
|
@doc false
|
|
def status_badge("success"), do: {"Success", "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300"}
|
|
def status_badge("failed"), do: {"Failed", "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300"}
|
|
|
|
def status_badge("partial_failure"),
|
|
do: {"Partial", "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-300"}
|
|
|
|
def status_badge(_), do: {"Never Run", "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300"}
|
|
end
|