@@ -2,12 +2,11 @@ import { and, db, eq } from "@databuddy/db";
22import { alarms } from "@databuddy/db/schema" ;
33import {
44 buildAnomalyNotificationPayload ,
5- type NotificationChannel ,
65 NotificationClient ,
76} from "@databuddy/notifications" ;
87import { z } from "zod" ;
8+ import { toNotificationConfig } from "../lib/alarm-notifications" ;
99import {
10- type AnomalyDetectionConfig ,
1110 detectAnomalies ,
1211 fetchAnomalyTimeSeries ,
1312} from "../lib/anomaly-detection" ;
@@ -65,26 +64,7 @@ export const anomaliesRouter = {
6564 permissions : [ "read" ] ,
6665 } ) ;
6766
68- const clientId = workspace . website . id ;
69- const config : Partial < AnomalyDetectionConfig > = { } ;
70-
71- if ( input . config ?. warningThreshold !== undefined ) {
72- config . warningThreshold = input . config . warningThreshold ;
73- }
74- if ( input . config ?. criticalThreshold !== undefined ) {
75- config . criticalThreshold = input . config . criticalThreshold ;
76- }
77- if ( input . config ?. baselineDays !== undefined ) {
78- config . baselineDays = input . config . baselineDays ;
79- }
80- if ( input . config ?. minimumBaselineCount !== undefined ) {
81- config . minimumBaselineCount = input . config . minimumBaselineCount ;
82- }
83- if ( input . config ?. percentChangeFallback !== undefined ) {
84- config . percentChangeFallback = input . config . percentChangeFallback ;
85- }
86-
87- return detectAnomalies ( clientId , config ) ;
67+ return detectAnomalies ( workspace . website . id , input . config ?? { } ) ;
8868 } ) ,
8969
9070 timeSeries : protectedProcedure
@@ -157,17 +137,9 @@ export const anomaliesRouter = {
157137 with : { destinations : true } ,
158138 } ) ;
159139
160- const relevantAlarms = matchingAlarms . filter ( ( alarm ) => {
161- if ( alarm . triggerType === "traffic_spike" ) {
162- return detected . some (
163- ( a ) => a . metric === "pageviews" || a . metric === "custom_events"
164- ) ;
165- }
166- if ( alarm . triggerType === "error_rate" ) {
167- return detected . some ( ( a ) => a . metric === "errors" ) ;
168- }
169- return false ;
170- } ) ;
140+ const relevantAlarms = matchingAlarms . filter ( ( alarm ) =>
141+ detected . some ( ( a ) => matchesTrigger ( alarm . triggerType , a . metric ) )
142+ ) ;
171143
172144 let notificationsSent = 0 ;
173145
@@ -179,15 +151,9 @@ export const anomaliesRouter = {
179151 continue ;
180152 }
181153
182- const relevantAnomalies = detected . filter ( ( a ) => {
183- if ( alarm . triggerType === "traffic_spike" ) {
184- return a . metric === "pageviews" || a . metric === "custom_events" ;
185- }
186- if ( alarm . triggerType === "error_rate" ) {
187- return a . metric === "errors" ;
188- }
189- return false ;
190- } ) ;
154+ const relevantAnomalies = detected . filter ( ( a ) =>
155+ matchesTrigger ( alarm . triggerType , a . metric )
156+ ) ;
191157
192158 for ( const anomaly of relevantAnomalies ) {
193159 const payload = buildAnomalyNotificationPayload ( {
@@ -204,9 +170,9 @@ export const anomaliesRouter = {
204170 eventName : anomaly . eventName ,
205171 } ) ;
206172
207- const clientConfig = buildClientConfig ( alarm . destinations ) ;
208- const channels = getChannels ( alarm . destinations ) ;
209-
173+ const { clientConfig, channels } = toNotificationConfig (
174+ alarm . destinations
175+ ) ;
210176 if ( channels . length === 0 ) {
211177 continue ;
212178 }
@@ -224,60 +190,9 @@ export const anomaliesRouter = {
224190 } ) ,
225191} ;
226192
227- interface AlarmDest {
228- config : unknown ;
229- identifier : string ;
230- type : string ;
231- }
232-
233- function buildClientConfig (
234- destinations : AlarmDest [ ]
235- ) : Record < string , Record < string , unknown > > {
236- const config : Record < string , Record < string , unknown > > = { } ;
237-
238- for ( const dest of destinations ) {
239- const cfg = ( dest . config ?? { } ) as Record < string , unknown > ;
240-
241- if ( dest . type === "slack" ) {
242- config . slack = { webhookUrl : dest . identifier } ;
243- } else if ( dest . type === "discord" ) {
244- config . discord = { webhookUrl : dest . identifier } ;
245- } else if ( dest . type === "teams" ) {
246- config . teams = { webhookUrl : dest . identifier } ;
247- } else if ( dest . type === "google_chat" ) {
248- config . googleChat = { webhookUrl : dest . identifier } ;
249- } else if ( dest . type === "telegram" ) {
250- config . telegram = {
251- botToken : cfg . botToken as string ,
252- chatId : dest . identifier || ( cfg . chatId as string ) ,
253- } ;
254- } else if ( dest . type === "webhook" ) {
255- config . webhook = {
256- url : dest . identifier ,
257- headers : cfg . headers as Record < string , string > | undefined ,
258- } ;
259- }
260- }
261-
262- return config ;
263- }
264-
265- function getChannels ( destinations : AlarmDest [ ] ) : NotificationChannel [ ] {
266- const channels : NotificationChannel [ ] = [ ] ;
267- for ( const dest of destinations ) {
268- if ( dest . type === "slack" ) {
269- channels . push ( "slack" ) ;
270- } else if ( dest . type === "discord" ) {
271- channels . push ( "discord" ) ;
272- } else if ( dest . type === "teams" ) {
273- channels . push ( "teams" ) ;
274- } else if ( dest . type === "google_chat" ) {
275- channels . push ( "google-chat" ) ;
276- } else if ( dest . type === "telegram" ) {
277- channels . push ( "telegram" ) ;
278- } else if ( dest . type === "webhook" ) {
279- channels . push ( "webhook" ) ;
280- }
193+ function matchesTrigger ( triggerType : string , metric : string ) : boolean {
194+ if ( triggerType === "traffic_spike" ) {
195+ return metric === "pageviews" || metric === "custom_events" ;
281196 }
282- return channels ;
197+ return triggerType === "error_rate" && metric === "errors" ;
283198}
0 commit comments