@@ -41,12 +43,23 @@ function RoutingPolicyList({
Something went wrong while fetching routing policies.
+ ) : hasSearchTerm ? (
+
No matching routing policies found.
) : (
-
No routing policies found.
+
+ No routing policies yet,{' '}
+
+ Learn more here
+
+
)}
),
- [isRoutingPoliciesError],
+ [isRoutingPoliciesError, hasSearchTerm],
);
return (
diff --git a/frontend/src/container/RoutingPolicies/__tests__/RoutingPoliciesList.test.tsx b/frontend/src/container/RoutingPolicies/__tests__/RoutingPoliciesList.test.tsx
index 62d41fc76cfa..f8adeaa4d9da 100644
--- a/frontend/src/container/RoutingPolicies/__tests__/RoutingPoliciesList.test.tsx
+++ b/frontend/src/container/RoutingPolicies/__tests__/RoutingPoliciesList.test.tsx
@@ -28,6 +28,7 @@ describe('RoutingPoliciesList', () => {
isRoutingPoliciesError={useRoutingPolicesMockData.isErrorRoutingPolicies}
handlePolicyDetailsModalOpen={mockHandlePolicyDetailsModalOpen}
handleDeleteModalOpen={mockHandleDeleteModalOpen}
+ hasSearchTerm={false}
/>,
);
@@ -51,6 +52,7 @@ describe('RoutingPoliciesList', () => {
isRoutingPoliciesError={false}
handlePolicyDetailsModalOpen={mockHandlePolicyDetailsModalOpen}
handleDeleteModalOpen={mockHandleDeleteModalOpen}
+ hasSearchTerm={false}
/>,
);
// Check for loading spinner by class name
@@ -67,6 +69,7 @@ describe('RoutingPoliciesList', () => {
isRoutingPoliciesError
handlePolicyDetailsModalOpen={mockHandlePolicyDetailsModalOpen}
handleDeleteModalOpen={mockHandleDeleteModalOpen}
+ hasSearchTerm={false}
/>,
);
expect(
@@ -82,8 +85,9 @@ describe('RoutingPoliciesList', () => {
isRoutingPoliciesError={false}
handlePolicyDetailsModalOpen={mockHandlePolicyDetailsModalOpen}
handleDeleteModalOpen={mockHandleDeleteModalOpen}
+ hasSearchTerm={false}
/>,
);
- expect(screen.getByText('No routing policies found.')).toBeInTheDocument();
+ expect(screen.getByText('No routing policies yet,')).toBeInTheDocument();
});
});
diff --git a/frontend/src/container/RoutingPolicies/types.ts b/frontend/src/container/RoutingPolicies/types.ts
index 631d7b09858d..8fc5908f7986 100644
--- a/frontend/src/container/RoutingPolicies/types.ts
+++ b/frontend/src/container/RoutingPolicies/types.ts
@@ -37,6 +37,7 @@ export interface RoutingPolicyListProps {
isRoutingPoliciesError: boolean;
handlePolicyDetailsModalOpen: HandlePolicyDetailsModalOpen;
handleDeleteModalOpen: HandleDeleteModalOpen;
+ hasSearchTerm: boolean;
}
export interface RoutingPolicyListItemProps {
diff --git a/go.mod b/go.mod
index fcd2e0124a9b..0b70e74e0a40 100644
--- a/go.mod
+++ b/go.mod
@@ -338,3 +338,5 @@ require (
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)
+
+replace github.com/expr-lang/expr => github.com/SigNoz/expr v1.17.7-beta
diff --git a/go.sum b/go.sum
index db6a0ae3d544..f6c6a857f389 100644
--- a/go.sum
+++ b/go.sum
@@ -102,6 +102,8 @@ github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA4
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/SigNoz/expr v1.17.7-beta h1:FyZkleM5dTQ0O6muQfwGpoH5A2ohmN/XTasRCO72gAA=
+github.com/SigNoz/expr v1.17.7-beta/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd h1:Bk43AsDYe0fhkbj57eGXx8H3ZJ4zhmQXBnrW523ktj8=
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd/go.mod h1:nxRcH/OEdM8QxzH37xkGzomr1O0JpYBRS6pwjsWW6Pc=
github.com/SigNoz/signoz-otel-collector v0.129.4 h1:DGDu9y1I1FU+HX4eECPGmfhnXE4ys4yr7LL6znbf6to=
@@ -248,8 +250,6 @@ github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
-github.com/expr-lang/expr v1.17.5 h1:i1WrMvcdLF249nSNlpQZN1S6NXuW9WaOfF5tPi3aw3k=
-github.com/expr-lang/expr v1.17.5/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM=
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
diff --git a/pkg/alertmanager/nfmanager/rulebasednotification/provider_test.go b/pkg/alertmanager/nfmanager/rulebasednotification/provider_test.go
index 37ef095ebfb5..8c32f6e42412 100644
--- a/pkg/alertmanager/nfmanager/rulebasednotification/provider_test.go
+++ b/pkg/alertmanager/nfmanager/rulebasednotification/provider_test.go
@@ -295,6 +295,15 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: true,
},
+ {
+ name: "simple equality check - match",
+ expression: `threshold.name = 'auth' AND ruleId = 'rule1'`,
+ labelSet: model.LabelSet{
+ "threshold.name": "auth",
+ "ruleId": "rule1",
+ },
+ expected: true,
+ },
{
name: "simple equality check - no match",
expression: `service == "payment"`,
@@ -304,6 +313,15 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: false,
},
+ {
+ name: "simple equality check - no match",
+ expression: `service = "payment"`,
+ labelSet: model.LabelSet{
+ "service": "auth",
+ "env": "production",
+ },
+ expected: false,
+ },
{
name: "multiple conditions with AND - both match",
expression: `service == "auth" && env == "production"`,
@@ -313,6 +331,15 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: true,
},
+ {
+ name: "multiple conditions with AND - both match",
+ expression: `service = "auth" AND env = "production"`,
+ labelSet: model.LabelSet{
+ "service": "auth",
+ "env": "production",
+ },
+ expected: true,
+ },
{
name: "multiple conditions with AND - one doesn't match",
expression: `service == "auth" && env == "staging"`,
@@ -322,6 +349,15 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: false,
},
+ {
+ name: "multiple conditions with AND - one doesn't match",
+ expression: `service = "auth" AND env = "staging"`,
+ labelSet: model.LabelSet{
+ "service": "auth",
+ "env": "production",
+ },
+ expected: false,
+ },
{
name: "multiple conditions with OR - one matches",
expression: `service == "payment" || env == "production"`,
@@ -331,6 +367,15 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: true,
},
+ {
+ name: "multiple conditions with OR - one matches",
+ expression: `service = "payment" OR env = "production"`,
+ labelSet: model.LabelSet{
+ "service": "auth",
+ "env": "production",
+ },
+ expected: true,
+ },
{
name: "multiple conditions with OR - none match",
expression: `service == "payment" || env == "staging"`,
@@ -340,6 +385,15 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: false,
},
+ {
+ name: "multiple conditions with OR - none match",
+ expression: `service = "payment" OR env = "staging"`,
+ labelSet: model.LabelSet{
+ "service": "auth",
+ "env": "production",
+ },
+ expected: false,
+ },
{
name: "in operator - value in list",
expression: `service in ["auth", "payment", "notification"]`,
@@ -348,6 +402,14 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: true,
},
+ {
+ name: "in operator - value in list",
+ expression: `service IN ["auth", "payment", "notification"]`,
+ labelSet: model.LabelSet{
+ "service": "auth",
+ },
+ expected: true,
+ },
{
name: "in operator - value not in list",
expression: `service in ["payment", "notification"]`,
@@ -356,6 +418,14 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: false,
},
+ {
+ name: "in operator - value not in list",
+ expression: `service IN ["payment", "notification"]`,
+ labelSet: model.LabelSet{
+ "service": "auth",
+ },
+ expected: false,
+ },
{
name: "contains operator - substring match",
expression: `host contains "prod"`,
@@ -364,6 +434,14 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: true,
},
+ {
+ name: "contains operator - substring match",
+ expression: `host CONTAINS "prod"`,
+ labelSet: model.LabelSet{
+ "host": "prod-server-01",
+ },
+ expected: true,
+ },
{
name: "contains operator - no substring match",
expression: `host contains "staging"`,
@@ -372,6 +450,14 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: false,
},
+ {
+ name: "contains operator - no substring match",
+ expression: `host CONTAINS "staging"`,
+ labelSet: model.LabelSet{
+ "host": "prod-server-01",
+ },
+ expected: false,
+ },
{
name: "complex expression with parentheses",
expression: `(service == "auth" && env == "production") || critical == "true"`,
@@ -382,6 +468,16 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: true,
},
+ {
+ name: "complex expression with parentheses",
+ expression: `(service = "auth" AND env = "production") OR critical = "true"`,
+ labelSet: model.LabelSet{
+ "service": "payment",
+ "env": "staging",
+ "critical": "true",
+ },
+ expected: true,
+ },
{
name: "missing label key",
expression: `"missing_key" == "value"`,
@@ -390,6 +486,14 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: false,
},
+ {
+ name: "missing label key",
+ expression: `"missing_key" = "value"`,
+ labelSet: model.LabelSet{
+ "service": "auth",
+ },
+ expected: false,
+ },
{
name: "rule-based expression with threshold name and ruleId",
expression: `'threshold.name' == "high-cpu" && ruleId == "rule-123"`,
@@ -400,6 +504,16 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: false, //no commas
},
+ {
+ name: "rule-based expression with threshold name and ruleId",
+ expression: `'threshold.name' = "high-cpu" AND ruleId == "rule-123"`,
+ labelSet: model.LabelSet{
+ "threshold.name": "high-cpu",
+ "ruleId": "rule-123",
+ "service": "auth",
+ },
+ expected: false, //no commas
+ },
{
name: "alertname and ruleId combination",
expression: `alertname == "HighCPUUsage" && ruleId == "cpu-alert-001"`,
@@ -410,6 +524,16 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: true,
},
+ {
+ name: "alertname and ruleId combination",
+ expression: `alertname = "HighCPUUsage" AND ruleId = "cpu-alert-001"`,
+ labelSet: model.LabelSet{
+ "alertname": "HighCPUUsage",
+ "ruleId": "cpu-alert-001",
+ "severity": "critical",
+ },
+ expected: true,
+ },
{
name: "kubernetes namespace filtering",
expression: `k8s.namespace.name == "auth" && service in ["auth", "payment"]`,
@@ -420,6 +544,16 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: true,
},
+ {
+ name: "kubernetes namespace filtering",
+ expression: `k8s.namespace.name = "auth" && service IN ["auth", "payment"]`,
+ labelSet: model.LabelSet{
+ "k8s.namespace.name": "auth",
+ "service": "auth",
+ "host": "k8s-node-1",
+ },
+ expected: true,
+ },
{
name: "migration expression format from SQL migration",
expression: `threshold.name == "HighCPUUsage" && ruleId == "rule-uuid-123"`,
@@ -430,6 +564,16 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: true,
},
+ {
+ name: "migration expression format from SQL migration",
+ expression: `threshold.name = "HighCPUUsage" && ruleId = "rule-uuid-123"`,
+ labelSet: model.LabelSet{
+ "threshold.name": "HighCPUUsage",
+ "ruleId": "rule-uuid-123",
+ "severity": "warning",
+ },
+ expected: true,
+ },
{
name: "case sensitive matching",
expression: `service == "Auth"`, // capital A
@@ -438,6 +582,14 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: false,
},
+ {
+ name: "case sensitive matching",
+ expression: `service = "Auth"`, // capital A
+ labelSet: model.LabelSet{
+ "service": "auth", // lowercase a
+ },
+ expected: false,
+ },
{
name: "numeric comparison as strings",
expression: `port == "8080"`,
@@ -446,6 +598,14 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: true,
},
+ {
+ name: "numeric comparison as strings",
+ expression: `port = "8080"`,
+ labelSet: model.LabelSet{
+ "port": "8080",
+ },
+ expected: true,
+ },
{
name: "quoted string with special characters",
expression: `service == "auth-service-v2"`,
@@ -454,6 +614,14 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: true,
},
+ {
+ name: "quoted string with special characters",
+ expression: `service = "auth-service-v2"`,
+ labelSet: model.LabelSet{
+ "service": "auth-service-v2",
+ },
+ expected: true,
+ },
{
name: "boolean operators precedence",
expression: `service == "auth" && env == "prod" || critical == "true"`,
@@ -464,6 +632,16 @@ func TestProvider_EvaluateExpression(t *testing.T) {
},
expected: true,
},
+ {
+ name: "boolean operators precedence",
+ expression: `service = "auth" AND env = "prod" OR critical = "true"`,
+ labelSet: model.LabelSet{
+ "service": "payment",
+ "env": "staging",
+ "critical": "true",
+ },
+ expected: true,
+ },
}
for _, tt := range tests {
@@ -554,6 +732,21 @@ func TestProvider_CreateRoute(t *testing.T) {
},
wantErr: false,
},
+ {
+ name: "valid route qb format",
+ orgID: "test-org-123",
+ route: &alertmanagertypes.RoutePolicy{
+ Identifiable: types.Identifiable{ID: valuer.GenerateUUID()},
+ Expression: `service = "auth"`,
+ ExpressionKind: alertmanagertypes.PolicyBasedExpression,
+ Name: "auth-service-route",
+ Description: "Route for auth service alerts",
+ Enabled: true,
+ OrgID: "test-org-123",
+ Channels: []string{"slack-channel"},
+ },
+ wantErr: false,
+ },
{
name: "nil route",
orgID: "test-org-123",
@@ -582,6 +775,17 @@ func TestProvider_CreateRoute(t *testing.T) {
},
wantErr: true,
},
+ {
+ name: "invalid route - missing name",
+ orgID: "test-org-123",
+ route: &alertmanagertypes.RoutePolicy{
+ Expression: `service = "auth"`,
+ ExpressionKind: alertmanagertypes.PolicyBasedExpression,
+ Name: "", // empty name
+ OrgID: "test-org-123",
+ },
+ wantErr: true,
+ },
}
for _, tt := range tests {