2026-04-20 19:26:16 +02:00
import type { ConditionKind } from '../../pages/Alerts/enums' ;
2026-04-20 13:31:55 +02:00
export type VariableType =
| 'string'
| 'Instant'
| 'number'
| 'boolean'
| 'url'
| 'uuid' ;
export interface AlertVariable {
path : string ; // e.g. "alert.firedAt"
type : VariableType ;
description : string ;
sampleValue : string ; // rendered as a faint suggestion preview
availableForKinds : 'always' | ConditionKind [ ] ;
mayBeNull? : boolean ; // show "may be null" badge in UI
}
/ * * V a r i a b l e s t h e s p e c § 8 c o n t e x t m a p e x p o s e s . A d d t o t h i s r e g i s t r y w h e n e v e r
* NotificationContextBuilder ( backend ) gains a new leaf . * /
export const ALERT_VARIABLES : AlertVariable [ ] = [
// Always available
{ path : 'env.slug' , type : 'string' , description : 'Environment slug' , sampleValue : 'prod' , availableForKinds : 'always' } ,
{ path : 'env.id' , type : 'uuid' , description : 'Environment UUID' , sampleValue : '00000000-0000-0000-0000-000000000001' , availableForKinds : 'always' } ,
{ path : 'rule.id' , type : 'uuid' , description : 'Rule UUID' , sampleValue : '11111111-...' , availableForKinds : 'always' } ,
{ path : 'rule.name' , type : 'string' , description : 'Rule display name' , sampleValue : 'Order API error rate' , availableForKinds : 'always' } ,
{ path : 'rule.severity' , type : 'string' , description : 'Rule severity' , sampleValue : 'CRITICAL' , availableForKinds : 'always' } ,
{ path : 'rule.description' , type : 'string' , description : 'Rule description' , sampleValue : 'Paging ops if error rate >5%' , availableForKinds : 'always' } ,
{ path : 'alert.id' , type : 'uuid' , description : 'Alert instance UUID' , sampleValue : '22222222-...' , availableForKinds : 'always' } ,
{ path : 'alert.state' , type : 'string' , description : 'Alert state' , sampleValue : 'FIRING' , availableForKinds : 'always' } ,
{ path : 'alert.firedAt' , type : 'Instant' , description : 'When the alert fired' , sampleValue : '2026-04-20T14:33:10Z' , availableForKinds : 'always' } ,
{ path : 'alert.resolvedAt' , type : 'Instant' , description : 'When the alert resolved' , sampleValue : '2026-04-20T14:45:00Z' , availableForKinds : 'always' , mayBeNull : true } ,
{ path : 'alert.ackedBy' , type : 'string' , description : 'User who ack\'d the alert' , sampleValue : 'alice' , availableForKinds : 'always' , mayBeNull : true } ,
{ path : 'alert.link' , type : 'url' , description : 'UI link to this alert' , sampleValue : 'https://cameleer.example.com/alerts/inbox/2222...' , availableForKinds : 'always' } ,
{ path : 'alert.currentValue' , type : 'number' , description : 'Observed metric value' , sampleValue : '0.12' , availableForKinds : 'always' , mayBeNull : true } ,
{ path : 'alert.threshold' , type : 'number' , description : 'Rule threshold' , sampleValue : '0.05' , availableForKinds : 'always' , mayBeNull : true } ,
2026-04-20 13:35:42 +02:00
// App subtree — populated on every kind except env-wide rules
{ path : 'app.slug' , type : 'string' , description : 'App slug' , sampleValue : 'orders' ,
availableForKinds : [ 'ROUTE_METRIC' , 'EXCHANGE_MATCH' , 'AGENT_STATE' , 'DEPLOYMENT_STATE' , 'LOG_PATTERN' , 'JVM_METRIC' ] , mayBeNull : true } ,
{ path : 'app.id' , type : 'uuid' , description : 'App UUID' , sampleValue : '33333333-...' ,
availableForKinds : [ 'ROUTE_METRIC' , 'EXCHANGE_MATCH' , 'AGENT_STATE' , 'DEPLOYMENT_STATE' , 'LOG_PATTERN' , 'JVM_METRIC' ] , mayBeNull : true } ,
2026-04-20 13:31:55 +02:00
2026-04-20 13:35:42 +02:00
// ROUTE_METRIC + EXCHANGE_MATCH share route.*
{ path : 'route.id' , type : 'string' , description : 'Route ID' , sampleValue : 'route-1' ,
availableForKinds : [ 'ROUTE_METRIC' , 'EXCHANGE_MATCH' ] } ,
{ path : 'route.uri' , type : 'string' , description : 'Route URI' , sampleValue : 'direct:orders' ,
availableForKinds : [ 'ROUTE_METRIC' , 'EXCHANGE_MATCH' ] } ,
2026-04-20 13:31:55 +02:00
// EXCHANGE_MATCH
2026-04-20 13:35:42 +02:00
{ path : 'exchange.id' , type : 'string' , description : 'Exchange ID' , sampleValue : 'exch-ab12' ,
availableForKinds : [ 'EXCHANGE_MATCH' ] } ,
{ path : 'exchange.status' , type : 'string' , description : 'Exchange status' , sampleValue : 'FAILED' ,
availableForKinds : [ 'EXCHANGE_MATCH' ] } ,
2026-04-20 13:31:55 +02:00
2026-04-20 13:35:42 +02:00
// AGENT_STATE + JVM_METRIC share agent.id/name; AGENT_STATE adds agent.state
{ path : 'agent.id' , type : 'string' , description : 'Agent instance ID' , sampleValue : 'prod-orders-0' ,
availableForKinds : [ 'AGENT_STATE' , 'JVM_METRIC' ] } ,
{ path : 'agent.name' , type : 'string' , description : 'Agent display name' , sampleValue : 'orders-0' ,
availableForKinds : [ 'AGENT_STATE' , 'JVM_METRIC' ] } ,
{ path : 'agent.state' , type : 'string' , description : 'Agent state' , sampleValue : 'DEAD' ,
availableForKinds : [ 'AGENT_STATE' ] } ,
2026-04-20 13:31:55 +02:00
// DEPLOYMENT_STATE
2026-04-20 13:35:42 +02:00
{ path : 'deployment.id' , type : 'uuid' , description : 'Deployment UUID' , sampleValue : '44444444-...' ,
availableForKinds : [ 'DEPLOYMENT_STATE' ] } ,
{ path : 'deployment.status' , type : 'string' , description : 'Deployment status' , sampleValue : 'FAILED' ,
availableForKinds : [ 'DEPLOYMENT_STATE' ] } ,
2026-04-20 13:31:55 +02:00
2026-04-20 13:35:42 +02:00
// LOG_PATTERN — leaf names match NotificationContextBuilder (log.pattern + log.matchCount)
{ path : 'log.pattern' , type : 'string' , description : 'Matched log pattern' , sampleValue : 'TimeoutException' ,
availableForKinds : [ 'LOG_PATTERN' ] } ,
{ path : 'log.matchCount' , type : 'number' , description : 'Matches in window' , sampleValue : '7' ,
availableForKinds : [ 'LOG_PATTERN' ] } ,
2026-04-20 13:31:55 +02:00
// JVM_METRIC
2026-04-20 13:35:42 +02:00
{ path : 'metric.name' , type : 'string' , description : 'Metric name' , sampleValue : 'heap_used_percent' ,
availableForKinds : [ 'JVM_METRIC' ] } ,
{ path : 'metric.value' , type : 'number' , description : 'Metric value' , sampleValue : '92.1' ,
availableForKinds : [ 'JVM_METRIC' ] } ,
2026-04-20 13:31:55 +02:00
] ;
/ * * F i l t e r v a r i a b l e s t o t h o s e a v a i l a b l e f o r t h e g i v e n c o n d i t i o n k i n d .
* If kind is undefined ( e . g . connection URL editor ) , returns only "always" vars + app . * . * /
export function availableVariables (
kind : ConditionKind | undefined ,
opts : { reducedContext? : boolean } = { } ,
) : AlertVariable [ ] {
if ( opts . reducedContext ) {
return ALERT_VARIABLES . filter ( ( v ) = > v . path . startsWith ( 'env.' ) ) ;
}
if ( ! kind ) {
return ALERT_VARIABLES . filter (
( v ) = > v . availableForKinds === 'always' ,
) ;
}
return ALERT_VARIABLES . filter (
( v ) = > v . availableForKinds === 'always' || v . availableForKinds . includes ( kind ) ,
) ;
}
/ * * P a r s e a M u s t a c h e t e m p l a t e a n d r e t u r n t h e s e t o f ` { { p a t h } } ` r e f e r e n c e s i t c o n t a i n s .
* Ignores ` {{#section}} ` / ` {{/section}} ` / ` {{!comment}} ` — plain variable refs only . * /
export function extractReferences ( template : string ) : string [ ] {
const out : string [ ] = [ ] ;
const re = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_.]*)\s*\}\}/g ;
let m ;
while ( ( m = re . exec ( template ) ) !== null ) out . push ( m [ 1 ] ) ;
return out ;
}
/** Find references in a template that are not in the allowed-variable set. */
export function unknownReferences (
template : string ,
allowed : readonly AlertVariable [ ] ,
) : string [ ] {
const allowedSet = new Set ( allowed . map ( ( v ) = > v . path ) ) ;
return extractReferences ( template ) . filter ( ( r ) = > ! allowedSet . has ( r ) ) ;
}