Skip to content

Commit 19a4daa

Browse files
authored
[OPIK-5206] [BE] feat: add V1 entity check for alerts in workspace version (#5975)
* [OPIK-5206] [BE] feat: add V1 entity check for alerts in workspace version Add alerts to the workspace version V1 entity check. Alerts without project_id (workspace-level) now signal VERSION_1. Project-scoped alerts do not trigger V1. * test(auth): address PR #5964 review feedback from thiagohora - Add comments explaining the serialize→Map→override→re-serialize pattern in testAuthSuccessful/testSessionAuthSuccessful (bypasses @jsonvalue to inject raw strings like "VERSION_1", "version_unknown")
1 parent 7eb21dd commit 19a4daa

5 files changed

Lines changed: 45 additions & 7 deletions

File tree

apps/opik-backend/src/main/java/com/comet/opik/domain/AlertDAO.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
@RegisterColumnMapper(MapFlatArgumentFactory.class)
4646
@RegisterArgumentFactory(AlertTypeArgumentFactory.class)
4747
@RegisterColumnMapper(AlertTypeColumnMapper.class)
48-
interface AlertDAO {
48+
public interface AlertDAO {
4949

5050
String FIND = """
5151
WITH target_alerts AS (
@@ -140,6 +140,9 @@ INSERT INTO alerts (id, name, enabled, alert_type, metadata, webhook_id, workspa
140140
void save(@Bind("workspaceId") String workspaceId, @BindMethods("bean") Alert alert,
141141
@Bind("webhookId") UUID webhookId);
142142

143+
@SqlQuery("SELECT EXISTS(SELECT 1 FROM alerts WHERE workspace_id = :workspaceId AND project_id IS NULL)")
144+
boolean hasVersion1Alerts(@Bind("workspaceId") String workspaceId);
145+
143146
@SqlQuery(FIND)
144147
@UseStringTemplateEngine
145148
@AllowUnusedBindings

apps/opik-backend/src/main/java/com/comet/opik/domain/workspaces/WorkspaceVersionService.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.comet.opik.api.OpikVersion;
44
import com.comet.opik.api.WorkspaceVersion;
5+
import com.comet.opik.domain.AlertDAO;
56
import com.comet.opik.domain.DashboardDAO;
67
import com.comet.opik.domain.DatasetDAO;
78
import com.comet.opik.domain.DemoData;
@@ -211,7 +212,10 @@ private boolean hasStateDbVersion1Entities(String workspaceId) {
211212
log.info("Found version_1 datasets in workspace '{}'", workspaceId);
212213
return true;
213214
}
214-
// TODO: alerts — no project_id column yet. Skipped until column is added.
215+
if (handle.attach(AlertDAO.class).hasVersion1Alerts(workspaceId)) {
216+
log.info("Found version_1 alerts in workspace '{}'", workspaceId);
217+
return true;
218+
}
215219
return false;
216220
});
217221
}

apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/AlertResourceTest.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2839,7 +2839,11 @@ private void compareAlerts(Alert expected, Alert actual, boolean decryptSecretTo
28392839
}
28402840

28412841
private Alert generateAlert() {
2842-
var alert = factory.manufacturePojo(Alert.class);
2842+
return generateAlert(factory);
2843+
}
2844+
2845+
static Alert generateAlert(PodamFactory podamFactory) {
2846+
var alert = podamFactory.manufacturePojo(Alert.class);
28432847

28442848
var webhook = alert.webhook().toBuilder()
28452849
.createdBy(null)
@@ -2851,7 +2855,7 @@ private Alert generateAlert() {
28512855
.map(trigger -> {
28522856
var configs = Optional.ofNullable(trigger.triggerConfigs())
28532857
.map(list -> list.stream()
2854-
.map(this::sanitizeTriggerConfig)
2858+
.map(AlertResourceTest::sanitizeTriggerConfig)
28552859
.toList())
28562860
.orElse(null);
28572861
// Replace TRACE_COST and TRACE_LATENCY with TRACE_ERRORS for test assertion purposes
@@ -2877,7 +2881,7 @@ private Alert generateAlert() {
28772881
.build();
28782882
}
28792883

2880-
private AlertTriggerConfig sanitizeTriggerConfig(AlertTriggerConfig config) {
2884+
private static AlertTriggerConfig sanitizeTriggerConfig(AlertTriggerConfig config) {
28812885
var builder = config.toBuilder()
28822886
.createdBy(null)
28832887
.createdAt(null);
@@ -2887,14 +2891,18 @@ private AlertTriggerConfig sanitizeTriggerConfig(AlertTriggerConfig config) {
28872891
var fixedConfigValue = new HashMap<>(config.configValue());
28882892
fixedConfigValue.put(
28892893
AlertTriggerConfig.PROJECT_IDS_CONFIG_KEY,
2890-
JsonUtils.writeValueAsString(List.of(factory.manufacturePojo(UUID.class))));
2894+
JsonUtils.writeValueAsString(List.of(UUID.randomUUID())));
28912895
builder.configValue(fixedConfigValue);
28922896
}
28932897
return builder.build();
28942898
}
28952899

28962900
private Alert generateAlertForProject(UUID projectId) {
2897-
var base = generateAlert();
2901+
return generateAlertForProject(factory, projectId);
2902+
}
2903+
2904+
static Alert generateAlertForProject(PodamFactory podamFactory, UUID projectId) {
2905+
var base = generateAlert(podamFactory);
28982906
var filteredTriggers = base.triggers().stream()
28992907
.map(t -> t.toBuilder()
29002908
.triggerConfigs(t.triggerConfigs().stream()

apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/WorkspaceVersionResourceTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import com.comet.opik.api.resources.utils.TestDropwizardAppExtensionUtils.CustomConfig;
1717
import com.comet.opik.api.resources.utils.TestUtils;
1818
import com.comet.opik.api.resources.utils.WireMockUtils;
19+
import com.comet.opik.api.resources.utils.resources.AlertResourceClient;
1920
import com.comet.opik.api.resources.utils.resources.AutomationRuleEvaluatorResourceClient;
2021
import com.comet.opik.api.resources.utils.resources.DashboardResourceClient;
2122
import com.comet.opik.api.resources.utils.resources.DatasetResourceClient;
@@ -219,6 +220,7 @@ class EntityWorkspaceVersionTest {
219220
private AutomationRuleEvaluatorResourceClient evaluatorClient;
220221
private ExperimentResourceClient experimentClient;
221222
private OptimizationResourceClient optimizationClient;
223+
private AlertResourceClient alertClient;
222224

223225
@BeforeAll
224226
void beforeAll(ClientSupport clientSupport) {
@@ -231,6 +233,7 @@ void beforeAll(ClientSupport clientSupport) {
231233
evaluatorClient = new AutomationRuleEvaluatorResourceClient(clientSupport, baseUrl);
232234
experimentClient = new ExperimentResourceClient(clientSupport, baseUrl, podamFactory);
233235
optimizationClient = new OptimizationResourceClient(clientSupport, baseUrl, podamFactory);
236+
alertClient = new AlertResourceClient(clientSupport);
234237
}
235238

236239
@AfterAll
@@ -385,6 +388,23 @@ void workspaceVersion__whenOptimizationWithoutProject__returnsVersion1() {
385388
API_KEY, workspaceName);
386389
assertThat(workspaceClient.getWorkspaceVersion(API_KEY, workspaceName)).isEqualTo(V1_WORKSPACE_VERSION);
387390
}
391+
392+
@Test
393+
void workspaceVersion__whenAlertWithoutProject__returnsVersion1() {
394+
var workspaceName = mockWorkspace();
395+
396+
// Project-scoped alert (projectId column) does not trigger version_1
397+
alertClient.createAlert(
398+
AlertResourceTest.generateAlertForProject(podamFactory, UUID.randomUUID()),
399+
API_KEY, workspaceName, 201);
400+
assertThat(workspaceClient.getWorkspaceVersion(API_KEY, workspaceName)).isEqualTo(V2_WORKSPACE_VERSION);
401+
402+
// Workspace level alert triggers version_1
403+
alertClient.createAlert(
404+
AlertResourceTest.generateAlert(podamFactory),
405+
API_KEY, workspaceName, 201);
406+
assertThat(workspaceClient.getWorkspaceVersion(API_KEY, workspaceName)).isEqualTo(V1_WORKSPACE_VERSION);
407+
}
388408
}
389409

390410
@Nested

apps/opik-backend/src/test/java/com/comet/opik/infrastructure/auth/RemoteAuthServiceTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ void testAuthSuccessful(boolean workspaceViaHeader, String opikVersionStr)
106106
var apiKey = "apiKey-" + UUID.randomUUID();
107107
var workspaceName = "workspace-" + UUID.randomUUID();
108108

109+
// Serialize via Map to inject the raw opikVersionStr (e.g. "VERSION_1", "version_unknown")
110+
// directly into JSON, bypassing @JsonValue which would normalize the casing
109111
Map<String, Object> responseMap = OBJECT_MAPPER.readValue(
110112
OBJECT_MAPPER.writeValueAsString(authResponse), Map.class);
111113
responseMap.put("opikVersion", opikVersionStr);
@@ -247,6 +249,7 @@ void testSessionAuthSuccessful(boolean workspaceViaHeader, String opikVersionStr
247249
var sessionTokenValue = "session-" + UUID.randomUUID();
248250
var workspaceName = "workspace-" + UUID.randomUUID();
249251

252+
// Serialize via Map to inject raw opikVersionStr, bypassing @JsonValue normalization
250253
Map<String, Object> responseMap = OBJECT_MAPPER.readValue(
251254
OBJECT_MAPPER.writeValueAsString(authResponse), Map.class);
252255
responseMap.put("opikVersion", opikVersionStr);

0 commit comments

Comments
 (0)