-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix(alerts): accept window_in_seconds as the threshold window #7927
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,8 +43,18 @@ public record AlertTriggerConfig( | |
| public static final String PROJECT_IDS_CONFIG_KEY = "project_ids"; | ||
| public static final String THRESHOLD_CONFIG_KEY = "threshold"; | ||
| public static final String WINDOW_CONFIG_KEY = "window"; | ||
| // Documented REST alias for WINDOW_CONFIG_KEY; stored configs may use either spelling. | ||
| public static final String WINDOW_IN_SECONDS_CONFIG_KEY = "window_in_seconds"; | ||
| public static final String NAME_CONFIG_KEY = "name"; | ||
| public static final String OPERATOR_CONFIG_KEY = "operator"; | ||
| // Comma-separated GuardrailType names (e.g. "PII,TOPIC"); empty/absent means all types. | ||
| public static final String GUARDRAIL_TYPES_CONFIG_KEY = "guardrail_types"; | ||
|
|
||
| public static String resolveWindow(Map<String, String> configValue) { | ||
| if (configValue == null) { | ||
| return null; | ||
|
Comment on lines
+53
to
+55
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Undeclared nullable API contract
Want Baz to fix this for you? Activate Fixer Other fix methodsPrompt for AI Agents |
||
| } | ||
| String window = configValue.get(WINDOW_CONFIG_KEY); | ||
| return window != null ? window : configValue.get(WINDOW_IN_SECONDS_CONFIG_KEY); | ||
|
Comment on lines
+57
to
+58
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blank canonical window breaks alert evaluation
Want Baz to fix this for you? Activate Fixer Other fix methodsPrompt for AI Agents |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,6 +59,7 @@ | |
| import static com.comet.opik.api.AlertTriggerConfig.OPERATOR_CONFIG_KEY; | ||
| import static com.comet.opik.api.AlertTriggerConfig.THRESHOLD_CONFIG_KEY; | ||
| import static com.comet.opik.api.AlertTriggerConfig.WINDOW_CONFIG_KEY; | ||
| import static com.comet.opik.api.AlertTriggerConfig.resolveWindow; | ||
|
|
||
| /** | ||
| * Scheduled job for processing metrics-based alerts. | ||
|
|
@@ -425,7 +426,7 @@ private TriggerConfig buildTriggerConfig(com.comet.opik.api.AlertTriggerConfig c | |
| } | ||
| BigDecimal threshold = new BigDecimal(thresholdString); | ||
|
|
||
| var windowString = config.configValue().get(WINDOW_CONFIG_KEY); | ||
| var windowString = resolveWindow(config.configValue()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Misleading missing-config diagnostics
Want Baz to fix this for you? Activate Fixer Other fix methodsPrompt for AI Agents |
||
| if (windowString == null) { | ||
| throw new IllegalArgumentException( | ||
| "Missing config value for key '%s' in trigger of type '%s'" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ | |
| import org.junit.jupiter.api.extension.ExtendWith; | ||
| import org.junit.jupiter.params.ParameterizedTest; | ||
| import org.junit.jupiter.params.provider.EnumSource; | ||
| import org.junit.jupiter.params.provider.MethodSource; | ||
| import org.mockito.ArgumentCaptor; | ||
| import org.mockito.Mock; | ||
| import org.mockito.junit.jupiter.MockitoExtension; | ||
|
|
@@ -32,6 +33,7 @@ | |
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.UUID; | ||
| import java.util.stream.Stream; | ||
|
|
||
| import static com.comet.opik.api.AlertTriggerConfig.NAME_CONFIG_KEY; | ||
| import static com.comet.opik.api.AlertTriggerConfig.OPERATOR_CONFIG_KEY; | ||
|
|
@@ -229,6 +231,33 @@ void payloadScalarsMatchSourceOrderEvenWhenSecondFetchCompletesFirst(AlertEventT | |
| assertThat(payload.get("conditions").get(1).get("threshold").asText()).isEqualTo("0.6000"); | ||
| } | ||
|
|
||
| @ParameterizedTest | ||
| @MethodSource("windowConfigKeys") | ||
| void firesTraceErrorsWhenWindowProvidedUnderCanonicalOrDocumentedAlias(String windowKey) { | ||
| Alert alert = alertWithErrorThreshold(windowKey, "2", "300"); | ||
|
|
||
| when(projectMetricsDAO.getTotalTraceErrors(anyList(), any(Instant.class), any(Instant.class))) | ||
| .thenReturn(Mono.just(new BigDecimal("3"))); | ||
| when(alertService.findAllByWorkspaceAndEventTypes(null, | ||
| MetricsAlertJob.SUPPORTED_EVENT_TYPES)).thenReturn(List.of(alert)); | ||
|
|
||
| job.doJob(null); | ||
|
|
||
|
Comment on lines
+234
to
+245
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Four metric aliases remain unverified
Want Baz to fix this for you? Activate Fixer You can also update your AI coding guidelines based on this comment by Other fix methodsPrompt for AI Agents |
||
| ArgumentCaptor<List<String>> payloadCaptor = listCaptor(); | ||
| verify(alertWebhookSender, timeout(ASYNC_TIMEOUT_MS)).createAndSendWebhook( | ||
| any(), eq(WORKSPACE_ID), anyString(), eq(AlertEventType.TRACE_ERRORS), anyList(), | ||
| payloadCaptor.capture(), anyList()); | ||
|
|
||
| JsonNode payload = JsonUtils.readValue(payloadCaptor.getValue().getFirst(), JsonNode.class); | ||
| assertThat(payload.get("window_seconds").asLong()).isEqualTo(300L); | ||
| assertThat(payload.get("metric_value").asText()).isEqualTo("3"); | ||
| assertThat(payload.get("threshold").asText()).isEqualTo("2"); | ||
| } | ||
|
|
||
| static Stream<String> windowConfigKeys() { | ||
| return Stream.of(WINDOW_CONFIG_KEY, "window_in_seconds"); | ||
| } | ||
|
|
||
| @Test | ||
| void doesNotEvaluateWhenInterrupted() throws org.quartz.UnableToInterruptJobException { | ||
| job.interrupt(); | ||
|
|
@@ -274,6 +303,30 @@ private static Alert alertWithGroupedFeedbackConfigs(AlertEventType eventType, i | |
| .build(); | ||
| } | ||
|
|
||
| private static Alert alertWithErrorThreshold(String windowKey, String threshold, String window) { | ||
| AlertTrigger trigger = AlertTrigger.builder() | ||
| .id(UUID.randomUUID()) | ||
| .eventType(AlertEventType.TRACE_ERRORS) | ||
| .triggerConfigs(List.of(AlertTriggerConfig.builder() | ||
| .id(UUID.randomUUID()) | ||
| .type(AlertTriggerConfigType.THRESHOLD_ERRORS) | ||
| .configValue(Map.of( | ||
| THRESHOLD_CONFIG_KEY, threshold, | ||
| windowKey, window)) | ||
|
Comment on lines
+306
to
+315
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Window alias precedence is untestedThe fixture sets exactly one window key, so its cases never exercise Want Baz to fix this for you? Activate Fixer Other fix methodsPrompt for AI Agents |
||
| .build())) | ||
| .build(); | ||
|
|
||
| return Alert.builder() | ||
| .id(UUID.randomUUID()) | ||
| .name("test-alert") | ||
| .enabled(true) | ||
| .webhook(Webhook.builder().url("http://example/hook").build()) | ||
| .triggers(List.of(trigger)) | ||
| .projectId(PROJECT_ID) | ||
| .workspaceId(WORKSPACE_ID) | ||
| .build(); | ||
| } | ||
|
|
||
| private static AlertTriggerConfig feedbackConfig(String operator, String threshold, Integer groupIndex) { | ||
| return AlertTriggerConfig.builder() | ||
| .id(UUID.randomUUID()) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alertTriggersToFormTriggersreads onlyconfig_value.window, so alias-onlywindow_in_secondsalerts produce forms without requiredwindow;formTriggersToAlertTriggerslikewise serializes onlywindow, which can emittrigger_configs: []and causeAlertService.updateto wipe existing configuration — should we resolvewindow_in_secondswithwindowtaking precedence in both projections?configValueis documented only asMap<String,String>, so REST consumers cannot discover thewindow_in_secondsalias or its precedence overwindow_seconds— should we add that to the source-level schema/API docs rather than generated OpenAPI artifacts?Want Baz to fix this for you? Activate Fixer
Other fix methods
Prompt for AI Agents