@@ -31,6 +31,9 @@ defmodule SentinelCp.Observability do
3131 @ doc "Gets an SLO by ID."
3232 def get_slo ( id ) , do: Repo . get ( Slo , id )
3333
34+ @ doc "Gets an SLO by ID, raising if not found."
35+ def get_slo! ( id ) , do: Repo . get! ( Slo , id )
36+
3437 @ doc "Lists all SLOs for a project."
3538 def list_slos ( project_id ) do
3639 from ( s in Slo , where: s . project_id == ^ project_id , order_by: [ asc: s . name ] )
@@ -43,6 +46,31 @@ defmodule SentinelCp.Observability do
4346 |> Repo . all ( )
4447 end
4548
49+ @ doc "Lists all enabled SLOs across all projects (for the SLI worker)."
50+ def list_all_enabled_slos do
51+ from ( s in Slo , where: s . enabled == true , order_by: [ asc: s . inserted_at ] )
52+ |> Repo . all ( )
53+ end
54+
55+ @ doc "Returns a summary of SLO statuses for a project."
56+ def slo_summary ( project_id ) do
57+ slos = list_slos ( project_id )
58+
59+ Enum . reduce ( slos , % { total: 0 , healthy: 0 , warning: 0 , breached: 0 } , fn slo , acc ->
60+ status = slo_status ( slo )
61+
62+ acc
63+ |> Map . update! ( :total , & ( & 1 + 1 ) )
64+ |> Map . update! ( status , & ( & 1 + 1 ) )
65+ end )
66+ end
67+
68+ @ doc "Returns the status atom for an SLO based on error budget remaining."
69+ def slo_status ( % Slo { error_budget_remaining: nil } ) , do: :healthy
70+ def slo_status ( % Slo { error_budget_remaining: budget } ) when budget >= 50.0 , do: :healthy
71+ def slo_status ( % Slo { error_budget_remaining: budget } ) when budget > 0.0 , do: :warning
72+ def slo_status ( % Slo { } ) , do: :breached
73+
4674 @ doc "Deletes an SLO."
4775 def delete_slo ( slo ) , do: Repo . delete ( slo )
4876
@@ -75,6 +103,9 @@ defmodule SentinelCp.Observability do
75103 @ doc "Gets an alert rule by ID."
76104 def get_alert_rule ( id ) , do: Repo . get ( AlertRule , id )
77105
106+ @ doc "Gets an alert rule by ID, raising if not found."
107+ def get_alert_rule! ( id ) , do: Repo . get! ( AlertRule , id )
108+
78109 @ doc "Lists alert rules for a project."
79110 def list_alert_rules ( project_id ) do
80111 from ( r in AlertRule , where: r . project_id == ^ project_id , order_by: [ asc: r . name ] )
@@ -140,4 +171,31 @@ defmodule SentinelCp.Observability do
140171 )
141172 |> Repo . aggregate ( :count )
142173 end
174+
175+ @ doc "Gets an alert state by ID, raising if not found."
176+ def get_alert_state! ( id ) , do: Repo . get! ( AlertState , id )
177+
178+ @ doc "Lists firing and pending alert states for a project, with preloaded rules."
179+ def list_firing_alerts ( project_id ) do
180+ from ( s in AlertState ,
181+ join: r in AlertRule ,
182+ on: s . alert_rule_id == r . id ,
183+ where: r . project_id == ^ project_id and s . state in [ "firing" , "pending" ] ,
184+ order_by: [ desc: s . started_at ] ,
185+ preload: [ alert_rule: r ]
186+ )
187+ |> Repo . all ( )
188+ end
189+
190+ @ doc "Lists recent alert states for a rule (paginated history)."
191+ def list_recent_alert_states ( alert_rule_id , opts \\ [ ] ) do
192+ limit = Keyword . get ( opts , :limit , 50 )
193+
194+ from ( s in AlertState ,
195+ where: s . alert_rule_id == ^ alert_rule_id ,
196+ order_by: [ desc: s . started_at ] ,
197+ limit: ^ limit
198+ )
199+ |> Repo . all ( )
200+ end
143201end
0 commit comments