@@ -791,7 +791,8 @@ Stream<Arguments> update() {
791
791
factory .manufacturePojo (AutomationRuleEvaluatorUpdateLlmAsJudge .class )),
792
792
arguments (
793
793
factory .manufacturePojo (AutomationRuleEvaluatorUserDefinedMetricPython .class ),
794
- factory .manufacturePojo (AutomationRuleEvaluatorUpdateUserDefinedMetricPython .class )),
794
+ factory .manufacturePojo (
795
+ AutomationRuleEvaluatorUpdateUserDefinedMetricPython .class )),
795
796
arguments (
796
797
factory .manufacturePojo (AutomationRuleEvaluatorTraceThreadLlmAsJudge .class ),
797
798
factory .manufacturePojo (AutomationRuleEvaluatorUpdateTraceThreadLlmAsJudge .class )),
@@ -822,6 +823,7 @@ void update(
822
823
.lastUpdatedBy (USER )
823
824
.name (updatedEvaluator .getName ())
824
825
.samplingRate (updatedEvaluator .getSamplingRate ())
826
+ .enabled (updatedEvaluator .isEnabled ())
825
827
.code (updatedEvaluator .getCode ())
826
828
.build ();
827
829
try (var actualResponse = evaluatorsResourceClient .getEvaluator (
@@ -834,6 +836,66 @@ void update(
834
836
assertIgnoredFields (actualAutomationRuleEvaluator , expectedAutomationRuleEvaluator );
835
837
}
836
838
}
839
+
840
+ @ Test
841
+ void updateEnabledToDisabled () {
842
+ // Create an enabled rule
843
+ var automationRuleEvaluator = factory .manufacturePojo (AutomationRuleEvaluatorLlmAsJudge .class )
844
+ .toBuilder ()
845
+ .enabled (true )
846
+ .build ();
847
+ var id = evaluatorsResourceClient .createEvaluator (automationRuleEvaluator , WORKSPACE_NAME , API_KEY );
848
+
849
+ // Update to disable it
850
+ var updatedEvaluator = factory .manufacturePojo (AutomationRuleEvaluatorUpdateLlmAsJudge .class )
851
+ .toBuilder ()
852
+ .projectId (automationRuleEvaluator .getProjectId ())
853
+ .enabled (false )
854
+ .build ();
855
+
856
+ try (var actualResponse = evaluatorsResourceClient .updateEvaluator (
857
+ id , WORKSPACE_NAME , updatedEvaluator , API_KEY , HttpStatus .SC_NO_CONTENT )) {
858
+ assertThat (actualResponse .hasEntity ()).isFalse ();
859
+ }
860
+
861
+ // Verify the rule is now disabled
862
+ try (var actualResponse = evaluatorsResourceClient .getEvaluator (
863
+ id , null , WORKSPACE_NAME , API_KEY , HttpStatus .SC_OK )) {
864
+ var actualAutomationRuleEvaluator = actualResponse .readEntity (AutomationRuleEvaluator .class );
865
+ assertThat (actualAutomationRuleEvaluator .isEnabled ()).isFalse ();
866
+ assertThat (actualAutomationRuleEvaluator .getName ()).isEqualTo (updatedEvaluator .getName ());
867
+ }
868
+ }
869
+
870
+ @ Test
871
+ void updateDisabledToEnabled () {
872
+ // Create a disabled rule
873
+ var automationRuleEvaluator = factory .manufacturePojo (AutomationRuleEvaluatorLlmAsJudge .class )
874
+ .toBuilder ()
875
+ .enabled (false )
876
+ .build ();
877
+ var id = evaluatorsResourceClient .createEvaluator (automationRuleEvaluator , WORKSPACE_NAME , API_KEY );
878
+
879
+ // Update to enable it
880
+ var updatedEvaluator = factory .manufacturePojo (AutomationRuleEvaluatorUpdateLlmAsJudge .class )
881
+ .toBuilder ()
882
+ .projectId (automationRuleEvaluator .getProjectId ())
883
+ .enabled (true )
884
+ .build ();
885
+
886
+ try (var actualResponse = evaluatorsResourceClient .updateEvaluator (
887
+ id , WORKSPACE_NAME , updatedEvaluator , API_KEY , HttpStatus .SC_NO_CONTENT )) {
888
+ assertThat (actualResponse .hasEntity ()).isFalse ();
889
+ }
890
+
891
+ // Verify the rule is now enabled
892
+ try (var actualResponse = evaluatorsResourceClient .getEvaluator (
893
+ id , null , WORKSPACE_NAME , API_KEY , HttpStatus .SC_OK )) {
894
+ var actualAutomationRuleEvaluator = actualResponse .readEntity (AutomationRuleEvaluator .class );
895
+ assertThat (actualAutomationRuleEvaluator .isEnabled ()).isTrue ();
896
+ assertThat (actualAutomationRuleEvaluator .getName ()).isEqualTo (updatedEvaluator .getName ());
897
+ }
898
+ }
837
899
}
838
900
839
901
@ Nested
@@ -1170,6 +1232,221 @@ void getLogsTraceThreadSkipped() {
1170
1232
1171
1233
}
1172
1234
1235
+ @ Nested
1236
+ @ TestInstance (TestInstance .Lifecycle .PER_CLASS )
1237
+ class DisabledRulesTest {
1238
+
1239
+ @ Test
1240
+ void createEvaluatorWithDefaultEnabledTrue () {
1241
+ // Create a rule without explicitly setting enabled - should default to true
1242
+ var automationRuleEvaluator = factory .manufacturePojo (AutomationRuleEvaluatorLlmAsJudge .class );
1243
+ var id = evaluatorsResourceClient .createEvaluator (automationRuleEvaluator , WORKSPACE_NAME , API_KEY );
1244
+
1245
+ try (var actualResponse = evaluatorsResourceClient .getEvaluator (
1246
+ id , null , WORKSPACE_NAME , API_KEY , HttpStatus .SC_OK )) {
1247
+ var actualAutomationRuleEvaluator = actualResponse .readEntity (AutomationRuleEvaluator .class );
1248
+ // Should default to enabled if not explicitly set
1249
+ assertThat (actualAutomationRuleEvaluator .isEnabled ()).isTrue ();
1250
+ }
1251
+ }
1252
+
1253
+ @ Test
1254
+ void createDisabledEvaluator () {
1255
+ // Create a disabled rule explicitly
1256
+ var automationRuleEvaluator = factory .manufacturePojo (AutomationRuleEvaluatorLlmAsJudge .class )
1257
+ .toBuilder ()
1258
+ .enabled (false )
1259
+ .build ();
1260
+ var id = evaluatorsResourceClient .createEvaluator (automationRuleEvaluator , WORKSPACE_NAME , API_KEY );
1261
+
1262
+ try (var actualResponse = evaluatorsResourceClient .getEvaluator (
1263
+ id , null , WORKSPACE_NAME , API_KEY , HttpStatus .SC_OK )) {
1264
+ var actualAutomationRuleEvaluator = actualResponse .readEntity (AutomationRuleEvaluator .class );
1265
+ assertThat (actualAutomationRuleEvaluator .isEnabled ()).isFalse ();
1266
+ }
1267
+ }
1268
+
1269
+ @ Test
1270
+ void getLogsTraceSkippedDueToDisabledRule () {
1271
+ // Test that disabled rules generate appropriate log messages (different from sampling rate messages)
1272
+ var projectName = "project-" + RandomStringUtils .randomAlphanumeric (36 );
1273
+ var projectId = projectResourceClient .createProject (projectName , API_KEY , WORKSPACE_NAME );
1274
+
1275
+ // Create a disabled Python rule with high sampling rate (to distinguish from sampling rate issues)
1276
+ var evaluatorPython = factory .manufacturePojo (AutomationRuleEvaluatorUserDefinedMetricPython .class )
1277
+ .toBuilder ()
1278
+ .samplingRate (1.0f ) // High sampling rate, but rule is disabled
1279
+ .enabled (false )
1280
+ .projectId (projectId )
1281
+ .build ();
1282
+ var idPython = evaluatorsResourceClient .createEvaluator (evaluatorPython , WORKSPACE_NAME , API_KEY );
1283
+
1284
+ // Create a disabled LLM rule with high sampling rate
1285
+ var evaluatorLlm = factory .manufacturePojo (AutomationRuleEvaluatorLlmAsJudge .class )
1286
+ .toBuilder ()
1287
+ .samplingRate (1.0f ) // High sampling rate, but rule is disabled
1288
+ .enabled (false )
1289
+ .projectId (projectId )
1290
+ .build ();
1291
+ var idLlm = evaluatorsResourceClient .createEvaluator (evaluatorLlm , WORKSPACE_NAME , API_KEY );
1292
+
1293
+ // Send a trace that should be skipped due to disabled rules (not sampling rate)
1294
+ var trace = factory .manufacturePojo (Trace .class ).toBuilder ()
1295
+ .projectName (projectName )
1296
+ .threadId (null )
1297
+ .build ();
1298
+ traceResourceClient .createTrace (trace , API_KEY , WORKSPACE_NAME );
1299
+
1300
+ Awaitility .await ().untilAsserted (() -> {
1301
+ var logPagePython = evaluatorsResourceClient .getLogs (idPython , WORKSPACE_NAME , API_KEY );
1302
+ assertDisabledRuleLogResponse (logPagePython , idPython , evaluatorPython , trace );
1303
+ var logPageLlm = evaluatorsResourceClient .getLogs (idLlm , WORKSPACE_NAME , API_KEY );
1304
+ assertDisabledRuleLogResponse (logPageLlm , idLlm , evaluatorLlm , trace );
1305
+ });
1306
+ }
1307
+
1308
+ @ Test
1309
+ void getLogsTraceThreadSkippedDueToDisabledRule () {
1310
+ // Test trace thread handling for disabled rules
1311
+ var projectName = "project-" + RandomStringUtils .randomAlphanumeric (36 );
1312
+ var projectId = projectResourceClient .createProject (projectName , API_KEY , WORKSPACE_NAME );
1313
+
1314
+ // Create disabled trace thread rules with high sampling rates
1315
+ var evaluatorPython = Optional .ofNullable (factory
1316
+ .manufacturePojo (AutomationRuleEvaluatorTraceThreadUserDefinedMetricPython .class )
1317
+ .toBuilder ()
1318
+ .samplingRate (1.0f ) // High sampling rate, but rule is disabled
1319
+ .enabled (false )
1320
+ .projectId (projectId )
1321
+ .build ())
1322
+ .map (evaluator -> evaluator .toBuilder ()
1323
+ .id (evaluatorsResourceClient .createEvaluator (evaluator , WORKSPACE_NAME , API_KEY ))
1324
+ .build ())
1325
+ .get ();
1326
+
1327
+ var evaluatorLlm = Optional .ofNullable (
1328
+ factory .manufacturePojo (AutomationRuleEvaluatorTraceThreadLlmAsJudge .class )
1329
+ .toBuilder ()
1330
+ .samplingRate (1.0f ) // High sampling rate, but rule is disabled
1331
+ .enabled (false )
1332
+ .projectId (projectId )
1333
+ .build ())
1334
+ .map (evaluator -> evaluator .toBuilder ()
1335
+ .id (evaluatorsResourceClient .createEvaluator (evaluator , WORKSPACE_NAME , API_KEY ))
1336
+ .build ())
1337
+ .get ();
1338
+
1339
+ // Send a trace that should create a thread but be skipped due to disabled rules
1340
+ var trace = factory .manufacturePojo (Trace .class ).toBuilder ()
1341
+ .projectName (projectName )
1342
+ .build ();
1343
+
1344
+ Instant createdAt = trace .createdAt ();
1345
+ traceResourceClient .createTrace (trace , API_KEY , WORKSPACE_NAME );
1346
+
1347
+ Awaitility .await ().untilAsserted (() -> {
1348
+ TraceThread traceThread = traceResourceClient .getTraceThread (trace .threadId (), projectId , API_KEY ,
1349
+ WORKSPACE_NAME );
1350
+
1351
+ var logPagePython = evaluatorsResourceClient .getLogs (evaluatorPython .getId (), WORKSPACE_NAME , API_KEY );
1352
+ var logPageLlm = evaluatorsResourceClient .getLogs (evaluatorLlm .getId (), WORKSPACE_NAME , API_KEY );
1353
+
1354
+ String threadModelId = Optional .ofNullable (traceThread .threadModelId ())
1355
+ .map (Object ::toString )
1356
+ .orElseThrow ();
1357
+
1358
+ Map <String , String > markers = Map .of ("thread_model_id" , threadModelId );
1359
+ String disabledMessage = "The threadModelId '%s' was skipped for rule: '%s' as the rule is disabled" ;
1360
+
1361
+ assertTraceThreadDisabledLogResponse (logPagePython , threadModelId , evaluatorPython , markers , createdAt ,
1362
+ disabledMessage );
1363
+ assertTraceThreadDisabledLogResponse (logPageLlm , threadModelId , evaluatorLlm , markers , createdAt ,
1364
+ disabledMessage );
1365
+ });
1366
+ }
1367
+
1368
+ @ Test
1369
+ void mixedEnabledAndDisabledRules () {
1370
+ // Test scenario with both enabled and disabled rules in same project
1371
+ var projectName = "project-" + RandomStringUtils .randomAlphanumeric (36 );
1372
+ var projectId = projectResourceClient .createProject (projectName , API_KEY , WORKSPACE_NAME );
1373
+
1374
+ // Create one enabled rule
1375
+ var enabledRule = factory .manufacturePojo (AutomationRuleEvaluatorLlmAsJudge .class )
1376
+ .toBuilder ()
1377
+ .samplingRate (0f ) // Low sampling rate to avoid actual processing
1378
+ .enabled (true )
1379
+ .projectId (projectId )
1380
+ .build ();
1381
+ var enabledId = evaluatorsResourceClient .createEvaluator (enabledRule , WORKSPACE_NAME , API_KEY );
1382
+
1383
+ // Create one disabled rule
1384
+ var disabledRule = factory .manufacturePojo (AutomationRuleEvaluatorUserDefinedMetricPython .class )
1385
+ .toBuilder ()
1386
+ .samplingRate (1.0f ) // High sampling rate, but rule is disabled
1387
+ .enabled (false )
1388
+ .projectId (projectId )
1389
+ .build ();
1390
+ var disabledId = evaluatorsResourceClient .createEvaluator (disabledRule , WORKSPACE_NAME , API_KEY );
1391
+
1392
+ // Send a trace
1393
+ var trace = factory .manufacturePojo (Trace .class ).toBuilder ()
1394
+ .projectName (projectName )
1395
+ .threadId (null )
1396
+ .build ();
1397
+ traceResourceClient .createTrace (trace , API_KEY , WORKSPACE_NAME );
1398
+
1399
+ Awaitility .await ().untilAsserted (() -> {
1400
+ // Enabled rule should generate sampling rate message (skipped due to 0% rate)
1401
+ var enabledLogPage = evaluatorsResourceClient .getLogs (enabledId , WORKSPACE_NAME , API_KEY );
1402
+ assertLogResponse (enabledLogPage , enabledId , enabledRule , trace );
1403
+
1404
+ // Disabled rule should generate disabled message
1405
+ var disabledLogPage = evaluatorsResourceClient .getLogs (disabledId , WORKSPACE_NAME , API_KEY );
1406
+ assertDisabledRuleLogResponse (disabledLogPage , disabledId , disabledRule , trace );
1407
+ });
1408
+ }
1409
+
1410
+ @ Test
1411
+ void disabledRuleDoesNotConsumeResources () {
1412
+ // Verify that disabled rules are skipped early and don't go through sampling logic
1413
+ var projectName = "project-" + RandomStringUtils .randomAlphanumeric (36 );
1414
+ var projectId = projectResourceClient .createProject (projectName , API_KEY , WORKSPACE_NAME );
1415
+
1416
+ // Create disabled rule with 100% sampling rate
1417
+ var disabledRule = factory .manufacturePojo (AutomationRuleEvaluatorLlmAsJudge .class )
1418
+ .toBuilder ()
1419
+ .samplingRate (1.0f ) // 100% sampling rate
1420
+ .enabled (false ) // But disabled
1421
+ .projectId (projectId )
1422
+ .build ();
1423
+ var disabledId = evaluatorsResourceClient .createEvaluator (disabledRule , WORKSPACE_NAME , API_KEY );
1424
+
1425
+ // Send multiple traces
1426
+ for (int i = 0 ; i < 5 ; i ++) {
1427
+ var trace = factory .manufacturePojo (Trace .class ).toBuilder ()
1428
+ .projectName (projectName )
1429
+ .threadId (null )
1430
+ .build ();
1431
+ traceResourceClient .createTrace (trace , API_KEY , WORKSPACE_NAME );
1432
+ }
1433
+
1434
+ // All traces should be skipped with "disabled" message, none with sampling rate message
1435
+ Awaitility .await ().untilAsserted (() -> {
1436
+ var logPage = evaluatorsResourceClient .getLogs (disabledId , WORKSPACE_NAME , API_KEY );
1437
+ assertLogPage (logPage , 5 ); // Should have 5 log entries
1438
+
1439
+ // All log messages should be about rule being disabled, not sampling rate
1440
+ assertThat (logPage .content ())
1441
+ .allSatisfy (log -> {
1442
+ assertThat (log .level ()).isEqualTo (LogLevel .INFO );
1443
+ assertThat (log .message ()).contains ("as the rule is disabled" );
1444
+ assertThat (log .message ()).doesNotContain ("per the sampling rate" );
1445
+ });
1446
+ });
1447
+ }
1448
+ }
1449
+
1173
1450
private void assertTraceThreadLogResponse (LogPage logPage , String threadModelId , AutomationRuleEvaluator <?> rule ,
1174
1451
Map <String , String > markers ,
1175
1452
Instant createdAt , String message ) {
@@ -1305,4 +1582,39 @@ private void assertLogResponse(LogPage logPage, UUID ruleId, AutomationRuleEvalu
1305
1582
.isEqualTo ("The traceId '%s' was skipped for rule: '%s' and per the sampling rate '0.0'" .formatted (
1306
1583
trace .id (), rule .getName ()));
1307
1584
}
1585
+
1586
+ private void assertDisabledRuleLogResponse (LogPage logPage , UUID ruleId , AutomationRuleEvaluator <?> rule ,
1587
+ Trace trace ) {
1588
+ assertLogPage (logPage , 1 );
1589
+
1590
+ assertThat (logPage .content ())
1591
+ .allSatisfy (log -> {
1592
+ assertThat (log .timestamp ()).isBetween (trace .createdAt (), Instant .now ());
1593
+ assertThat (log .ruleId ()).isEqualTo (ruleId );
1594
+ assertThat (log .level ()).isEqualTo (LogLevel .INFO );
1595
+ assertThat (log .markers ()).isEqualTo (Map .of ("trace_id" , trace .id ().toString ()));
1596
+ });
1597
+
1598
+ // Verify the message specifically mentions rule being disabled
1599
+ assertThat (logPage .content ().getFirst ().message ())
1600
+ .isEqualTo ("The traceId '%s' was skipped for rule: '%s' as the rule is disabled" .formatted (
1601
+ trace .id (), rule .getName ()));
1602
+ }
1603
+
1604
+ private void assertTraceThreadDisabledLogResponse (LogPage logPage , String threadModelId ,
1605
+ AutomationRuleEvaluator <?> rule ,
1606
+ Map <String , String > markers , Instant createdAt , String messageTemplate ) {
1607
+ assertLogPage (logPage , 1 );
1608
+
1609
+ assertThat (logPage .content ())
1610
+ .allSatisfy (log -> {
1611
+ assertThat (log .timestamp ()).isBetween (createdAt , Instant .now ());
1612
+ assertThat (log .ruleId ()).isEqualTo (rule .getId ());
1613
+ assertThat (log .level ()).isEqualTo (LogLevel .INFO );
1614
+ assertThat (log .markers ()).isEqualTo (markers );
1615
+ });
1616
+
1617
+ assertThat (logPage .content ().getFirst ().message ())
1618
+ .isEqualTo (messageTemplate .formatted (threadModelId , rule .getName ()));
1619
+ }
1308
1620
}
0 commit comments