Design At: designs/dashboards/interactive-table-cell-drilldown
What changed
New component: DashboardLogDrawer.vue
Clicking a cell in a dashboard table panel now opens a side drawer
scoped to that field/value filter. The drawer shows filtered log events
with a SQL editor (Ctrl+Enter to run, Reset to restore default),
server-side pagination with configurable page size (50/100/200/500), and
an event detail sub-drawer on row click.
Event detail has three tabs:
Insights — Field Anomaly Profile (rarity scoring vs current page), Event
Timeline (SVG histogram of event frequency across the selected time
range), Log Patterns (grouped by structure via /patterns/extract, each
with "Open in Logs" that prefills the log explorer), and Surrounding
Events (events from the same stream in a configurable ±1–10 min window,
paginated 20/page with per-row expand)
Details — key/value field list with copy buttons
JSON — raw event JSON
Modified: PanelSchemaRenderer.vue
Fixed type mismatch: route.query.cell_panel returns string; panel schema
IDs may be number — comparison now uses String() on both sides
Added cell_event_ts to the URL param cleanup list so it's cleared when
the outer log drawer closes
Modified: en-US.json — added complete panel.logExplorer key tree
including the new insights subsection
Modified: design-debt-baseline.json — updated baseline after audit (new
file's rawVarInComponent uses are all in sanctioned color-mix()
positions)
URL persistence
All drawer state is encoded in URL params (cell_panel, cell_field,
cell_value, cell_stream, cell_stype, cell_t0, cell_t1, cell_event_ts).
Sharing the URL reopens both drawers and restores the exact event — on
mount it does a WHERE _timestamp = {ts} targeted fetch (size 1) rather
than searching through paginated results.
Zero backend changes
Uses only existing endpoints: search (SQL), search (histogram via
histogram() function), and patterns/extract.
---------
Co-authored-by: Shrinath Rao <shnath@openobserve.ai>
Co-authored-by: ktx-abhay <abhay.padamani@kiara.tech>
Co-authored-by: Abhay Padamani <141122205+ktx-abhay@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Fixes intermittent alpha1 failure of
`GeneralTests/logoManagement.spec.js` ("Logo Upload on Management").
**Cause:** the `_meta` general-settings navigations raced the org-switch
redirect to `/web/`, failing three different ways across the 3 retries —
home-icon timeout, missing logo upload field, and `goto` interrupted by
nav to `/web/`. Not a regression (spec unchanged since #13391); ~50%
flake on alpha.
**Fix:** `openMetaGeneralSettings()` retries the goto until the URL
sticks on `_meta` settings and the page renders; dropped the redundant
`navigateToManagement()` menu click that competed with the goto.
## What this is
Database Monitoring: an aggregation and presentation layer over
telemetry OpenObserve already ingests. It reads three vantages — client
spans, server-vantage logs, and collector metrics — and turns them into
per-statement, per-instance and per-database views.
No new storage engine and no new ingest path. Statements are normalized
and rolled up into a stats stream, and the read API queries that stream
through the existing search path.
## Shape of the change
| | |
|---|---|
| Read endpoints | 17 |
| UI pages | 8 (plus a shell) |
| Commits | 141 |
**Backend** — `src/core/src/db_monitoring/` holds the non-HTTP half
(normalization, rollup, server-vantage parsing). The read API lives in
`src/api/management/src/request/db_monitoring/` as a handler / models /
service trio; the service layer is split one file per feature.
**Frontend** — `web/src/views/DatabaseMonitoring/`, with shared logic in
`web/src/composables/dbm/`.
## Feature availability
Databases, queries, query detail and history, endpoints, samples,
activity, insights, plans, server metrics, server queries, server
samples, instances, instance metrics, badges — is available in OSS.
## Reviewing this
It is a large diff, and most of it is additive: 327 files, and the two
largest non-test files are the shared service module and the deadlocks
service. Suggested order:
1. `src/api/management/src/request/db_monitoring/handler.rs` — the 17
entry points and their gating
2. `src/api/management/src/request/db_monitoring/service/common.rs` —
auth, SQL building, the shared read path
3. `src/core/src/db_monitoring/rollup.rs` — how the stats stream is
produced
4. `web/src/views/DatabaseMonitoring/` — the pages
Time semantics are the subtlest part. Rollup rows are stamped at the
window **end**, so reads are exclusive at both edges; `stats_read_range`
carries the reasoning and a test pins it.
## Known follow-ups
- **The server-vantage stream named as `_o2_dbm_server`.**
## Testing
- Rust: unit tests across the DBM core and read service, including
SQL-building, auth-gating and normalizer cases
- Web: unit tests across the DBM pages and composables
- Playwright: DBM suite covering the pages and cross-page navigation
Verified before opening: OSS and enterprise builds both compile
warning-free, `cargo fmt` clean, `vue-tsc` clean, and the full web unit
suite passes (44,704 tests).
## Alert Library
A curated catalog at `/alert-library`: browse packs of ready-made
alerts, see
which ones can actually run against the streams this org already
receives,
preview a verdict against real data, and install one or many.
The library is a **peer page** of Alerts / Notification Destinations /
Templates
— flat routing, presented as tabs via a shared `AlertSectionTabs` in
`OPageLayout #header-tabs` (the pattern `PipelineSectionTabs` already
ships).
### What landed, by phase
| Phase | What |
| --- | --- |
| Data layer | S3 manifest fetch, cache, readiness resolution
(`useAlertLibrary`, `constants/alertLibrary`, `types/alertLibrary`) |
| Gallery | Rail (packs / categories, multi-select, segmented switch),
stat-strip availability facets, cards |
| Drawer | Detail view with query preview and customizable tunables |
| Install | Bulk-install wizard — destination selection, per-alert
tunables, stale-session hardening |
| Prefill | Carry a library alert (query, trigger, threshold, severity)
into the alert editor |
### Design decisions worth knowing
- **Rail navigates, strip filters.** Availability is deliberately not in
the rail
— carrying both overflowed it and pushed a facet group below the fold.
- **Colour marks the exception only.** No "Ready" pill (it would
describe ~75 of
86 cards and signal nothing); only unusable cards carry a label.
- **A failed `/streams` call leaves readiness unknown**, rather than
claiming
"none of your data matches" — that claim would be about the request, not
the
org, and it is the most alarming thing this page can say. Counts read
`—`
until there is an answer, and a failed *refresh* keeps the verdict it
already had.
- **No "Installed" tile** — installed detection doesn't exist yet, and a
tile
reading a permanent zero is dishonest UI.
- **`severity: warning` renders as `medium`** — the badge group's scale
is
critical/high/medium/low/info, and medium is the amber it also gives P3,
which
is exactly the priority a warning alert installs as.
- Pack and category names come from the manifest, so they render as
`raw()` data —
a pack added upstream needs no frontend release.
### Design-system changes (used by the library, applied app-wide)
- **`warning-quiet` badge tone** — one variant whose values differ by
theme: the
soft amber wash in light, a neutral chip in dark. Neither the filled
amber chip
nor the neutral one was right in both, because the two ramps aren't
symmetric.
- **Border tokens now meet WCAG 2.1 SC 1.4.11.** Every border token
measured
under 3:1 (`border-strong` was 2.52:1). `border-default` firms to
grey-300 and
controls get a token that clears 3:1.
- Nav: Reliability's flyout groups its four alerting entries under one
**Alerts**
header, declared in the same order as the tab strip those pages carry.
Flyout
levels are sized by depth.
- `sensors-off` icon added to the `OIcon` registry.
### Fixes that rode along
- **metrics**: a shared URL whose `metrics_data` blob omits panel query
fields no
longer blanks the whole metrics page — blob queries are filled out to
the panel
schema before being laid over.
- **dashboards**: a PromQL series with no legend template was named
`JSON.stringify(metric)`, so fifty series read identically for the width
of the
chip. Series are now named by the labels that actually differ,
preferring the
ones that say *which workload* it is (namespace / service / pod / host).
Correlation's semantic groups refine that when loaded, but nothing is
fetched
to render a chart — that endpoint is enterprise-only and a chart must
render
the same on OSS.
- **alerts**: preview evaluates against the alert's real trigger;
deleting a row
no longer reloads the list behind it; peer tabs no longer shift when
switching
alerting pages.
- **ui**: badge-only select options no longer render bold.
### Tests
- `test(search)`: pins the streaming-aggregate **wire contract** at the
HTTP
boundary. Under streaming aggregates each partition carries the
cumulative
merged state (last frame is the answer) while ordinary partitions are
disjoint
and must be summed. No pytest had ever received `streaming_aggs: true` —
the
emitter could have flipped to per-partition deltas with the whole suite
green
while dashboards, Logs and the alert preview all silently over-counted.
- `test(e2e)`: four helpers that passed only while an org stayed small —
an
assertion reading past a 20-row page, an unfiltered `OSelect` that
virtualises
past 50 entries, a tab click landing on the rename pencil, and two
builder
tests asserting on whichever metric sorted first.
- New unit specs across the library components, composable, facets,
tunables and
install path.
### Notes
- **i18n**: en-US only; CI generates the other 14 locales.
- **No backend changes** — this branch is entirely `web/` plus test
helpers.
Adds E2E coverage for the date-time picker copy/paste feature from
#13374,
which shipped with no E2E tests.
**14 tests — 3 P0, 9 P1, 2 P2**
Logs picker (auto-apply, 12): controls render with aria-labels; copy
resolves a
*relative* range to absolute epoch µs; full copy→change→paste
round-trip; paste
of epoch µs / epoch seconds / ISO-with-Z / slash-absolute / human-log
formats;
single-value proximity rule on both boundaries; unparseable text and
empty
clipboard rejected without side effects.
Metrics picker (manual-apply, 2): paste stages until Apply commits;
closing
without Apply reverts the trigger label — guards the `triggerLabel`
change that
#13374's own review flagged as a behavioural warning.
**Notes**
- The paste grammar is already exhaustively unit-tested in
`web/src/utils/dateTimeRangeParse.spec.ts` (497 lines), so this suite
covers
the UI wiring those can't reach plus one representative case per format
family.
- Assertions use a copy-back round-trip rather than wall-clock strings:
`getConsumableDateTime()` resolves through `store.state.timezone`, so
pasting
epoch µs and re-reading via Copy is exact and timezone-independent.
Wall-clock
assertions are used only for the two timezone-naive formats.
- `navigator.clipboard.readText()` requires a secure context. localhost
and https
qualify; a plain-HTTP host does not, and paste has no `execCommand`
fallback
(copy does). CI targets localhost, so this is satisfied there.
- Registered in the `GeneralTests` CI shard.
Adds E2E regression cover for the fix in #13812.
The Logs → Build toggle mounts PanelFieldList with pageKey "build" in
edit
mode, which deferred its stream-list fetch until a stream was already
set.
Since ZO_QUERY_ON_STREAM_SELECTION defaults to true, the logs page
selects
nothing, so Build mounted blank and its Stream dropdown was left
enabled,
openable and permanently empty on "No options found" — no way to pick a
stream, no way out.
## Tests (Logs-SelectStar shard)
- **P0** — with no stream selected on the logs page, the Build tab's
Stream
dropdown is populated and a stream can actually be picked: no empty
state,
options render, `e2e_automate` is offered, selecting it lands, and the
field list loads that stream's schema.
- **P1** — changing Stream Type with a blank stream refetches the list:
logs → traces drops `e2e_automate` (a logs stream) and loads a real
traces
list, switching back restores it. Covers the stream_type watcher, which
is
gated on the same `initialStreamsLoaded` flag the fix arms.
`beforeEach` asserts the logs page starts with no stream selected —
without
that precondition both tests would pass against the buggy build.
## Notes
- Page-object locators are scoped to
`[data-test="logs-build-query-page"]`:
the Visualize tab mounts a second PanelEditor that stays in the DOM
under
`v-show`, so an unscoped `index-dropdown-stream` is a strict-mode
violation.
- Verified 6/6 green over 3 repeats locally. The shard runs the backend
with
ZO_QUICK_MODE_ENABLED=true, which the local instance does not —
assertions
avoid named fields so quick mode's field narrowing can't affect them.
Automated coverage for **email alert destinations and distribution
lists**, and unblocks the email tests that have been skipped since they
were written. **32 cases** — 27 active, 5 skipped regressions tracking
open defects (openobserve/o2-enterprise#2471).
**The unblocking.** The two skipped tests said *"the test environment
does not have email infrastructure set up"*. That over-stated it:
creating, editing, validating and deleting an email destination needs
SMTP switched **on**, and never reads a mailbox. Only delivery
assertions do. The suite is tiered on that:
| Tier | Needs | Cases |
|---|---|---|
| A | SMTP enabled | 24 |
| B | a readable sink | 2 |
| C | an alias that fans out | 1 |
**Tests**
- `Alerts/dl-email-destinations.spec.js` (32) — the DL half (alias
accepted, addressed, fanned out) and the recipient-list half, where our
code actually runs: splitting/trimming/lowercasing, the org-membership
gate, edit round-trips, add/remove/delete, both form paradigms (prebuilt
free-text vs the custom path's org-user picker), template auto-attach,
duplicate names, cancel, type-switching, keyboard, dark mode at 420px.
- `utils/mail-sink.js` — pluggable sink: Mailpit, a Mailinator inbox, or
a clean skip with a stated reason.
- `utils/o2-api.js` — storage is asserted from the API, never the form
field: an input can echo what you typed while the stored value differs.
- `scripts/dl-relay.js` — SMTP relay that expands one alias into
per-member deliveries, so fan-out runs with no external provider. Same
pattern as the in-test HTTP receiver in
`alerts-content-templates.spec.js`.
- `alertDestinationsPage.js` — +17 methods; the spec carries zero raw
locators.
**Replaces, without losing coverage.** The two skipped tests in
`alerts-destinations-prebuilt.spec.js` are removed for a pointer mapping
their intent to its new home: create (`D-01`), edit-add (`D-07`),
edit-remove (`ML-08`), delete (`UI-12`), format validation (`UI-02`).
Three of those did not exist before. That file now has no skips.
**CI.** `ZO_SMTP_ENABLED: true` was already set in `playwright.yml` but
had no host or port, so it fell back to `localhost:25` where nothing
listens — destinations could be **saved** while every **send** failed
silently. The workflow now starts Mailpit and a DL relay as static
binaries before the app (no Docker-in-Docker on these runners) and
points `ZO_SMTP_*` at them.
All three tiers run with no secrets and no egress. Verified in the CI
topology: **27 passed, 5 skipped**, `D-02` confirmed reaching all three
member mailboxes.
**Behaviour.** Self-cleaning via the `auto_dest_dl_*` prefix
`cleanup.spec.js` already sweeps, plus an API `afterAll`. `mode:
'parallel'` — each test owns its fixtures.
**Findings** are filed in openobserve/o2-enterprise#2471, not fixed
here. Two P1s: the *"Show matching rows"* toggle is ignored so log rows
are always embedded, and a user removed from the org keeps receiving
alerts while the destination can no longer be saved. The regression
tests for each are in this PR, skipped until the fix lands.
**Paired ENT PR: openobserve/o2-enterprise#2472.** ENT's workflow sets
`ZO_SMTP_ENABLED: false`, so without it the suite skips all 27 cases
there. The shard matrix needs no ENT change — ENT builds it from this
repo's `ci_matrix.json` plus its overlay, so the new spec is picked up
automatically. Merge the two together: this one alone leaves enterprise
with no email coverage.
## What
The `@enterprise` Service Graph smoke test `P0: Navigate to service
graph and verify chart renders`
(`tests/ui-testing/playwright-tests/Traces/service-graph.spec.js:128`)
hard-asserts:
```js
await expect(page).toHaveURL(/\/traces\/service-graph/);
```
## Why
[#13852](https://github.com/openobserve/openobserve/pull/13852) converts
the standalone `/traces/service-graph` route into a **redirect** to the
canonical `/traces?tab=service-graph` tab. The chart still renders —
every other Service Graph spec passes — but this literal URL assertion
no longer matches, so the **Playwright E2E Crosscheck (Enterprise
gate)** fails on #13852 on every attempt.
## Fix
Accept both the legacy route and the `?tab=service-graph` form:
```js
await expect(page).toHaveURL(/\/traces(\/service-graph|\?.*\btab=service-graph\b)/);
```
Backward-compatible and test-only:
- Against **main** (standalone route) → `…/traces/service-graph?…` →
matches ✅
- Against **#13852** (redirect) → `…/traces?…&tab=service-graph` →
matches ✅
- Rejects plain `/traces` and `tab=services-catalog` ✅ (verified)
## Ordering note
This assertion lives in the OSS repo (`@enterprise`-tagged, runs only in
the ENT gate build). Once this merges to `main`, **#13852 must
sync/rebase on main** for its crosscheck to pick up the tolerant
assertion.
## Problem
`reportFolders.spec.js` failed intermittently in CI. `P2: Move button
disabled
when source equals destination` failed on all 3 retries with a
freshly-created
report each time — the signature of poisoned shared state, not timing
flake.
## Root causes
Four independent test-side defects, all rooted in specs sharing one org,
one
user, and one `default` folder under `fullyParallel: true, workers: 5`:
1. **Reports bound to a stranger's dashboard.** `createReportViaApi`
ignored the
dashboard the test had just created and attached to `dashboards[0]` —
whichever
dashboard sorted first alphabetically in the shared folder. Deleting a
dashboard removes its reports, so a neighbouring spec's cleanup made the
report vanish mid-test.
2. **Blanket cleanups.** `cleanupDashboards()` deletes *every* dashboard
owned by
the automation user — every other spec's included (16 in one observed
run).
`reportFolders` also deleted every report matching `test_report_`, which
swept
up `scheduledReportsDrawer`'s `test_report_sched_*`.
3. **Bulk select-all across the shared folder.**
`reports-bulk-operations` did
"select all in `default` → bulk move/delete". Under parallel load that
captured other specs' reports. Confirmed on a live server:
`reportFolders`'
report was found sitting in `bulk_test_folder_*`, created 78ms apart.
4. **A retry that could never succeed.** `expectReportVisibleInTable`
retried by
re-typing the same value into the search box. The Vue model never
changed, and
the in-folder search is a client-side filter over already-fetched rows —
it
cannot pull in a report the page hasn't loaded. The poll burned its full
45s
doing nothing. Separately, `clickMove()` swallowed its dialog-close
wait, so a
move that never landed passed silently and failed a *later* test
instead.
## Changes
Adds automated test coverage for **SLOs** (#13547, #13577, #13579,
#13704, #13761, #13784), which shipped with none. **149 tests** — 77
Playwright + 72 pytest — all three SLI types, both query languages,
happy / negative / edge paths. Clean branch off `main`.
**UI (Playwright · `tests/ui-testing`)**
- `pages/sloPages/` — 4 page objects (verified `data-test` selectors,
hydration + Monaco-sync waits, OSelect commit-retry, virtualized-list
filtering).
- `playwright-tests/SLO/slo-crud.spec.js` (24) — create
count/time-slice/grouped through the form, detail, edit, SLI-type
change, tags, pause, delete, filters; negative: dismissed delete, empty
search, cancel, unmeasured SLI is absent not zero.
- `playwright-tests/SLO/slo-form-validation.spec.js` (20) — server
errors surfacing in the form, conditional fields per SLI type, count +
time-slice previews, **PromQL count and time-slice created end to end**;
negative: no stream, invalid aggregate, over-long name, target
boundaries.
- `playwright-tests/SLO/slo-alert-sli.spec.js` (8) — **the `alert` SLI
type**: source picker, create, config carries only the alert id, detail
names the source; negative: ineligible source offered-but-disabled,
grouped alert SLI, slice finer than source cadence.
- `playwright-tests/SLO/slo-timeslice.spec.js` (10) — **#13761 P0
regression** (measured SLI, not the preview), PromQL, previews, gaps,
language-flip clearing.
- `playwright-tests/SLO/slo-alerts.spec.js` (15) — burn-rate +
error-budget kinds, presets, edit, freeze door, burndown; negative: no
destination, cancel, blank/invalid name.
- `playwright-tests/utils/slo-seed.js` — backdated seeding, metrics
seeding, eligible-source-alert fixture, ground-truth query, fail-fast
env guard, transient-store retry, full fixture teardown (alerts →
streams → destinations → templates).
**API (pytest · `tests/api-testing`)**
- `tests/slos/test_slo_validation.py` (72) — full REST surface
(create/get/update/enable/delete/groups/slo-eligible), validation on
**both create and update** (name, target, window, slice, config shapes,
comparators, float-threshold round-trip), boundaries (target
0.001/99.999, name 256 vs 257, threshold 0/negative/1e12/NaN, unicode
names), alert-source eligibility (silence-gated, too-infrequent, cron),
PromQL config shapes, auth 401/404.
**CI**
- Two shards: `SLO` (`workers: 3`) and `SLO-Measurement` (`workers: 1`).
Split because the backfill job runs with
`ZO_SCHEDULER_SLO_BACKFILL_CONCURRENCY=1`, so measurement specs sharing
an instance starve each other's waits — `fullyParallel` races spec
*files*, which `mode:'serial'` cannot prevent. The 3-worker pin is
SQLite: at 5, concurrent SLO/alert/destination writes trip `database is
locked` on the single-node meta store.
- New optional per-shard `ingest_allowed_upto` (`240`) alongside the
existing `quick_mode_enabled`: the workflow default of 5 **hours**
silently drops the 8 days of history an SLO window needs, while still
answering `200`. Other shards keep the default. Same precedent as
`api-testing.yml`'s `48`.
**Feature code**: 9 additive `data-test`/`dataTest` attributes in
`SloDetail.vue`, `SloList.vue`, `SloAlertCondition.vue` — no logic
changes, `vue-tsc` clean. The stat tiles, tabs and burn-window inputs
had no stable selectors; `OStatStrip`/`OTab` already forward the
attribute.
**Behaviour**: self-cleaning (`e2e_slo_*`, worker-scoped since
`afterAll` runs per worker).
**Verified in CI** on this branch: `e2e / SLO` **52 passed** (1.8m,
workers pinned to 3), `e2e / SLO-Measurement` **25 passed** (11.3m,
workers pinned to 1) — the measurement shard passing confirms the
per-shard `ingest_allowed_upto` took effect, since those tests are
impossible under the 5-hour default. pytest **72 passed** locally; in
CI's 599-test run one setup delete hit a transient 500 (see findings)
and now retries.
**Findings for the feature owner** (reported, not fixed here):
- A blank "good when" shows `Request failed with status code 422` —
`pruned()` drops the empty key, so the API's real 400 message never
reaches `save()`.
- A rejected alert name shows no banner: `SloAlertForm.submit()` returns
on `nameError` before setting `saveError`.
- `avg(latency_ms); DROP TABLE users--` is accepted at create time
(parsed only at measurement).
- The name-length message says "less than 256 characters" but 256 is
accepted — 257 is the first rejection.
- Duplicate alert names on one SLO are accepted (duplicate SLO names are
a 409), and the panel lists them by name, so they are indistinguishable.
- SLO names accept characters that break the list's own
`slos-slolist-edit-${name}` selectors; alert names correctly reject
them.
- All chart/preview root `data-test` ids are unreachable — a parent's
fallthrough attribute overrides the child root's.
- Both edit forms hydrate asynchronously, so a blank form can flash
before the stored values appear.
- `DELETE /slos/{id}` on a freshly-created SLO returned **500** once
under CI load (not reproducible locally in 12 rapid iterations).
Contention is arguably a retryable 503, and the body carried no
actionable message.
- **A scheduled alert whose source stream is deleted is retried
forever** (`Stream "…" not found in schema`), and the SLO backfill lane
shares the scheduler's worker budget — so accumulated dead alerts
*starve SLO measurement*. Observed: 65 of them held coverage at 1 of 7
chunks indefinitely; deleting them restored 0.143 → 1.0 in 240s.
Harmless in CI (fresh instance per shard) but it degrades any long-lived
environment. The suite now cleans up after itself; the retry-forever
behaviour itself may deserve a look.
**Paired ENT PR: openobserve/o2-enterprise#2434.** ENT's matrix is this
repo's manifest plus an overlay, merged by this repo's
`build-ci-matrix.js`, so both SLO shards reach ENT automatically —
verified: 31 merged shards, both SLO entries carrying
`ingest_allowed_upto=240` and their worker pins, no other shard
affected. What ENT was missing is *consuming* those two fields, which
#2434 adds. Merge the two together: this one alone leaves ENT booting
the SLO shards on the 5-hour ingest window, and #2434 alone is inert.
*(An earlier review reported ENT as using an inline matrix with no SLO
shard — that turned out not to be the case.)*
## What
**Phase 2** of the Playwright→pytest API-test migration (Phase 1 =
Correlation, #13788 / o2-enterprise#2419). Ports the 4 **pure-API**
Alerts 4.0 (multi-alert) specs into pytest and deletes the Playwright
versions — they launched a browser only to assert the raw API.
## Changes — `tests/api-testing/tests/alerts/`
| File | Ported from |
|---|---|
| `multialert_helpers.py` | `utils/alerts-api-helpers.js` (v1/v2
endpoints, canonical payloads, seed fixtures, ingest, scheduler-outcome
polling) |
| `conftest.py` | `alerts` fixture — seeds template + destination +
stream, auto-cleans created alerts |
| `test_multialert_api.py` | `alerts-multialert-api.spec.js` — 8 tests
(save-time guardrails, round-trip type contract, B1 group-endpoint RBAC
guard, tag/priority facets) |
| `test_multialert_regression.py` |
`alerts-multialert-regression.spec.js` — 4 tests (zero-behaviour-change
guards) |
| `test_multialert_firing.py` | `alerts-multialert-firing.spec.js` — 4
tests (fires at critical/warning, per-group fan-out, simple rollup) |
| `test_multialert_transitions.py` |
`alerts-multialert-transitions.spec.js` — 1 test (Ok→Warning→Critical
history) |
**17 tests, 1:1 port.** Deletes the 4 API specs and trims them from
`ci_matrix.json`'s Alerts `run_files`. Keeps
`alerts-multialert-ui.spec.js` (UI) and `alerts-api-helpers.js` (still
used by the UI + priority-tags specs).
## No workflow change needed
Unlike the Correlation pilot (which needed a dedicated server env), the
main `api_integration_tests` job **already** runs `tests/alerts/`, and
its server env **already** sets `ZO_ALERT_SCHEDULE_INTERVAL: 3` — the
exact scheduler cadence the firing/transitions tests need. So these
tests are picked up automatically with no CI edits.
## Verified locally
Full suite against a fresh single-node enterprise build: **17/17
passed** (~52s), including the timing-dependent firing
(critical/warning/per-group/rollup) and level-transition history tests.
## What
Removes the 5 **pure-API** Correlation Playwright specs now ported to
pytest. Part of the Playwright→pytest API-test migration (Phase 1
pilot).
Deleted (browser-launched only to make API assertions — no UI):
`correlation-{discovery,custom-groups,identity-sets,reset,edge-cases}.spec.js`
## The pytest port lives in the ENT repo
Correlation is **enterprise-only** (`service_streams` returns 403 on OSS
builds), so — like every other ENT-only api-testing suite
(cipher/dedup/rbac/…) — the pytest suite lives in **o2-enterprise** and
is merged into the OSS tree at CI time. Companion PR:
openobserve/o2-enterprise#2419 (adds
`tests/api-testing/tests/correlation/` + the `api_correlation_tests` job
+ matrix edit). Verified there: **14/14** green on the enterprise build.
## Kept in Playwright (not migrated)
The 4 UI Correlation specs — `settings`, `alert-grouping`, `cache`,
`drawer` (13–92 browser markers each) — render and assert on UI, so they
stay. `correlation-api-helpers.js` stays too (the UI specs still import
it for seeding).
## Why
After #13776 merged, the alpha1 **RegressionSet** shard still failed —
but on a **new, different** cause than #13776 fixed. All 4
`alerts-regression` tests died at the very first step
(`clickAddAlertButton`) because the button was **disabled**:
```
<button disabled aria-disabled="true"
title="No destinations available"
data-test="alert-list-add-alert-btn">
19 × locator resolved to disabled → toBeEnabled() timed out after 15s
```
## Root cause (reproduced live on the RegressionSet org, auto_org_2)
[`AlertList.vue`](web/src/components/alerts/AlertList.vue#L51) disables
the Add Alert button while `destinations.length === 0`:
```vue
:disabled="!destinations.length || !templates.length"
:title="!destinations.length ? t('alerts.noDestinations') : ''"
```
On the **shared** cloud org the **fixed** names `e2e_promql_dest` /
`e2e_promql_template` were stuck ghost records — a concurrent run's
cleanup couldn't fully delete them (UI delete timed out /
`destinations_templates_fk` 500). So the beforeAll create returned `400
"already exists"`, yet the destinations list loaded **empty**, keeping
the button disabled through the 15s `toBeEnabled` wait. Not a product
regression — the org simply had no *loadable* destination.
This is the same **shared-org fixed-name collision** class fixed for
streams in #13776.
## Fix
- `e2e_promql_dest` / `e2e_promql_template` → `..._<RUN_TOKEN>` (unique
per run), so the beforeAll always creates a real, loadable destination.
Keeps the `e2e_promql_` prefix so `cleanup.spec.js` still reclaims them.
- Gate the beforeAll on the destination actually appearing in the
`/alerts/destinations` list API before tests run, so the button is
reliably enabled (`destinations.length > 0`) instead of racing the
fetch.
## Verification (alpha, RegressionSet org auto_org_2 — reproduced 100%
before)
- Before: `Add Alert` disabled → `toBeEnabled` 15s timeout → 4/4 fail.
- After: `Destination confirmed in list API | e2e_promql_dest_<token>` →
button enabled → **alerts-regression 5/5 pass** (#9967, #10899, #10872,
#10110, #10472); alerts saved for both `>=` and `<=`.
## Scope
OSS-only test file. Picked up by the ENT alpha1 workflow the same way as
#13776 (checks out opensource `main`, or dispatch with
`o2_opensource_repo=test/alerts-regression-destinations-gate`).