-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[OPIK-8204] [QA] test: cover the alerts CRUD lifecycle with E2E tests #8117
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
Merged
AndreiCautisanu
merged 5 commits into
main
from
andreicautisanu/OPIK-8204-e2e-alerts-crud
Sep 3, 2026
Merged
Changes from 3 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
17b7301
[OPIK-8204] [QA] test: cover the alerts CRUD lifecycle with E2E tests
AndreiCautisanu 4d4190f
fix(alerts-e2e): address review — trigger order, cleanup path, testid…
AndreiCautisanu 9ea615f
test(alerts-e2e): fail legibly on duplicate triggers of one event type
AndreiCautisanu ce9f1a6
fix(alerts-e2e): scope UI cleanup to exact names, guard the zero-trig…
AndreiCautisanu 2147555
Merge origin/main into andreicautisanu/OPIK-8204-e2e-alerts-crud
AndreiCautisanu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| import { test as baseTest } from './dashboard-cleanup.fixture'; | ||
| import { shouldLeaveArtifacts } from '../core/artifacts'; | ||
| import { uuid7 } from '../core/backend/uuid7'; | ||
|
|
||
| /** Wire values of `AlertTriggerWriteEventType`; the UI renders them under its own titles. */ | ||
| export const ALERT_EVENT_TYPE = { | ||
| promptCreated: 'prompt:created', | ||
| promptCommitted: 'prompt:committed', | ||
| promptDeleted: 'prompt:deleted', | ||
| experimentFinished: 'experiment:finished', | ||
| traceCost: 'trace:cost', | ||
| traceLatency: 'trace:latency', | ||
| traceErrors: 'trace:errors', | ||
| } as const; | ||
|
|
||
| export type AlertEventType = (typeof ALERT_EVENT_TYPE)[keyof typeof ALERT_EVENT_TYPE]; | ||
|
AndreiCautisanu marked this conversation as resolved.
|
||
|
|
||
| /** Trigger titles the alerts list and editor show for each event type. */ | ||
| export const ALERT_EVENT_TITLE: Record<AlertEventType, string> = { | ||
| [ALERT_EVENT_TYPE.promptCreated]: 'New prompt added', | ||
| [ALERT_EVENT_TYPE.promptCommitted]: 'New prompt version created', | ||
| [ALERT_EVENT_TYPE.promptDeleted]: 'Prompt deleted', | ||
| [ALERT_EVENT_TYPE.experimentFinished]: 'Experiment finished', | ||
| [ALERT_EVENT_TYPE.traceCost]: 'Cost threshold', | ||
| [ALERT_EVENT_TYPE.traceLatency]: 'Latency threshold', | ||
| [ALERT_EVENT_TYPE.traceErrors]: 'Trace errors threshold', | ||
| }; | ||
|
|
||
| export interface AlertSeed { | ||
| /** Appended to the test namespace, so one test can seed several alerts. */ | ||
| suffix: string; | ||
| enabled?: boolean; | ||
| eventTypes?: AlertEventType[]; | ||
| } | ||
|
|
||
| export interface AlertRef { | ||
| id: string; | ||
| name: string; | ||
| webhookUrl: string; | ||
| enabled: boolean; | ||
| eventTypes: AlertEventType[]; | ||
| } | ||
|
|
||
| export interface AlertFixtures { | ||
| /** | ||
| * One alert under the `project` fixture: enabled, General destination, a | ||
| * single `prompt:created` trigger. That trigger is what makes it render a | ||
| * full list row — a trigger-less alert shows "-" under Events. | ||
| */ | ||
| alert: AlertRef; | ||
|
|
||
| /** | ||
| * Seeds extra alerts, for tests needing more than one row. A callback | ||
| * because the count and trigger mix differ per test; each is torn down | ||
| * alongside the `alert` fixture's own. | ||
| */ | ||
| seedAlerts: (seeds: AlertSeed[]) => Promise<AlertRef[]>; | ||
|
|
||
| /** | ||
| * Cleans up alerts a test creates through the UI, which have no id until the | ||
| * form submits and the row renders. | ||
| * | ||
| * Requesting the fixture is the whole API — alerts are found at teardown by | ||
| * the test's own namespace prefix rather than registered by the test. An | ||
| * id-registration call would be skipped by a failure between the create and | ||
| * the registration, leaking exactly the alert whose run went wrong. | ||
| */ | ||
| uiAlertCleanup: void; | ||
| } | ||
|
|
||
| /** | ||
| * Seeding and teardown for project-scoped alerts. | ||
| * | ||
| * Teardown is mandatory: alerts do not cascade with their project. `alerts` | ||
| * holds `project_id` as a plain indexed column with no FK, and | ||
| * `ProjectService.delete` only touches `ProjectDAO` — so an alert outlives the | ||
| * project that scoped it and surfaces in the next spec's list. Same hazard | ||
| * `automationRulesCleanup` documents for rules. | ||
| * | ||
| * Ids are minted client-side because `POST /v1/private/alerts` answers 201 | ||
| * with no body; without them teardown would need a workspace-wide paginated | ||
| * read to find each alert by name. | ||
| * | ||
| * Best-effort: a failed delete warns rather than throws, so cleanup cannot | ||
| * mask the assertion failure that explains the run. | ||
| */ | ||
| export const test = baseTest.extend<AlertFixtures>({ | ||
| seedAlerts: async ({ sdkClient, project, backendClient, testNamespace }, use, testInfo) => { | ||
| const created: AlertRef[] = []; | ||
|
|
||
| await use(async (seeds) => { | ||
| const refs: AlertRef[] = []; | ||
| for (const seed of seeds) { | ||
| const ref: AlertRef = { | ||
| id: uuid7(), | ||
| name: `${testNamespace}-alert-${seed.suffix}`, | ||
| webhookUrl: `https://example.com/e2e-webhook-${seed.suffix}`, | ||
| enabled: seed.enabled ?? true, | ||
| eventTypes: seed.eventTypes ?? [ALERT_EVENT_TYPE.promptCreated], | ||
| }; | ||
| // The Python bridge has no alert routes; the TS SDK is the public surface. | ||
| await sdkClient.typescript.api.alerts.createAlert({ | ||
| id: ref.id, | ||
| name: ref.name, | ||
| enabled: ref.enabled, | ||
| alertType: 'general', | ||
| projectId: project.id, | ||
| webhook: { url: ref.webhookUrl }, | ||
| triggers: ref.eventTypes.map((eventType) => ({ eventType })), | ||
| }); | ||
| created.push(ref); | ||
| refs.push(ref); | ||
| } | ||
| return refs; | ||
| }); | ||
|
|
||
| if (created.length === 0) return; | ||
|
|
||
| await testInfo.attach('opik.alerts', { | ||
| body: JSON.stringify(created, null, 2), | ||
| contentType: 'application/json', | ||
| }); | ||
|
|
||
| if (shouldLeaveArtifacts(testInfo)) { | ||
| console.warn( | ||
| `[alert fixture] leaving ${created.length} alert(s) under ${project.name} for debugging`, | ||
| ); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| await backendClient.deleteAlertsBatch(created.map((a) => a.id)); | ||
| } catch (err) { | ||
| console.warn('[alert fixture] batch delete warning:', err); | ||
| } | ||
| }, | ||
|
|
||
| alert: async ({ seedAlerts }, use) => { | ||
| const [seeded] = await seedAlerts([{ suffix: 'seeded' }]); | ||
| await use(seeded); | ||
| }, | ||
|
|
||
| uiAlertCleanup: [ | ||
| async ({ backendClient, testNamespace }, use, testInfo) => { | ||
| await use(); | ||
|
|
||
| if (shouldLeaveArtifacts(testInfo)) return; | ||
|
|
||
| // Deletes by prefix, so this also sweeps the fixture-seeded alerts — | ||
| // harmless, since `deleteAlertsBatch` is idempotent for ids already gone. | ||
| try { | ||
| const leftover = await backendClient.listAlertsWithPrefix(`${testNamespace}-alert-`); | ||
| await backendClient.deleteAlertsBatch(leftover.map((a) => a.id)); | ||
|
AndreiCautisanu marked this conversation as resolved.
Outdated
AndreiCautisanu marked this conversation as resolved.
Outdated
|
||
| } catch (err) { | ||
| console.warn('[alert fixture] UI-created alert cleanup warning:', err); | ||
| } | ||
| }, | ||
| // Opted into by name, so the extra workspace-wide read stays off every | ||
| // test that only seeds through `seedAlerts`. | ||
| { auto: false }, | ||
| ], | ||
| }); | ||
|
|
||
| export { expect } from './dashboard-cleanup.fixture'; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.