Commit Graph
1111 Commits
Author SHA1 Message Date
ktx-akshay aa00c5528e test: improve Alpha automation test stability (#13841)
…color-by-series features
2026-08-20 10:10:43 +00:00
Vamsi Karthik RandShrinath Rao 6930a9b724 test(mobile): RUM SDK end-to-end suite across 4 platforms + self-contained CI (#13628)
## What this PR adds

end-to-end test suite for the **Mobile RUM SDK** under
`tests/mobile-testing/`, plus a **self-contained CI workflow** that runs
it on real emulators/simulators against an OpenObserve **built from this
PR's source**. Test / fixture / CI only — no product-code/existing test
changes

## How each test works (3 layers)

Every scenario is a real end-to-end journey, so a failure points at the
actual broken layer:

1. **Drive** — a Maestro flow taps through the app like a user
(`maestro/<track>/*.yaml`).
2. **API** — query the `_rumdata` stream and assert the telemetry is
correct (`utils/ooClient.js`, poll-and-retry for async ingestion).
3. **UI** — open the RUM dashboard and assert the sessions

## Coverage (42 tests across 4 platforms)

The **native platforms (android-native, ios-native) are now at parity
with React Native** — each new
native test (masking, network, bg/fg, user-identity, no-phone-home) was
built + validated on a real
emulator/simulator before landing.

| Capability | Layer | RN-Android | Android-native | RN-iOS | iOS-native
|
|---|---|:-:|:-:|:-:|:-:|
| Core telemetry (views/actions/handled-error/crash → `_rumdata`) | API
| ✅ | ✅ | ✅ | ✅ |
| Crash session viewable in dashboard | UI | ✅ | ✅ | ✅ | ✅ |
| Attributes: env / service / version tagging | API | ✅ | ✅ | ✅ | ✅ |
| User identity (`setUser`) | API | ✅ | ✅ | ✅ | ✅ |
| Network: fetch → resource + 4xx/5xx | API | ✅ | ✅ | ✅ | ✅ |
| Session-replay PII masking | UI | ✅ | ✅ | ✅ | ✅ |
| Background / foreground view transitions | API | ✅ | ✅ | — | ✅ |
| No-phone-home (no egress to Datadog hosts) | security | ✅ | ✅ | — | ✅
|

- Shared per-capability factories keep the specs DRY
(`utils/rumChecks.js`, `utils/coreRumSpec.js`).
- Negatives are covered: 4xx capture, PII-must-not-leak (masking), no
phone-home, handled-error ≠ crash.
- `o2-enterprise#2289` dashboard bugs are kept as inline skips that flip
green once fixed.
- Full capability × platform detail + on-device status:
`docs/COVERAGE-MATRIX.md`.

## Fixture apps + build-time config

- Four vendored apps under `apps/`: `o2-rum-tester` (React Native —
drives both RN platforms), `o2-native-android` (Kotlin), `o2-native-ios`
(Swift / xcodegen).
- Each reads its RUM target (endpoint / org / token) and SDK version
from a **build-time-overridable** config. The committed default is the
current pin, so local dev is unchanged; CI regenerates it to point at
the runner's OpenObserve and can pin the latest SDK on release.

## CI — `.github/workflows/mobile-sdk-e2e.yml` (self-contained, no
secrets)

- **Build once per OS from source.** `build_oo_linux` / `build_oo_macos`
compile OpenObserve (binary + embedded web UI) from this PR's code and
cache it. Device jobs download and run it **natively**, and the app
posts real RUM to it (Android reaches the host at `10.0.2.2`, the iOS
simulator via `localhost`). No Docker, no shared cluster, no secrets.
- **Device jobs run in parallel.** `android` (Linux + KVM emulator →
rn-android + android-native) and `ios` (macOS simulator, pinned Xcode
16.4 → rn-ios + ios-native).
- **`merge_reports`** combines both platforms into one browsable HTML
report — even when a device job fails.
- **`decide` picks which platform runs, to keep scarce macOS runners in
check:**
- PR touching Android or shared mobile-RUM code → **Android auto-runs**;
iOS is gated behind the `mobile-e2e` label.
  - `mobile-e2e` label → both platforms (full pre-merge gate).
- `repository_dispatch(mobile-sdk-release)` from an SDK repo → runs that
SDK's platform (companion PRs wire this up:
openobserve-react-native-rum#3, openobserve-sdk-android#1,
openobserve-sdk-ios#1).
  - `workflow_dispatch` → manual, choose platforms.

*Separate workflow :* it needs macOS **and** Android emulators, builds 4
phone apps, and is dispatch-driven — matching the repo's existing
per-suite split

## Status

Green end-to-end on GitHub runners (Android + iOS device jobs + merge) :
https://github.com/openobserve/openobserve/actions/runs/31733717758/job/94565520891

---------

Co-authored-by: Shrinath Rao <shnath@openobserve.ai>
2026-08-20 08:13:35 +00:00
ktx-akshay 6fe56575e2 test(datetime-picker): E2E cover for copy/paste in the date-time picker (#13894)
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.
2026-08-20 07:29:14 +00:00
ktx-akshay 78998b5fa3 test(logs): E2E cover for Build tab stream list with no stream selected (#13884)
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.
2026-08-20 05:38:27 +00:00
Vamsi Karthik R c3056cc720 test(alerts): E2E suite for email destinations and distribution lists (#13876)
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.
2026-08-20 04:19:42 +00:00
Shrinath Rao 30ef3ec242 test(traces): heal service-graph/catalog rail-flyout nav for #13852 ?tab= reroute (#13875)
## What

Heals the two Traces-Regression tests that broke after #13852
(\`fix(ui): clean up SLO detail and traces navigation\`).

Failing scheduled runs:
- ENT:
https://github.com/openobserve/o2-enterprise/actions/runs/32213206401/job/95953026991
— \`traces-ent.spec.js\` Service Graph test
- OSS:
https://github.com/openobserve/openobserve/actions/runs/32213278081 —
\`traces-regression.spec.js\` Service Catalog test

Both failed all 4 retries with \`locator.click: Timeout 45000ms
exceeded\` on the left-rail flyout item.

## Root cause

#13852 rerouted the Traces rail-flyout children through the Traces
page's in-page \`?tab=\` views. That changed **both**:

| | before | after |
|---|---|---|
| flyout \`data-test\` | \`nav-group-item-serviceGraph\` |
\`nav-group-item-traces-service-graph\` |
| | \`nav-group-item-servicesCatalog\` |
\`nav-group-item-traces-services-catalog\` |
| URL | \`/traces/service-graph\` | \`/traces?tab=service-graph\` |
| | \`/traces/services\` | \`/traces?tab=services-catalog\` |

(\`childDataTest = nav-group-item-\${child.name}-\${child.tab}\`; legacy
paths now only exist as redirects.) The stale \`data-test\` made
\`.click()\` wait the full 45s action timeout.

## Fix

\`serviceGraphPage.navigateToServiceGraph()\` and
\`servicesCatalogPage.clickServiceCatalogTab()\`:
- point at the new flyout \`data-test\` ids
- \`waitFor\` the teleported flyout item to be **visible** before
clicking — the flyout opens after ONavGroup's 120ms \`OPEN_DELAY\`, so
the item isn't in the DOM the instant \`hover()\` resolves
- match the new \`/traces?tab=\` URL

Engine/other nav helpers untouched.

## Verification

Run locally against a #13852 frontend (localhost:5080, enterprise
build):
- ✅ Live flyout click →
\`/web/traces?org_identifier=default&tab=service-graph\` and
\`...&tab=services-catalog\`
- ✅ **OSS** \`traces-regression.spec.js\` "Clearing search filter should
not blank out Service Catalog table" — **passes**
- ✅ **ENT** \`traces-ent.spec.js\` Service Graph nav now works — test
reaches the chart assertion (line 54); the only residual local failure
is absent service-graph topology (0 nodes locally; CI seeds it via
ingestion + the daemon)

## Follow-up (not in this PR / not in any CI matrix)

\`Traces/traces-toolbar-tabs.spec.js\` +
\`tracesPage.switchToServiceMaps()\` still assert the pre-#13852 "flyout
→ standalone route" dual-semantics that #13852 removed. Not run by any
CI matrix, so left for a separate change aligned with the new
single-\`?tab=\` semantics.
2026-08-19 11:30:22 +00:00
Vamsi Karthik R 7b79fdcdbe ci(playwright): stop SLO-Measurement backfill from serializing 21 daily chunks (#13870)
## What this fixes

The `SLO-Measurement` shard (`slo-timeslice.spec.js` +
`slo-alerts.spec.js`) has been taking **~26 minutes**

## Root cause

Both specs seed **8 days of history** and create SLOs with a **7-day
rolling window**, then block in `beforeAll` until the SLO is actually
measured (`waitForSloMeasured`). The server backfills history in
`ZO_SLO_BACKFILL_CHUNK_SECS` chunks — default **86400s (1 day)** — and
only **one chunk at a time** (`ZO_SCHEDULER_SLO_BACKFILL_CONCURRENCY=1`,
by design, so bulk backfill never starves real-time alerts).

The shard backfills **3 SLOs** across the two specs, each needing 7
daily chunks to cover its 7-day window:

- `slo-timeslice.spec.js` `beforeAll`: 2 SLOs (`sloLt`, `sloGt`) × 7
chunks = 14
- `slo-alerts.spec.js` `beforeAll`: 1 SLO × 7 chunks = 7

**21 chunks total, strictly serial**, at roughly 40s/chunk ≈ **~14
minutes of pure backfill wait**, before any UI assertion runs. This is
the server behaving correctly and exactly as designed for production —
the test is just paying for it the slow way.

## Fix

Neither spec asserts on per-chunk timing or partial coverage, only on
the final measured state, so raising the chunk size to cover the whole
window in one pass is free wall-clock, not a behavior change.

- Threaded `slo_backfill_chunk_secs` through `build-ci-matrix.js`'s
existing per-shard passthrough, same pattern already used for
`ingest_allowed_upto` / `quick_mode_enabled`.
- Set it to `604800` (7 days) for the `SLO-Measurement` shard only — the
server-wide default (`86400`) is untouched everywhere else.
- Documented the new knob in `tests/ui-testing/ci-matrix/README.md`.

## Validation

**Synthetic (API-only), same seed shape as the real specs** (8 days,
1-min interval, deterministic 3h fault window), against a local build:

| | Default (86400s chunk) | Fixed (604800s chunk) |
|---|---|---|
| Time to measured | 246.9s (~4.1 min) for **one** SLO, stepping 1/7 →
2/7 → ... every ~40s | **3.0s** |
| Final SLI | 98.21428571428571% | 98.21428571428571% (**identical**) |

Same math, same result — only the number of scheduler round-trips
changes.

**Real shard, end-to-end**, run exactly as CI invokes it (`npx
playwright test slo-timeslice.spec.js slo-alerts.spec.js --workers=1`)
against a server mirroring this shard's full env including the fix:

- **25/25 tests passed**, 0 regressions
- Total wall-clock (server start → global teardown): **4m Avg**

Local timing won't map 1:1 onto the GitHub Actions runner (different
disk/scheduler characteristics — even the "before" baseline was faster
locally than the ~26min/~5min-per-spec seen in CI), so the authoritative
number is whatever this PR's own CI run reports. But the structural win
— 1 chunk instead of 7 per SLO — is verified correct and regression-free
independent of machine.

## Companion PR

The enterprise repo's `playwright.yml` independently consumes per-shard
matrix fields (it explicitly mirrors OSS's `ingest_allowed_upto`
handling for the same reason), so it needs the equivalent env
passthrough or it'll keep paying the old cost even after this merges.
Companion PR: openobserve/o2-enterprise#2460
2026-08-19 00:58:33 +00:00
Shrinath Rao 87f7960d48 test(traces): accept redirected ?tab=service-graph URL in Service Graph smoke (#13868)
## 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.
2026-08-18 18:38:40 +00:00
Chaitanya Sistlaandgithub-actions[bot] 351b356620 feat(alerts,slos): export as Terraform and OpenTofu (#13849)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-18 17:27:27 +00:00
71ac1071be fix: draft workflows (#13666)
Signed-off-by: Yashodhan Joshi <yjdoc2@gmail.com>
Co-authored-by: Yashodhan Joshi <yjdoc2@gmail.com>
Co-authored-by: Yashodhan Joshi <yashodhan@openobserve.ai>
2026-08-18 09:15:28 +00:00
ktx-akshay 411be8968a test: fix flaky report folder move test (#13847)
## 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
2026-08-18 07:30:29 +00:00
Vamsi Karthik R 99b1a23773 test(slo): E2E + API suite for SLOs (#13836)
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.)*
2026-08-17 18:50:29 +00:00
Abhay Padamaniandgithub-actions[bot] f112ac01a5 fix: dashboard layout improvements (#13815)
<h2 style="unicode-bidi: plaintext; color: rgb(204, 204, 204);
font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;,
Roboto, sans-serif; font-style: normal; font-variant-ligatures: normal;
font-variant-caps: normal; letter-spacing: normal; orphans: 2;
text-align: start; text-indent: 0px; text-transform: none; widows: 2;
word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal;
background-color: rgb(24, 24, 24); text-decoration-thickness: initial;
text-decoration-style: initial; text-decoration-color:
initial;">Dashboard layout &amp; UX improvements</h2><p
style="white-space: pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em;
unicode-bidi: plaintext; color: rgb(204, 204, 204); font-family:
-apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, Roboto,
sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures:
normal; font-variant-caps: normal; font-weight: 400; letter-spacing:
normal; orphans: 2; text-align: start; text-indent: 0px; text-transform:
none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;
background-color: rgb(24, 24, 24); text-decoration-thickness: initial;
text-decoration-style: initial; text-decoration-color: initial;">Polish
pass on the dashboard experience: settings layout, drag/resize grid
feedback, empty states, and per-tab panel counts.</p><h3
style="unicode-bidi: plaintext; color: rgb(204, 204, 204); font-family:
-apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, Roboto,
sans-serif; font-style: normal; font-variant-ligatures: normal;
font-variant-caps: normal; letter-spacing: normal; orphans: 2;
text-align: start; text-indent: 0px; text-transform: none; widows: 2;
word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal;
background-color: rgb(24, 24, 24); text-decoration-thickness: initial;
text-decoration-style: initial; text-decoration-color: initial;">✨
Highlights</h3><p style="white-space: pre-wrap; margin-top: 0.1em;
margin-bottom: 0.2em; unicode-bidi: plaintext; color: rgb(204, 204,
204); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe
UI&quot;, Roboto, sans-serif; font-size: 13px; font-style: normal;
font-variant-ligatures: normal; font-variant-caps: normal; font-weight:
400; letter-spacing: normal; orphans: 2; text-align: start; text-indent:
0px; text-transform: none; widows: 2; word-spacing: 0px;
-webkit-text-stroke-width: 0px; background-color: rgb(24, 24, 24);
text-decoration-thickness: initial; text-decoration-style: initial;
text-decoration-color: initial;"><strong>Grid feedback while
dragging/resizing panels</strong> (<a
href="vscode-webview://0vkpvjgb486eanj04qjfqcnqmnatevjacbbbqdp3ecrs4efo5krm/web/src/views/Dashboards/RenderDashboardCharts.vue"
target="_blank" rel="noopener noreferrer" style="color: rgb(77, 170,
252); text-decoration: rgb(77, 170,
252);">RenderDashboardCharts.vue</a>)</p><ul
style="padding-inline-start: 2em; color: rgb(204, 204, 204);
font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;,
Roboto, sans-serif; font-size: 13px; font-style: normal;
font-variant-ligatures: normal; font-variant-caps: normal; font-weight:
400; letter-spacing: normal; orphans: 2; text-align: start; text-indent:
0px; text-transform: none; widows: 2; word-spacing: 0px;
-webkit-text-stroke-width: 0px; white-space: normal; background-color:
rgb(24, 24, 24); text-decoration-thickness: initial;
text-decoration-style: initial; text-decoration-color: initial;"><li
style="unicode-bidi: plaintext;">Added a live alignment grid that
appears only during a drag or resize, drawn from GridStack's real cell
metrics (192 columns × row height).</li><li style="unicode-bidi:
plaintext;">The grid is<span> </span><strong>operation-aware</strong>:
lines snap to the top/left edges
while<span> </span><em>moving</em><span> </span>and the bottom/right
edges while<span> </span><em>resizing</em>, so the edge you're dragging
always lands on a line and every line is reachable (no skipped
positions).</li><li style="unicode-bidi: plaintext;">Panels are made
opaque during interaction (<code style="font-family: monospace; color:
rgb(208, 208, 208); background-color: rgb(60, 60, 60); padding: 2px 4px;
border-radius: 3px; word-break: break-word; font-size:
0.9em;">--color-surface-base</code>) so the grid only shows in the
gutters/empty cells, not through chart or table panels.</li></ul><p
style="white-space: pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em;
unicode-bidi: plaintext; color: rgb(204, 204, 204); font-family:
-apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, Roboto,
sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures:
normal; font-variant-caps: normal; font-weight: 400; letter-spacing:
normal; orphans: 2; text-align: start; text-indent: 0px; text-transform:
none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;
background-color: rgb(24, 24, 24); text-decoration-thickness: initial;
text-decoration-style: initial; text-decoration-color:
initial;"><strong>Empty states</strong> (<a
href="vscode-webview://0vkpvjgb486eanj04qjfqcnqmnatevjacbbbqdp3ecrs4efo5krm/web/src/lib/core/EmptyState/presets.ts"
target="_blank" rel="noopener noreferrer" style="color: rgb(77, 170,
252); text-decoration: rgb(77, 170, 252);">presets.ts</a>, <a
href="vscode-webview://0vkpvjgb486eanj04qjfqcnqmnatevjacbbbqdp3ecrs4efo5krm/web/src/components/dashboards/settings/VariableSettings.vue"
target="_blank" rel="noopener noreferrer" style="color: rgb(77, 170,
252); text-decoration: rgb(77, 170, 252);">VariableSettings.vue</a>, <a
href="vscode-webview://0vkpvjgb486eanj04qjfqcnqmnatevjacbbbqdp3ecrs4efo5krm/web/src/views/Dashboards/ScheduledDashboards.vue"
target="_blank" rel="noopener noreferrer" style="color: rgb(77, 170,
252); text-decoration: rgb(77, 170,
252);">ScheduledDashboards.vue</a>)</p><ul style="padding-inline-start:
2em; color: rgb(204, 204, 204); font-family: -apple-system,
&quot;system-ui&quot;, &quot;Segoe UI&quot;, Roboto, sans-serif;
font-size: 13px; font-style: normal; font-variant-ligatures: normal;
font-variant-caps: normal; font-weight: 400; letter-spacing: normal;
orphans: 2; text-align: start; text-indent: 0px; text-transform: none;
widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;
white-space: normal; background-color: rgb(24, 24, 24);
text-decoration-thickness: initial; text-decoration-style: initial;
text-decoration-color: initial;"><li style="unicode-bidi:
plaintext;">Replaced the generic<span> </span><code style="font-family:
monospace; color: rgb(208, 208, 208); background-color: rgb(60, 60, 60);
padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size:
0.9em;">NoData</code><span> </span>placeholder with<span> </span><code
style="font-family: monospace; color: rgb(208, 208, 208);
background-color: rgb(60, 60, 60); padding: 2px 4px; border-radius: 3px;
word-break: break-word; font-size:
0.9em;">OEmptyState</code><span> </span>for the Variables settings table
and the Scheduled Dashboards list.</li><li style="unicode-bidi:
plaintext;">Added a new<span> </span><code style="font-family:
monospace; color: rgb(208, 208, 208); background-color: rgb(60, 60, 60);
padding: 2px 4px; border-radius: 3px; word-break: break-word; font-size:
0.9em;">no-variables</code><span> </span>preset (illustration + copy +
"Add variable" action); reused the existing<span> </span><code
style="font-family: monospace; color: rgb(208, 208, 208);
background-color: rgb(60, 60, 60); padding: 2px 4px; border-radius: 3px;
word-break: break-word; font-size:
0.9em;">no-reports</code><span> </span>preset for scheduled
dashboards.</li><li style="unicode-bidi: plaintext;">Fixed the Variables
table so a long
list<span> </span><strong>scrolls</strong><span> </span>instead of
clipping, and made the Scheduled Dashboards table fill the drawer height
so the empty state centers.</li></ul><p style="white-space: pre-wrap;
margin-top: 0.1em; margin-bottom: 0.2em; unicode-bidi: plaintext; color:
rgb(204, 204, 204); font-family: -apple-system, &quot;system-ui&quot;,
&quot;Segoe UI&quot;, Roboto, sans-serif; font-size: 13px; font-style:
normal; font-variant-ligatures: normal; font-variant-caps: normal;
font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start;
text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px;
-webkit-text-stroke-width: 0px; background-color: rgb(24, 24, 24);
text-decoration-thickness: initial; text-decoration-style: initial;
text-decoration-color: initial;"><strong>Dashboard settings
layout</strong> (<a
href="vscode-webview://0vkpvjgb486eanj04qjfqcnqmnatevjacbbbqdp3ecrs4efo5krm/web/src/components/dashboards/settings/GeneralSettings.vue"
target="_blank" rel="noopener noreferrer" style="color: rgb(77, 170,
252); text-decoration: rgb(77, 170,
252);">GeneralSettings.vue</a>)</p><ul style="padding-inline-start: 2em;
color: rgb(204, 204, 204); font-family: -apple-system,
&quot;system-ui&quot;, &quot;Segoe UI&quot;, Roboto, sans-serif;
font-size: 13px; font-style: normal; font-variant-ligatures: normal;
font-variant-caps: normal; font-weight: 400; letter-spacing: normal;
orphans: 2; text-align: start; text-indent: 0px; text-transform: none;
widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;
white-space: normal; background-color: rgb(24, 24, 24);
text-decoration-thickness: initial; text-decoration-style: initial;
text-decoration-color: initial;"><li style="unicode-bidi:
plaintext;">Reworked the General settings layout.</li></ul><p
style="white-space: pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em;
unicode-bidi: plaintext; color: rgb(204, 204, 204); font-family:
-apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, Roboto,
sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures:
normal; font-variant-caps: normal; font-weight: 400; letter-spacing:
normal; orphans: 2; text-align: start; text-indent: 0px; text-transform:
none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;
background-color: rgb(24, 24, 24); text-decoration-thickness: initial;
text-decoration-style: initial; text-decoration-color:
initial;"><strong>Tab panel-count badge</strong> (<a
href="vscode-webview://0vkpvjgb486eanj04qjfqcnqmnatevjacbbbqdp3ecrs4efo5krm/web/src/components/dashboards/tabs/TabList.vue"
target="_blank" rel="noopener noreferrer" style="color: rgb(77, 170,
252); text-decoration: rgb(77, 170, 252);">TabList.vue</a>)</p><ul
style="padding-inline-start: 2em; color: rgb(204, 204, 204);
font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;,
Roboto, sans-serif; font-size: 13px; font-style: normal;
font-variant-ligatures: normal; font-variant-caps: normal; font-weight:
400; letter-spacing: normal; orphans: 2; text-align: start; text-indent:
0px; text-transform: none; widows: 2; word-spacing: 0px;
-webkit-text-stroke-width: 0px; white-space: normal; background-color:
rgb(24, 24, 24); text-decoration-thickness: initial;
text-decoration-style: initial; text-decoration-color: initial;"><li
style="unicode-bidi: plaintext;">Each dashboard tab now shows a badge
with its panel count (section headers excluded), so tab density is
visible without opening each tab.</li></ul><p style="white-space:
pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em; unicode-bidi:
plaintext; color: rgb(204, 204, 204); font-family: -apple-system,
&quot;system-ui&quot;, &quot;Segoe UI&quot;, Roboto, sans-serif;
font-size: 13px; font-style: normal; font-variant-ligatures: normal;
font-variant-caps: normal; font-weight: 400; letter-spacing: normal;
orphans: 2; text-align: start; text-indent: 0px; text-transform: none;
widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;
background-color: rgb(24, 24, 24); text-decoration-thickness: initial;
text-decoration-style: initial; text-decoration-color:
initial;"><strong>Variable dependency indicator</strong> (<a
href="vscode-webview://0vkpvjgb486eanj04qjfqcnqmnatevjacbbbqdp3ecrs4efo5krm/web/src/components/dashboards/settings/VariableSettings.vue"
target="_blank" rel="noopener noreferrer" style="color: rgb(77, 170,
252); text-decoration: rgb(77, 170,
252);">VariableSettings.vue</a>)</p><ul style="padding-inline-start:
2em; color: rgb(204, 204, 204); font-family: -apple-system,
&quot;system-ui&quot;, &quot;Segoe UI&quot;, Roboto, sans-serif;
font-size: 13px; font-style: normal; font-variant-ligatures: normal;
font-variant-caps: normal; font-weight: 400; letter-spacing: normal;
orphans: 2; text-align: start; text-indent: 0px; text-transform: none;
widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;
white-space: normal; background-color: rgb(24, 24, 24);
text-decoration-thickness: initial; text-decoration-style: initial;
text-decoration-color: initial;"><li style="unicode-bidi:
plaintext;">Variables that depend on others show a "Depends on: …" chip
(tree icon + count) in the settings table.</li></ul><p
style="white-space: pre-wrap; margin-top: 0.1em; margin-bottom: 0.2em;
unicode-bidi: plaintext; color: rgb(204, 204, 204); font-family:
-apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, Roboto,
sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures:
normal; font-variant-caps: normal; font-weight: 400; letter-spacing:
normal; orphans: 2; text-align: start; text-indent: 0px; text-transform:
none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;
background-color: rgb(24, 24, 24); text-decoration-thickness: initial;
text-decoration-style: initial; text-decoration-color:
initial;"><strong>Panel header token</strong> (<a
href="vscode-webview://0vkpvjgb486eanj04qjfqcnqmnatevjacbbbqdp3ecrs4efo5krm/web/src/lib/styles/tokens/component.css"
target="_blank" rel="noopener noreferrer" style="color: rgb(77, 170,
252); text-decoration: rgb(77, 170, 252);">component.css</a>)</p><ul
style="padding-inline-start: 2em; color: rgb(204, 204, 204);
font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;,
Roboto, sans-serif; font-size: 13px; font-style: normal;
font-variant-ligatures: normal; font-variant-caps: normal; font-weight:
400; letter-spacing: normal; orphans: 2; text-align: start; text-indent:
0px; text-transform: none; widows: 2; word-spacing: 0px;
-webkit-text-stroke-width: 0px; white-space: normal; background-color:
rgb(24, 24, 24); text-decoration-thickness: initial;
text-decoration-style: initial; text-decoration-color: initial;"><li
style="unicode-bidi: plaintext;">Lightened<span> </span><code
style="font-family: monospace; color: rgb(208, 208, 208);
background-color: rgb(60, 60, 60); padding: 2px 4px; border-radius: 3px;
word-break: break-word; font-size:
0.9em;">--color-panel-bar-bg</code><span> </span>(grey-200 → grey-100)
for a softer chart-header band; the grey-300 divider still carries the
header/body edge.</li></ul><h3 style="unicode-bidi: plaintext; color:
rgb(204, 204, 204); font-family: -apple-system, &quot;system-ui&quot;,
&quot;Segoe UI&quot;, Roboto, sans-serif; font-style: normal;
font-variant-ligatures: normal; font-variant-caps: normal;
letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px;
text-transform: none; widows: 2; word-spacing: 0px;
-webkit-text-stroke-width: 0px; white-space: normal; background-color:
rgb(24, 24, 24); text-decoration-thickness: initial;
text-decoration-style: initial; text-decoration-color: initial;">

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-17 10:02:32 +00:00
ktx-akshay 33c6e9e0e1 test(dashboards): cover CTE/subquery match_all queries through the panel search path (#13838)
## What

First test coverage for the dashboard **panel** endpoints and the
**dashboard
search path**, running CTE/subquery `match_all` queries as real
dashboard panels.

Nothing in the repo previously touched either:
`grep -rn "/panels\|search_type=dashboards" tests/api-testing/` returned
zero hits.

## Why

Panels query `_search?type=logs&search_type=dashboards&dashboard_id=…` —
a
different route into the engine than the Logs page (`search_type=ui`).
The
`match_all` + FST projection rewrite fixed in #13808 runs inside it, so
a query
that passes via Logs can still regress on a dashboard.

The rewrite has to preserve the projection of the block the predicate
sits in.
When that block is the whole statement, the projection is obvious. When
it's a
CTE body feeding a JOIN, a window, or a four-branch UNION ALL, it has to
preserve
the *CTE's* output schema while the outer query reads different columns
from it —
which is where it went wrong.

## Coverage

**71 distinct queries across 29 combination families**, all with
`match_all`
inside a `WITH …` body or a derived table:

| Axis | Variants |
|---|---|
| Nesting depth | 1, 2, 3, and 5-level CTE chains |
| Nesting kind | CTEs and derived tables (and one query using both) |
| UNION ALL | 2, 3, and 4 separate `match_all` branches |
| JOIN | CTE⋈CTE, derived⋈derived, cross-stream |
| Window | `ROW_NUMBER`, `RANK`, `DENSE_RANK`, partitioned aggregates |
| Negation | `NOT match_all`, and mixed with positive `match_all` |
| Combined with | `COUNT(DISTINCT)`, `CASE`, `HAVING`, `histogram()` |

Each case creates a real panel, runs it via the dashboard search path,
then
asserts row count against the DuckDB oracle and that no expected column
was
dropped from the projection. A further 30 cases run the same SQL through
**both**
Logs and dashboard and diff the results — that's what catches a rewrite
firing on
only one path.

Queries are reused from the existing query-agent corpus rather than
duplicated,
so the two suites can't drift: query-agent verifies the values via the
Logs path,
this verifies the same SQL survives the dashboard path.

Cases are **discovered by classifying the SQL**, not from a hand-written
ID list,
so new combinations added to the corpus are picked up automatically.
Test ids
name the shape (`Q1022-cte1_union_cdist_4ma`), so a failure identifies
the
combination without opening the corpus.

Plus 14 tests for the panel endpoints themselves — AddPanel /
UpdatePanel /
DeletePanel, contract + CRUD lifecycle + 7 error paths derived from
`DashboardError` in `src/core/src/http.rs`.

## Changes

| File | |
|---|---|
| `tests/dashboards/test_dashboard_cte_panels.py` | new — 103 tests |
| `tests/dashboards/test_dashboard_panels.py` | new — 14 tests |
| `tests/dashboards/conftest.py` | new — shared fixtures |
| `support/panel_queries.py` | new — discovery + panel helpers |
| `support/endpoints/dashboards.py` | +78, additive |
| `support/endpoints/search.py` | +36, additive |
| `support/factories.py` | +73, additive |

The three shared-file edits are **187 added lines, 0 modified** —
nothing
existing can break.

## Results

168 tests in tests/api-testing/tests/dashboards/ (117 new + 51
pre-existing)
166 passed, 1 skipped, 1 xfailed     # full nested-query corpus present
74 passed, 2 skipped, 1 xfailed     # current corpus on main


Repeated 2× and 4× respectively, no flakes.

Discovery scales with the corpus: on main it finds 8 nested `match_all`
cases,
and 71 once the larger sentinel set is present. Cases that aren't in the
corpus
skip with an explanatory message instead of failing, so this is safe to
merge in
either order.
2026-08-17 09:47:17 +00:00
Neha PandClaude Opus 4.7 8a4f7ea23b test(query-agent): add 70 match_all sentinel queries for FST rewrite path coverage (#13824)
## Summary

Adds Q1003–Q1072 (70 queries) to
`tests/test-data/query-agent/queries/full_text_search.json`. All
exercise the `match_all` + FST projection rewrite path fixed in #13808,
under diverse plan shapes.

Structural patterns mirror real production customer queries (fan-out
UNION ALL, multi-CTE, CSE-triggering scalar wrappers on the FTS field)
but use **only test-fixture fields and values** — no customer
identifiers.

## Shape coverage

| Family | Range | Count | What it exercises |
|---|---|---|---|
| A. Subquery + `match_all` + GROUP BY | Q1003–Q1012 | 10 | Simple
inline-view aggregations |
| B. CTE with fan-out UNION ALL, `match_all` per branch | Q1013–Q1027 |
15 | Dominant customer pattern (multi-branch UNION + per-branch FTS +
LIKE/regex + outer aggregate) |
| C. CTE + scalar wrapper on `log`
(trim/upper/split_part/regexp_replace/substring/concat/length) +
`match_all` | Q1028–Q1037 | 10 | **CSE-trigger sentinels** — the
wrappers cause DataFusion CSE to insert the intermediate
`ProjectionExec` that #13808 fixes |
| D. Deep multi-CTE + `match_all` + regex/LIKE/CASE combos | Q1038–Q1047
| 10 | Deep plan trees, many projection candidates |
| E. Edge cases (DISTINCT, HAVING, CASE, IN-list) | Q1048–Q1052 | 5 |
Corner combinations |
| F. `match_all` inside JOINs (self-join, LEFT JOIN, cross-stream) |
Q1053–Q1058 | 6 | JOIN builders inject their own projections |
| G. `match_all` + window functions (ROW_NUMBER, RANK, DENSE_RANK,
SUM/COUNT OVER) | Q1059–Q1063 | 5 | Window planning inserts projections
above filter |
| H. Negated `match_all` (NOT, AND NOT, UNION of pos/neg) | Q1064–Q1066
| 3 | Rewriter's negation code path |
| I. Multiple `match_all` in same query (AND, OR, nested) | Q1067–Q1069
| 3 | Two FST fields injected simultaneously |
| J. `histogram` + `match_all` time-series | Q1070–Q1072 | 3 | Common
customer pattern; histogram optimizer + FST rewriter together |

## Verification

- All 70 queries have DuckDB oracle results auto-populated via
`compute_counts.py`.
- Two initial cross-stream JOIN queries (Q1055, Q1058) were rewritten to
disambiguate the FTS field between streams — now they scope `match_all`
inside a per-stream CTE before joining.
- Audit for customer-token leakage (pharma, tomcat, o2_test, usgm,
storenumber, tagname, cash, credit_card, etc.) returns **zero hits**
across Q1003–Q1072.

## Implementation notes

- Added generator script
[tests/test-data/query-agent/gen_q1003_q1052.py](tests/test-data/query-agent/gen_q1003_q1052.py)
alongside existing `gen_q201_q400.py` / `gen_q511_q556.py` convention.
Deterministic regeneration.
- Wide JSON churn across the other 12 category files is
`compute_counts.py` re-baselining expected results against a fresh
`BASE_TS` (`base_ts_override.json` is untracked); tuple-order shuffles
are harmless under `results_mode: rowsort`.
- Coverage is most meaningful under the `remove_filter_with_index_false`
CI leg (added in #13810), where `match_all` actually reaches
`RewriteMatchPhysical`. Under the default flag (`true`), the index
optimizer removes `match_all` before the rewriter runs, masking the bug
path.

## Test plan

- [ ] `api_query_agent / single_node_opt_true` — green
- [ ] `api_query_agent / single_node_opt_false` — green
- [ ] `api_query_agent / remove_filter_with_index_false` — green (this
is the leg that actually validates the rewriter under all 70 shapes)
- [ ] Q1003–Q1072 all pass on both memtable and parquet phases

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-17 06:42:06 +00:00
Hengfei Yang 41ff3cd9b6 feat: replace distinct_values_* streams with tantivy TopK for the _values API (#13834)
Design at: #13822

## Summary

`distinct_values_*` metadata streams existed to serve one reader: the
`_values` API. They are a redundant — and lossier — materialization of
what the tantivy secondary index already holds, and the index has the
collectors needed to replace them outright (`SimpleTopN` /
`SimpleDistinct`). This PR removes the distinct streams from the write
and read paths and lets the `_values` API be served by the tantivy index
optimizer.

### 1. Treat distinct value fields as secondary index fields (read-time
union)

- `get_stream_setting_index_fields()` — the single accessor used by both
the index build side (ingester parquet/pack jobs, compactor merge) and
the query side (leader/follower index optimizers) — now unions
`distinct_value_fields` into the effective index field set, gated on
`enable_distinct_fields`. Fields that cannot be secondary-indexed (full
text search keys, partition keys, reserved columns) are skipped; the
values API falls back to a normal scan for those.
- The distinct list is deliberately **not merged into
`settings.index_fields`**: the two stay independent, so removing a
distinct value field (or disabling `enable_distinct_fields`) cleanly
stops its indexing with no residue, and the user-managed `index_fields`
list is never mutated behind their back.
- Correctness gate: a distinct-only field is considered indexed since
its `added_ts` (`get_stream_setting_index_updated_at_for_fields`), so
parquet files older than that are served by DataFusion scan instead of
the index (`split_file_list_by_time_range`). `added_ts` is set on add
and reset when `enable_distinct_fields` is re-enabled (existing
behavior). For streams configured before this release the fields only
start being indexed at upgrade time, so `added_ts` is additionally
clamped by a release-wide cutoff `/tantivy/distinct_fields/updated_at` —
a lazy marker initialized to the first access after upgrade, following
the existing `ttv_secondary_index_updated_at` pattern. No data migration
and no `DB_SCHEMA_VERSION` bump needed.

### 2. `_values` API always queries the original stream

- Removed `can_use_distinct_stream()` and the `distinct_values_*` /
`SUM(count)` / `StreamType::Metadata` rewrite from `values_inner` (GET
`/_values`, formerly `values_v1`) and `build_search_request_per_field`
(POST `/_values_stream`).
- The generated SQL shapes are exactly what the index optimizer detects:
- `SELECT f AS zo_sql_key, COUNT(*) AS zo_sql_num FROM s GROUP BY
zo_sql_key ORDER BY zo_sql_num DESC, zo_sql_key ASC` + limit →
`SimpleTopN`
- `no_count`: `SELECT f AS zo_sql_key FROM s GROUP BY zo_sql_key ORDER
BY zo_sql_key ASC` + limit → `SimpleDistinct`
- The `keyword` filter (`str_match_ignore_case`) is a valid index
condition, so keyword-filtered values queries stay on the index path
too.

### 3. Remove distinct values generation

- Dropped the per-record distinct value collection in logs and traces
ingestion and the whole `core::metadata` module (channel, mem-table,
flush job, shutdown hook).
- Removed the now-unused `DISTINCT_FIELDS` static and the
`ZO_FEATURE_DISTINCT_EXTRA_FIELDS` / `ZO_DISTINCT_VALUES_INTERVAL` /
`ZO_DISTINCT_VALUES_HOURLY` config knobs, including the `SUM(count)`
merge rollup the last one gated — legacy `distinct_values_*` streams now
compact as ordinary metadata streams. The default fields
`DISTINCT_FIELDS` carried (`service_name`, `operation_name`) are already
default secondary index fields via `SQL_SECONDARY_INDEX_SEARCH_FIELDS`,
so traces `_values` queries on them remain index-served.
- Renamed `values_v1` to `values_inner`: the `v2` variant was removed
long ago (#5087) and the distinct stream switch inside `v1` is gone now
too, so the version suffix was pure history.

### 4. Default fields after this change

- `service_name`, `operation_name`: default secondary index fields
(unchanged, via `SQL_SECONDARY_INDEX_SEARCH_FIELDS`).
- `trace_id`: now also a built-in default **bloom filter** field
(`BLOOM_FILTER_DEFAULT_FIELDS`, gated on
`ZO_FEATURE_DEFAULT_INDEX_FIELDS_ENABLED`), in addition to being a
default index field — bloom sits on top of the secondary index and
prunes files for high-cardinality equality lookups.
- There is no "default distinct fields" concept anymore.

### 5. Fix surfaced by e2e: SimpleDistinct returned null values as `""`

The tantivy index encodes null/missing field values as empty strings
(`index_builder` adds `""` for null docs), so the `SimpleDistinct`
term-dictionary walk returned `""` as a distinct value — sorted first in
ascending order, it surfaced as a blank first option in dashboard
variable dropdowns, which the DataFusion `GROUP BY` path never returns
(its null group sorts last). This was exposed once distinct fields
became index-served; `handle_simple_distinct` now skips empty terms
(with a unit test).

### 6. Drive-by: fix the failing `api_fuzz_tests` CI job

`CreateAlertRequestSchema` was an untagged documentation union with two
identical variants, producing `oneOf: [CreateAlertRequestBody,
CreateAlertRequestBody]` in the OpenAPI spec. Under `oneOf` semantics
(exactly one branch must match) no instance can ever validate, so
schemathesis filtered out every generated example and `api_fuzz_tests`
failed on `POST /api/v2/{org_id}/alerts` on every CI run since that
schema landed. The handler deserializes `CreateAlertRequestBody`
directly, so the annotation now documents exactly that type; runtime
behavior is unchanged.

## What stays (intentionally)

- `distinct_value_fields` / `enable_distinct_fields` in stream settings:
still the user-facing way to declare values-API fields; the list now
drives the read-time index-field union described above.
- The `distinct_value_fields` DB table that tracks dashboard/report
usage of these fields (still gates removal in the settings API).
- `DISTINCT_STREAM_PREFIX` helpers, retention sync and stream-deletion
cleanup for existing `distinct_values_*` streams — legacy stream data
keeps compacting (as ordinary metadata streams) and ages out via
retention; no new records are written to them.

## Testing

- `cargo check --workspace --all-targets` and `cargo clippy --workspace
--all-targets` clean.
- Enterprise build verified with `Cargo.toml.openobserve` (`cargo check
--all-targets`).
- New unit tests for the index-field union (skip rules for
FTS/partition/reserved fields, `enable_distinct_fields` gating) and the
`added_ts` / release-cutoff indexed-since resolution; schema, infra,
config and api-search unit tests pass. A few pre-existing failures on a
clean tree (config `ssrf_guard`, one flaky sqlite-backed destinations
test, one `.env`-dependent index-fields test) are local-environment
issues and unrelated.

Closes #13822
2026-08-16 15:19:31 +00:00
Prabhat Sharmaandgithub-actions[bot] 0dba67d638 feat: composite alerts (#13797)
Design at : alerts/composite_alerts/composite_alerts.md

## Summary
- add composite alert graph persistence, evaluation, scheduling,
lifecycle APIs, and deletion safeguards
- add composite alert UI forms, preview, references, list/detail
integration, and localization
- add Rust, API, Vue, and Playwright contracts

## Verification
- cargo fmt --all -- --check
- cargo check -p openobserve-core
- cargo check -p openobserve-api-management
- npm run type-check:composite-alerts
- npm run type-check:app
- npm run lint:ci -- --quiet
- design/token/style checks
- targeted Rust/Vitest suites
- browser QA against local alerts UI

## Follow-ups (intentionally deferred, documented in the spec)
- `RunOutcome::Error`/`NotifyFailed` outcome records on composite
delivery failure (§11.5)
- Incident detail live-definition resolver for composites + "definition
unavailable" retention (§9.6)
- `alert_composite_*` observability metrics (§16)
- SLO/stream delete-conflict 409 bodies with readable-vs-hidden parent
split (§10.3)
- Super-cluster startup preflight (§18)

<img width="1650" height="979" alt="image"
src="https://github.com/user-attachments/assets/3def5246-7bf7-48fe-9a42-c0ac08271ea9"
/>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-15 00:57:20 +00:00
ktx-akshay bd28872ddc test: improve stability of Alpha environment tests (#13817) 2026-08-14 12:25:41 +00:00
Neha PandClaude Opus 4.7 7fbce61fd1 test: add Q1001 + Q1002 sentinels for match_all/CSE + widen CI matrix (#13810)
Two regression sentinels for the FST projection rewrite path fixed in
openobserve/openobserve#13808 (currently in the merge queue on
branch-v0.40.0 and awaiting the main-branch counterpart):

  Q1001 — CTE with trim(log) alias, match_all in WHERE, column-pruned
  outer SELECT. Mirrors the shape of the PR's own unit test
  (test_cse_projection_schema_propagation) — CSE inserts a
  ProjectionExec between filter and datasource; a regression in the
  rewriter's f_down handling would surface here.

  Q1002 — five-CTE pipeline (filtered → in_shop → scoped → normalized
  → cleaned) mixing match_all with LIKE, split_part, regexp_replace ID
  normalization, and GROUP BY aggregation. Higher structural pressure:
  more intermediate ProjectionExec nodes and more chances for a stale
  column index to leak through.

Uses only test-fixture fields (log, resource_path, page_url,
http_method); no customer identifiers.

Also adds a third leg to the query-agent CI matrix that runs with
ZO_FEATURE_QUERY_REMOVE_FILTER_WITH_INDEX=false — exercises the
non-index-filter path that the match_all projection rewriter also
touches.

The wide JSON churn across the other query files is compute_counts.py
re-baselining expected results against a fresh BASE_TS
(base_ts_override.json is untracked); tuple-order shuffles are harmless
under results_mode: rowsort.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-13 09:46:15 +00:00
7f647109fc fix: preserve projection schema when rewriting match_all (#13808)
## Summary

Forward-port of #13798 to `main`.

- rebuild `ProjectionExec` expressions against the rewritten child
schema before DataFusion validates the projection
- carry full-text search fields through intermediate projections and
preserve the original `FilterExec` output schema
- reject a full-text search field that a projection already exposes as
an alias for a different expression, instead of silently searching the
wrong data
- add a regression for a projection sitting between the filter and the
datasource

## Root cause

`RewriteMatchPhysical` expanded the `NewEmptyExec` projection with
full-text search fields. When an optimizer rule (CSE, for example) had
inserted an intermediate `ProjectionExec`, adding the FST field changed
the child column positions while the projection still referenced its
columns by their old indices, so DataFusion rejected the plan.

The old `filter_projection` was also computed as positions inside
`parquet_projection`, i.e. in the datasource output space. That is only
valid when the filter reads the datasource directly. It is now mapped by
name against the actual filter input schema, and the field order of
`filter.schema()` is preserved so the operators above the filter keep
reading the same columns.

Two latent issues are fixed along the way:

- a scan with `projection == None` (all columns) was narrowed down to
only the FST fields
- `schema.index_of(...).unwrap()` in the datasource rewrite panicked
instead of returning a plan error

## Differences from #13798

`main` is on DataFusion 54 and builds the filter through
`FilterExecBuilder`, so step 5 uses `apply_projection` and keeps
`with_fetch`.

The regression test also differs. The CTE query used in #13798 no longer
produces an intermediate projection on DataFusion 54 — CSE does not
extract `btrim(log)` there — so the test would be vacuous.
`test_projection_between_filter_and_scan` builds the offending plan
shape directly instead, which does not depend on optimizer internals.

## Verification

- `cargo test -p search --no-default-features --lib
datafusion::optimizer::physical_optimizer::rewrite_match` — 7 passed, 0
failed
- `test_projection_between_filter_and_scan` fails on `main` without the
fix (`SchemaError: Unable to get field named "__common_expr_1"`)

---------

Co-authored-by: Neha P <neha@openobserve.ai>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-13 07:53:44 +00:00
ktx-akshay d1f8941512 test: stabilize test cases for the Alpha environment (#13551) 2026-08-13 06:41:03 +00:00
Shrinath Rao 496c0d5842 ci(tests): pure-API Playwright spec classifier + anti-drift guard (Phase 3) (#13794)
## What

**Phase 3** of the Playwright→pytest API-test migration — the anti-drift
guardrail. (Phase 1 = Correlation #13788 / o2-ent#2419; Phase 2 = Alerts
#13791.)

> 📚 **Stacked on #13791.** Base is the Phase 2 branch so the diff shows
only these two files; GitHub auto-retargets this PR to `main` once
#13791 merges.

## Changes

- **`tests/ui-testing/scripts/classify-specs.js`** — codifies the
migration classifier. Labels each `*.spec.js` as `UI_ONLY` / `UI+SETUP`
/ `PURE_API` / `REVIEW` from UI markers (`page.goto`, `.click`,
`locator`, `getByRole`, `toBeVisible`, page-object imports, nav
helpers…) vs API markers (`page.request`, `authedRequest`,
`getAuthHeaders`, `/_json`, api-helper imports). Modes:
  - `--all` — regenerate the inventory (summary + PURE_API/REVIEW lists)
  - `--check <files>` — exit 1 if any file is pure-API (the guard)
  - `--json`
- Conservative by design: a spec is `PURE_API` only when **not one** UI
marker appears, so genuine UI tests are never nagged.
- **`.github/workflows/pure-api-spec-guard.yml`** — runs `--check` on
the **newly-added** `*.spec.js` files in a PR and **fails** if any is
pure-API (backend/API assertions with no browser). Pushes new API tests
into pytest (`tests/api-testing/`) from the start. Only added files are
checked, so editing an existing UI spec never trips it.

## Why

The migration removed 9 pure-API specs from the browser harness
(Correlation ×5, Alerts ×4). Without a guard, new API tests get authored
as Playwright specs again and the drift returns. This makes the rule
enforceable in CI instead of relying on reviewer memory.

## Verification

- Re-ran the classifier over all **257** specs → **`PURE_API: 0`** (both
phases fully removed their targets; zero drift). Sole `REVIEW` =
`cleanup.spec.js`, a teardown utility (not a test).
- Self-tested the guard: ✅ flags a synthetic pure-API spec (exit 1 +
actionable message), ✅ passes a UI spec (exit 0).
- Resolved the 6 previously-uncertain "Category C" specs → all UI or
teardown; **0 additional migrations**.
2026-08-13 05:07:55 +00:00
Shrinath Rao 673bedb468 test(alerts): migrate multi-alert API specs from Playwright to pytest (Phase 2) (#13791)
## 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.
2026-08-13 01:41:58 +00:00
Shrinath Rao c64a59865e test(correlation): remove 5 migrated API specs from Playwright (ported to pytest in ENT) (#13788)
## 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).
2026-08-12 16:11:44 +00:00
Shrinath Rao 2834186b8a test(e2e/alpha1): unique per-run alert destination/template so Add Alert button enables (#13783)
## 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`).
2026-08-12 11:57:01 +00:00
ktx-akshay fa3d294999 test(reports): e2e regression coverage for dashboard scheduled reports drawer (#13569) (#13781)
## What

Adds E2E regression coverage for the dashboard **Scheduled Reports
drawer**, verifying the two
bugs fixed in #13569.

No production code is touched — this is test-only.

## Why

#13569 (`fix: dashboard reports drawer duplicates entries and hides
reports in custom folders`)
shipped with unit-test updates only. The drawer had no E2E coverage at
all, so both regressions
could return unnoticed.

| Bug | Root cause | Fix under test |
|-----|-----------|----------------|
| **1. Rows duplicate on reopen** | `scheduledReports` was seeded with
the `reports` prop and `formatReports()` pushed onto it, mutating the
prop. The drawer stays mounted between opens, so each reopen multiplied
the rows. | `ScheduledDashboards.vue` now rebuilds the array with
`.map()` |
| **2. Custom-folder reports hidden** | `reports.list(org, folderId,
dashboardId)` passed the **dashboard's** folder, but `folder_id` filters
by the **report's own** folder | `ViewDashboard.vue` passes `""` so
`folder_id` is omitted |

## Tests added — 9, all passing


`tests/ui-testing/playwright-tests/Reports/scheduledReportsDrawer.spec.js`

| # | Test | Covers |
|---|------|--------|
| 1 | P0: Drawer opens and lists the dashboard's cached report |
baseline |
| 2 | P0: Bug 13569-1 — reopening the drawer does not duplicate rows |
**bug 1** — exact row count across 3 close/reopen cycles |
| 3 | P0: Bug 13569-2 — report saved in a custom report folder is listed
| **bug 2** — report in custom folder, dashboard in default folder |
| 4 | P1: Reports list request omits `folder_id` | **bug 2** — pins the
network contract the fix changed |
| 5 | P1: Switching tabs re-filters the list without duplicating rows |
`filterReports()`, the second path bug 1 corrupted |
| 6 | P1: Search filter does not survive a reopen or leave stale rows |
filter + rebuild interaction |
| 7 | P2: Dashboard with no reports shows the empty state on every open
| empty state across reopen |
| 8 | P2: A report bound to another dashboard is not listed | guards
against over-correcting bug 2 (`dashboard_id` scoping must survive) |
| 9 | Cleanup: Remove all test resources via API | teardown |
2026-08-12 11:01:49 +00:00
Shrinath Rao a28dcda276 test(correlation): standardize e2e suite onto framework harness (phase 0 — reference pattern) (#13775)
## What & why

Begins consolidating the **correlation (service-streams) e2e suite**
onto the standard `tests/ui-testing` framework. Today those specs live
in a self-contained parallel harness (own configs, own auth, own `O2_*`
env vars, per-test orgs with no teardown, non-standard names) and are
**not registered in any CI matrix** — so they never run automatically.

This is **Phase 0**: land the shared standard-shaped API helper and
convert the first spec as the **reference pattern** for review, before
batch-converting the rest.

## Changes
- **`utils/correlation-api-helpers.js`** (new) — single source of truth
for correlation API plumbing. Auth via `authedRequest`/`getAuthHeaders`
(framework session + 401/403 self-heal), per-test **fresh-org create +
teardown**. No `test`/`expect`.
- **`Correlation/correlation-discovery.spec.js`** (new) — converted from
`journeyA`: `enhanced-baseFixtures`, `@correlation`/`@P0` tags,
Copyright + JSDoc header, `testLogger.testStart`, standard `ZO_*` env
and naming. Assertions ported **1:1** (DISC-01/02/03).
- Remove `Correlation/journeyA-discovery.api.spec.js` (superseded).

## Deliberate deviation (documented)
Correlation discovery / identity config / `_reset` are **org-global and
destructive**, so each test provisions a **fresh org** and tears it down
in `afterEach`. This keeps the isolation the feature genuinely needs
while fixing the real defect in the old suite: **orgs were created but
never deleted** (leak).

## Validation
- `node --check` clean; requires resolve.
- Collects cleanly under `playwright.config.js` (`--list` → 3 tests,
tagged).
- ⚠️ **Green run pending an enterprise env** (service_streams 403s on
OSS). Not wired into CI yet, so `main` stays green. **Draft** until
validated.

## Follow-ups (later phases)
Batch-convert remaining 4 API + 3 UI specs · revive
`correlationSettings.spec.js` against the new UI · register a dedicated
**Correlation** shard in the ENT `ci_matrix.ent.json` · delete
`corr-*.config.js`.
2026-08-12 07:43:41 +00:00
Shrinath Rao 6650950a50 test(e2e/alpha1): fix share-link redirect origin and cloud metrics ingest auth (#13776)
## Why

The scheduled **Alpha1 (Cloud)** e2e run failed on three jobs
([o2-enterprise run
31547517666](https://github.com/openobserve/o2-enterprise/actions/runs/31547517666)):
**Logs-Features** (7), **Functions** (11), **RegressionSet** (4).
Root-caused each against a freshly-deployed alpha; two are genuine test
heals, one was pure environment skew.

## Root causes & fixes

### 1. Logs-Features — shareLink + monaco-query-prefill (7 tests) ✅
fixed
On cloud the app builds the share link from its **public** base URL
(`*.external.zinclabs.dev`), while the tests authenticate against the
ingress host (`*.internal.zinclabs.dev`). `page.goto(sharedUrl)`
navigated **cross-origin**, dropped the auth cookies, and bounced to Dex
— so every post-redirect state assertion read back `null`.

**Fix:** re-point the short URL's origin to `ZO_BASE_URL` in
`clickShareLinkAndGetUrl()` (right next to the existing HTTP→HTTPS
"maintain auth context" step). `/short/<id>` resolves identically on the
backend regardless of ingress host, so only the origin changes and the
session stays valid. Same-origin envs (localhost / matching deployed
host) are untouched.

### 2. RegressionSet — alerts Bug #9967 PromQL (1 test) ✅ fixed
`ingestMetricsData()` authenticated with `ZO_ROOT_USER_EMAIL:PASSWORD`,
but **cloud ingestion needs `email:passcode`**. The ingest silently
401'd → the `e2e_test_cpu_usage` metrics stream was never created → the
PromQL alert flow timed out selecting it.

**Fix:** build auth via `getAuthHeaders()` (passcode on cloud, root
creds on self-hosted), refresh the passcode from the live browser
session first, and poll until the stream is indexed so the wizard's
stream-select doesn't race WAL lag.

### 3. Functions — radio → segmented toggle (11 tests) — no change
needed
The page object was already migrated to the segmented toggle in #13766;
these failures were the **alpha frontend lagging the merged test code**.
Green after the redeploy with zero code change.

## Verification (against alpha, `auto_org_1`)
- `shareLink.spec.js` + `monaco-query-prefill.spec.js` → **11/11
passed**
- `alerts-regression.spec.js` (full) → **5/5 passed** (#9967, #10110,
#10899, #10872, #10472)
- `Functions/js-transform-type.spec.js` → **7/7**,
`row-expansion.spec.js` → green

## Scope
OSS-only (test files). The ENT `playwright_alpha1.yml` checks out
opensource at `main`, so merging makes the next scheduled run pick this
up. It can also be pre-verified by dispatching that workflow with
`o2_opensource_repo=test/alpha1-e2e-heals-0812`.
2026-08-12 06:35:48 +00:00
Prabhat Sharma fb50aaea7f fix(deps): resolve 39 Dependabot alerts (#13774)
## Summary

- update vulnerable web transitive dependencies (`axios`,
`brace-expansion`, `fast-uri`, `http-proxy-middleware`, `js-yaml`,
`uuid`, and `websocket-driver`)
- remove the unused `node-polyfill-webpack-plugin`, eliminating the
unpatched `elliptic` dependency chain
- update Playwright to 1.55.1 and `node-forge` to 1.4.0 in the UI test
lockfile
- adapt `BaseImport` to Axios 1.18's stricter response-header type

This is expected to resolve 39 of the 41 currently open Dependabot
alerts.

## Remaining alerts

- `rmcp` remains on the project's custom `rmcp-openapi` fork. Updating
it safely requires a coordinated change to the enterprise
`Cargo.toml.openobserve`, which currently replaces the OSS manifest
during enterprise builds and pins the old API. The compatible
intermediate upstream releases also lack the fork's required
`allOf`/`anyOf` support.
- `thrift` 0.17.0 remains through `parquet` 58.4.0. The patched Thrift
line (0.23+) is outside Parquet 58's version constraint. Parquet 59
removes the Thrift dependency, but adopting it requires a coordinated
Arrow/DataFusion 59 upgrade.

## Validation

- `npm ci` and `npm audit --package-lock-only` in `web/` (0
vulnerabilities)
- `npm ci` and `npm audit --package-lock-only` in `tests/ui-testing/` (0
vulnerabilities)
- `npm run type-check:app`
- `npm run lint:ci -- --quiet`
- `npm run lint:design:strict`
- `NODE_OPTIONS=--max-old-space-size=8192 npm run build`
- `npm run test:unit -- --run` (41,917 passed, 404 skipped)
- `npx vitest run src/components/common/BaseImport.spec.ts --environment
jsdom` (41 passed)
- Playwright test discovery under 1.55.1 (2,076 tests in 265 files)
- `cargo fmt --all -- --check`
- `cargo test -p openobserve-mcp` (94 passed, 1 ignored)
- `cargo audit` (0 vulnerabilities; 2 allowed unmaintained warnings)
2026-08-11 22:17:35 +00:00
Prabhat Sharma c451db0835 fix: tighten workflow permissions and CSS selector escaping (#13772)
## Summary

- set explicit least-privilege permissions for audit, JavaScript
license, and release workflows while preserving release-job write access
- grant the spam-cleaner workflow the issue and pull-request scopes
required to delete comments
- escape quotes and backslashes before interpolating log toast text into
CSS attribute selectors

## Validation

- parsed the four modified workflow files as YAML
- ran Node syntax checking on
tests/ui-testing/pages/logsPages/logsPage.js
- ran git diff whitespace validation
2026-08-11 18:57:58 +00:00
Prabhat Sharmaandgithub-actions[bot] 360b989272 feat(slos): PromQL support for count and time-sliced SLOs, plus SLO/alert regression fixes (#13761)
Supersedes #13738, whose commit is included here as the first of four.

Adds PromQL support to count and time-sliced SLOs, lets metrics streams
choose SQL or PromQL, and fixes three shipped bugs found along the way.

## Shipped bug: every SQL time-slice SLO recorded wrong history

`fetch_rows`' `SliQueryPlan::Single` arm normalized every hit with
`with_good_column: true`, but that arm serves **two** shapes and only
one projects `zo_slo_good`:

| shape | projects | result |
|---|---|---|
| count single-query | `zo_slo_good` + `zo_slo_value` | correct |
| time slice | `zo_slo_value` only | `good` defaulted to `0.0` |

So every SQL time-slice slice was classified from `0.0` instead of its
aggregate. With a positive threshold, `<`/`<=` (e.g. `p95 < 300ms`)
recorded every slice **good** — the SLO read ~100%, the budget never
burned, and no alert on it could fire. `>`/`>=` recorded every slice
**bad** and fired continuously. Shipped in v0.92.0 (`4772df1723`);
`to_row` had no time-slice test, which is how it got through.

Fixed by deriving the flag from the SLI type in a new
`single_query_rows`. Deliberately **not** fixed inside `to_row` by
falling back to `total` — that would read a count bucket whose `SUM`
came back NULL as *fully good*, inventing uptime. A test forbids it.

**Stored history is not rewritten.** It ages out within one window; an
operator can force a rebuild sooner by making a real edit (a no-op
re-save does nothing — `update` bumps only on `definition_changed`). No
auto-migration: bumping every time-slice SLO at boot would freeze every
attached alert org-wide until backfill cleared the coverage floor, and
double each org's row budget for 97 days — a self-inflicted monitoring
gap to repair a reporting error.

## PromQL support

**Time slice** carried a `query_language` field the planner ignored, so
a PromQL time-slice SLO saved cleanly and measured nothing, forever.

- `SliQueryPlan::PromQlValue` — one expression, not a good/total pair,
planned at slice ends from the same helper the count arm uses.
- The expression is passed through **verbatim**. Grouping comes from
series labels; wrapping it in `sum by (...)` the way the SQL arm injects
GROUP BY would destroy the aggregate (the sum of four pods' p95 is not a
p95).
- `promql_value_rows` **rejects** two series colliding on one
`(slice_start, group_key)` via `RejectReason::AmbiguousSeries` instead
of summing. Summing is right for counters (what `promql_rows` does) and
meaningless here. Detection is unconditional, so the ungrouped case — an
unaggregated `histogram_quantile` returning one series per instance — is
covered. Rejects are plumbed through `fetch_rows` into
`PassResult.rejected` on both the live and backfill paths.

**Count** already had a working `CountSource::PromQl` backend that no UI
could reach.

## Metrics streams choose SQL or PromQL

`language_suits_stream` refused SQL whenever `stream_type == "metrics"`,
and the form derived the language from the stream type — so metrics had
no SQL option. A metrics stream is an ordinary stream with a `value`
column, and `SELECT histogram(_timestamp, '5 minute'), avg(value) FROM
cpu_usage` is a perfectly good SLI. SQL is now valid for any stream
type; PromQL stays metrics-only. The form asks via a toggle (defaulting
to PromQL) instead of deriving.

## Validation

- `scope` refused for PromQL — a SQL `WHERE` fragment with nowhere to go
in a PromQL plan.
- Empty `query` refused for **both** languages (the SQL aggregate was
previously never validated — same silent-freeze shape).
- Expressions parsed with `promql-parser` at save, in `core` (`config`
has no parser).
- **Parse-only by design.** A grouping check requiring the root to be an
aggregation whose `by(...)` matches `group_by` wrongly rejects
`histogram_quantile(0.95, sum by (le, region)(...))` (function-call
root; `le` is a necessary label superset) and binary-op roots like `sum
by (region)(increase(a[5m])) - sum by (region)(increase(b[5m]))`. A
"warn instead" variant needs a warnings channel the save API doesn't
have.

## Also fixed

- **Time-slice comparator 422.** `SliConfig::TimeSlice.comparator` has
no serde default, so a dropdown the user never opened was omitted and
the save died with a bare 422 — blocking creation of *any* time-slice
SLO from the UI. Seeded to `<` only when blank, so a stored comparator
survives an edit.
- **SLO/alert/service-stream regressions** (the commit from #13738):
fractional-threshold serde round-trip, `SloAlertCondition` UI, alerts
table, service streams.

## Verification

- 487 config · 206 core · 45 infra · 418 frontend — all passing; clippy
`-D warnings`, `cargo fmt`, prettier, eslint, `vue-tsc` all clean.
- Driven through a real browser against a running server (isolated
instance, own data dir). All four SLI/language combinations create and
store correctly: count·PromQL, count·**SQL over metrics**,
time-slice·PromQL, time-slice·**SQL over metrics**. Also verified: the
toggle swaps field shapes both ways, scope shows for SQL and hides for
PromQL, PromQL-only hints appear only in PromQL mode, and editing a
stored SQL-over-metrics SLO reopens as SQL with its predicate intact.
- Validation rejections confirmed live with actionable messages:
PromQL-with-scope, unparseable PromQL, and an empty SQL aggregate.

Known limitation, pre-existing: `threshold` likewise has no serde
default and is not seeded, so leaving it blank still 422s. That is
genuine user error rather than an untouched-control trap; adding form
validation was out of scope.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-11 17:35:52 +00:00
Bhargav b07b3d4229 feat(functions): replace language radios with a segmented toggle (#13766)
The Add Function header selected VRL vs JavaScript with a radio group
and a single info icon. That icon only ever described the
ALREADY-SELECTED language, so learning what the other one was for meant
switching to it first.

- Radio group -> OFormToggleGroup segmented control with a sliding
indicator, each option carrying a language mark (V / JS). The badge
variants match the transType column in FunctionList so VRL/JS read the
same everywhere.
- Give EACH option its own info icon and static tooltip, so both
languages are readable without selecting either. Forced-language hosts
(workflow function nodes are JS-only) hide the toggle, so the locked
language's tip moves to a standalone icon.
- Add a pipe divider between the two options, and one between the
function-name title and the toggle.

data-test renames `...-vrl-radio` -> `...-vrl-option` (same for js):
"radio" described the old implementation. E2E page objects would have
broken on this change regardless of naming -- toggle items expose
data-state="on|off", not the radio group's "checked|unchecked".

Verified: FunctionsToolbar.spec.ts 18/18 pass, vue-tsc clean. ESLint
could not be run locally (@intlify/eslint-plugin-vue-i18n missing from
node_modules).
2026-08-11 13:25:45 +00:00
ktx-akshay 4531ae74d8 test(dashboards): e2e coverage for favorites cross-folder search fix (#13437) (#13758)
## Summary

Adds E2E coverage for #13437 (`fix: dashboard all folder search for
favorites`).
That PR changed three things in `Dashboards.vue`; this adds one test per
change.

## Test cases added

| # | Test case | Covers | Priority |
|---|-----------|--------|----------|
| 1 | `should surface a non-favorited dashboard in the Favorites folder
when searching across folders` | Favorites view yields to an active
cross-folder search instead of short-circuiting to the stored favorites
list | P0 |
| 2 | `should restore the favorites-only list after clearing a
cross-folder search` | Clearing resets both scope models (`searchQuery`
+ `filterQuery`), so the favorites-only list comes back | P1 |
| 3 | `should expose a clear button for a current-folder search and
restore the full list` | Search field is unconditionally `clearable` —
the clear (X) button now renders in "This folder" scope too | P1 |

## What each test proves

**1.** Seeds two dashboards in one folder, favorites only the first.
Asserts the
un-favorited one is **absent** from the Favorites view, then switches
the scope
toggle to "All folders", searches it by name, and asserts it appears.
Before the
fix the rows computed returned `favorites.value.map(...)` whenever the
`__favorites__` pseudo-folder was active, so a cross-folder hit could
never render.

**2.** Runs that same cross-folder search, then clicks clear and asserts
the field
is empty, the searched-for dashboard drops out, and the favorite is
listed again.

**3.** Searches in the default "This folder" scope, asserts the filter
is active
(second dashboard filtered out), that
`[data-test="dashboard-search-clear"]` is
visible, and that clicking it empties the field and restores both rows.
Previously `:clearable="searchAcrossFolders"` meant no clear button
existed in
this mode at all.

## Files changed

-
`tests/ui-testing/playwright-tests/dashboards/dashboard-favorites.spec.js`
— 3 new tests (file now has 9)
- `tests/ui-testing/pages/dashboardPages/dashboard-favorites.js` — 3
locators (`searchScopeAllFolders`, `searchScopeCurrentFolder`,
`searchClearBtn`) + 5 methods (`searchAcrossFolders`, `clearSearch`,
`verifySearchClearButtonVisible`, `verifySearchInputEmpty`)

## Test design

Each test seeds a second, deliberately un-favorited dashboard
(`DashB_<ts>`), named
so neither dashboard's name is a substring of the other — that second
row is the
only thing that can prove the search reached past the favorites list.
Every test
asserts its own baseline *before* the action, so a pass is attributable
to the
change under test rather than to a permissive list. All selectors read
from source;
no new `waitForTimeout` sleeps; cleanup covered by the existing
`afterEach`.

## Test results

`9 passed` locally against `localhost:5080`. New tests verified green on
repeated
isolated runs.

## Caveats

- Tests were **not** verified to fail against pre-fix code — the app is
served from
a prebuilt bundle (no dev server), so reverting the hunks would require
a full
frontend rebuild. The in-test baseline assertions mitigate this but
don't replace it.
- This spec file is **self-hosted-only**: `login()` in `dashLogin.js`
waits for the
`login-user-id-field` form, while alpha cloud uses Dex SSO. Confirmed
pre-existing
(an untouched test in this file fails at the identical line), but it
means these
  tests won't run in a cloud-targeted CI job.
2026-08-11 11:01:33 +00:00
Shrinath Rao 56bedcb126 test(logs): fix flaky pattern-card click under OVirtualScroll windowing (#13752)
## Summary

Fixes the flaky failure seen in ENT PR #2386's `e2e / Logs-Features`
shard:

```
Logs/searchPatterns.spec.js:293 › should open pattern details dialog when clicking pattern card
TimeoutError: locator.click: Timeout 45000ms exceeded.
  - waiting for locator('[data-test="pattern-card-0"]')
```

Failed all 4 attempts, took the rest of the shard down (`1 failed, 10
did not run, 57 passed`). **Unrelated to #2386's LLM-annotation
changes** — it's pre-existing test-infra.

## Root cause

`PatternList` renders cards inside `OVirtualScroll`, which stamps the
**absolute** item index onto each card (`pattern-card-<absoluteIndex>`)
and mounts **only the visible window**. The card getters were already
migrated to address cards by *rendered position* (`patternCardAt()` /
`patternCardPart()` — suffix + `.nth()`), but three code paths were left
on the absolute-index selector:

- `clickPatternDetailsIcon()` → clicked
`[data-test="pattern-card-${index}"]`
- `clickPatternIncludeBtn()` / `clickPatternExcludeBtn()`
- the `waitForPatternsToLoad()` `'patterns'` visibility check

When the mounted window starts above index 0, `getPatternCardCount()`
(suffix `.count()`) still reports cards present, but the absolute
`pattern-card-0` root is absent → the click waits the full 45s. Locally
the whole list fits (no windowing), so `pattern-card-0` is always
mounted and the bug hides — which is why it only surfaces in CI's
headless viewport.

## Fix

Migrate the card-root click helpers and the load-check to
`patternCardAt(index)` (rendered position), consistent with
`getPatternCardCount()` and the getters. Remove the now-unused
absolute-index `patternCard` / `patternCardDetailsIcon` locators (the
latter also misleadingly named — it resolved to the card root, not an
icon).

## Validation

Ran the full spec locally headless against a local **enterprise** build
(v0.92.0-rc3):

```
playwright-tests/Logs/searchPatterns.spec.js — 15 passed (1.3m)
```

including the previously-failing `should open pattern details dialog
when clicking pattern card`, with no regression to the other 14. Also
verified via DOM probe that rendered cards are addressed correctly by
rendered position while the absolute `pattern-card-0` root is the
fragile anchor under windowing.

## Scope

Test-only (`tests/ui-testing/pages/logsPages/logsPage.js`). No product
code touched.
2026-08-11 09:49:03 +00:00
ktx-akshay 053b1b8c09 test(metrics): E2E for explorer share URL and Visualize deep-link (#13747)
Adds E2E coverage for PR #13274 (metric page share URL), which shipped
with
unit tests but no Playwright coverage — the existing
metrics-share-deep-link
suite targets the legacy `/metrics/editor` route, not the Explorer.

15 tests covering:
- F1 — share button presence, `metrics_data` blob written on chart
build,
  stripped on leaving Visualize, absent on a blank canvas, volatile keys
excluded, card drill-in, short-URL clipboard copy, full share round-trip
- F2 — `?mode=visualize` stays on the Explorer; legacy no-mode
`metrics_data`
still redirects to the editor; corrupt and version-mismatched blobs
absorbed

New `MetricsExplorerPage` page object; registered in PageManager and the
`Metrics` CI shard.

Verified 15/15 on two consecutive runs against v0.92.0-rc3 (~36s).
2026-08-11 08:08:48 +00:00
Shrinath Rao 3886dff356 refactor(sentinel): dedup page-object methods + testLogger convention (follow-up to #13735) (#13746)
## Summary

Post-POM-sweep cleanup (follow-up to #13727 / #13735): removes
duplication and dead code introduced/surfaced during the sweep, and
closes the `testLogger` convention gap. No production code — page
objects + specs only. Net **−101 lines**.

## A) Duplicate method names — removed 22 shadowed dead methods

7 page objects each had **two class methods with the same name**. In JS
the later definition wins, so the earlier copy was **unreachable dead
code** (callers always ran the later one; tests pass with it). Removed
the earlier/shadowed copy of each — behavior-preserving.

`alertsPage` (5), `logsPage` (11), `pipelinesPage` (2), `metricsPage`
(1), `streamsPage` (1), `cipherKeysFormValidationPage` (1),
`correlationSettingsPage` (1).

## B) Duplicate implementations — consolidated

- **crossLinkPage**: removed `crossLinkItemEditBtnLocatorByIdx` /
`crossLinkItemDeleteBtnLocatorByIdx` — **0 callers anywhere** (incl.
commented code); dead duplicates of the live
`getCrossLinkEditBtnLocator` / `getCrossLinkDeleteBtnLocator`.
- **alertsPage**: unified 3 identical-body pairs **via delegation** to a
single impl — `getTemplateRowByText→getAlertRowByText`,
`getDestinationsSelect→getAlertDestinationsSelectLocator`,
`getAddAlertSubmitButton→getAlertSubmitButtonLocator`. Both names kept
(zero spec edits), duplicated logic removed.

## C) Broad dead-code sweep — intentionally NOT in this PR

The wider "defined-but-never-called" backlog (~890 candidates, mostly
pre-existing) is a **risky, separate pass** and was deliberately
excluded.

## D) testLogger — added to 22 specs missing it

Added a `testLogger` import + `testLogger.testStart(testInfo.title,
testInfo.file)` in `beforeEach` (or `testLogger.info` in the first test
where there's no async `beforeEach`) to the 22 specs that didn't use it:
8 Correlation (5 api + 3 ui), 13 Dashboards, and `Login/auth`.
Convention-only.

## Verification

- **0 duplicate-name methods** remain across all page objects (was 22).
- **Method-resolution scan** (every `pm.<obj>.<method>()` in all 264
specs must resolve): 4 unresolved, **all 4 pre-existing on `main`**, **0
introduced by this PR**. (This is the runtime-existence check that `node
--check` can't do — the gap that caused the crossLinkPage regression on
#13735.)
- All changed `.js` pass `node --check`; all 22 specs have real
(non-comment) `testLogger` usage.
- CI-gated for browser execution.

## Stats
30 files changed (+82 / −183).
2026-08-11 07:42:28 +00:00
Shrinath Rao cb78ef36fd test(sentinel): complete POM compliance — Dashboards + Correlation (follow-up to #13727) (#13735)
## Summary

Completes the Sentinel **Page Object Model** compliance sweep of the
Playwright suite (follow-up to #13727, which covered the non-Dashboards
shards). Every raw `page.locator(...)` / `page.getBy*(...)` selector —
in **actions and assertions** — is relocated out of the spec files into
the relevant page object; specs now call page-object methods/getters
only.

**Result: 0 raw fixture-`page` selectors across the entire
`playwright-tests` suite** (single-line *and* multi-line, verified with
a dotall/multiline scanner). Together with #13727, the whole suite is
POM-clean.

## What's in this PR

**Dashboards shards — ~1,460 selectors** relocated into their page
objects (`dashboard-panel-configs/actions/edit/time`, `dashboard-chart`,
`dashboard-variables-scoped`, `dashboard-filter`, `dashboard-drilldown`,
`crossLinkPage`, `visualise`, `dashboard-share-export`,
`dashboard-create`, `dashboard-settings`, `dashboard-variables`):

| Shard | selectors |
|-------|----------:|
| Dashboard-Config-Settings (11 specs) | ~255 |
| Dashboards-Variables (12 specs, 4 batches) | ~693 |
| Dashboards-Tables (7 specs) | ~127 |
| Dashboards-Charts (9 specs) | ~81 |
| Dashboards-Core + Visualize (10 specs) | ~144 |
| Dashboards-Settings + Panel-DateTime (8 specs) | ~107 |
| Dashboard-Regression (1 spec) | ~52 |

**Correlation UI specs** (3 new `Correlation/*.ui.spec.js` that arrived
via main's merge, after the original audit) — converted into a new
`correlationDrawerPage` + an extended `correlationSettingsPage`, with
`PageManager` wired into specs that previously used none.

**Audit stragglers** the original **single-line** audit grep
undercounted — multi-line `page\n.locator(...)` chains in
`logstable.spec.js` (→ `logsPage.getTableHeaderByField()`) and
`dashboard-streaming.spec.js` (2 add-panel selectors).

**AI-review fixes** (DeepSeek, on this PR) — removed 2 dead-code
duplicate getters + 1 redundant method alias (merge artifacts from the
sequential batch commits).

One Node-context `console.log` (`dashboard-general-setting`'s
`beforeEach`) → `testLogger.info`.

## Approach

Nearly every Dashboards spec shares the same page objects, so the work
was done in **sequential batches** (one committed, verified unit at a
time) to keep page-object edits conflict-free. 13 commits, mapping 1:1
to those batches.

## Not changed (by design)

- **`waitForTimeout`** left as-is — logged in the audit, not rewritten
(per direction).
- **Browser-context code inside `page.evaluate()` /
`waitForFunction()`** (`document.querySelector`, `window.*`) left as-is
— it runs in the page's JS engine, not Node, so `testLogger` (a Node
module) can't be called there. *No `console.*` remained in dashboards
specs*; the Node-context ones were converted to `testLogger`.
- **`newPage.locator(...)`** on separate popup `Page` instances left
as-is (not the fixture `page`).

## Verification

- **0 raw fixture-`page` selectors** across all specs — single- and
multi-line, comments stripped.
- All changed `.js` files pass `node --check`.
- **CI-gated** for browser execution (no local runs), consistent with
#13727. The enterprise E2E crosscheck already passed against this PR.

## Stats

82 files changed (+3,355 / −2,211) — 61 specs, 21 page objects.
Selectors **moved into page objects, not deleted**.
2026-08-11 06:09:06 +00:00
Shrinath Rao 82d14e8458 test(sentinel): POM compliance across Playwright suite (non-Dashboards) (#13727)
## What & why

Sentinel (Council of Agents) audit-driven **Page Object Model
compliance** across the Playwright suite. Many specs (a lot added
recently, some AI-generated) had raw `page.locator(...)` /
`page.getBy*(...)` selectors directly in spec files — including inside
`expect(...)` — which the Sentinel flags as CRITICAL. This PR relocates
those selectors into their page objects and calls page-object
methods/getters instead.

A full audit of **all 256 specs** was generated first (stored locally
under `tests/ui-testing/MD_Files/sentinel-audit-2026-08-10/`, which is
gitignored). Original totals: **1,609** raw selectors, **190** inside
assertions, plus console/testLogger items.

## Scope of THIS PR (non-Dashboards)

Every non-Dashboards spec is now **100% raw-selector clean** (verified:
0 occurrences). Shards covered:

- **GeneralTests, Logs (Core/Builder-Advanced/Features/Regression),
Traces (+Reg), Streams (+Reg), Metrics (+Reg), Pipelines, Reports
(+Reg), Anomaly, Login/auth, auth-warm, and the unmatched Alerts
specs.**
- `console.*` → `testLogger` where it was Node-context (kept `console.*`
**inside `page.evaluate()`** — that's browser context; `testLogger`
doesn't exist there).
- `testLogger` import/use added where missing.
- **`waitForTimeout` intentionally left as-is** (logged in the audit,
not rewritten — per direction).

## Deferred to a follow-up PR

The **heavy Dashboards shards** (Variables 693, Config-Settings 255,
Tables 127, Charts 81, Core 79, Visualize 65, Settings 52,
Panel-DateTime 55, Dashboard-Regression 52 raw selectors) are **not** in
this PR — they're the bulk of the work and will be a separate, focused
pass. (dashboard-streaming console cleanup is included here.)

## Verification

Fixes were applied and executed **shard by shard** against a local env,
gated green before commit where the env allowed:

| Group | Local result |
|-------|--------------|
| GeneralTests (edited specs) | 52 passed / 11 skipped / 0 failed |
| Logs (shareLink, logsQuickPick, indexquery) | 21 passed |
| Traces / Streams / Metrics | 49 passed / 2 skipped / 0 failed |
| Pipelines | 56 passed / 17 skipped / 0 failed |
| logs-bugs :548, dashboard-streaming | verified not regressed
(isolation) |

**Alerts create-wizard specs** (Alerts shard, Alerts-Regression,
unmatched Alerts) could not be gated locally — the alert-creation
wizard's stream popover times out on this env **on unmodified code too**
(reproduced on original, test `:363`), so those refactors are 1:1
selector→method mappings, syntax-clean (`node --check`), and rely on CI
to gate.

Every changed `.js` passes `node --check`. `waitForTimeout` unchanged;
no behavior changes intended.

## Stats
51 files changed. Selectors moved into page objects, not deleted.
2026-08-10 15:30:21 +00:00
Ashish Kolhe b3e6a77270 fix(correlation): service-streams P2 behavioral fixes + discovered services flat table (#13710)
Correlation P2 round: behavioral/security fixes across the
service-streams backend and correlation FE, plus the Discovered Services
UX rework and the correlation e2e suites.

**Companion enterprise PR (must merge together):**
openobserve/o2-enterprise#2381 — the enterprise side calls the new 3-arg
`put(org, record, max_streams_per_type)` introduced here.

## Commit 1 — behavioral fixes (F2–F38)
**Backend**
- F25/F26: RBAC on `list_services` (`service_streams` GET — the one
previously-unenforced endpoint in the module) + validate
`distinguish_by` ids on identity-config save
- F2: validate trace filter param before WHERE-clause interpolation
- F6: `_reset` invalidates caches on all nodes via reload event; org
deletion clears service-streams caches
- F8: shared coverage-based identity-set ranking for ingest and read
- F10: purge rows of removed/changed identity sets on config save
- F19: `put` returns deleted orphan keys for cache eviction
- F28: discriminate no-match from failure; stop aliasing 404 to no-match
- F32: cap DB-side stream union at `max_streams_per_type`
- `all_dimensions` unions on put so late-tracked dimensions surface
- F14: `StreamInfo.dropped_dimensions` contract field; allow empty
`tracked_alias_ids` when identity sets exist

**Frontend**
- F35/F36/F31: per-stream filter-edit resolution via semantic groups;
dimension edits in both key spaces; subject overrides never guess fields
- F27/F30: echo `source_stream`/`source_type`; stop guessing another
stream's filters; chip dedup
- F2/F38: SQL field/value escaping via shared `quoteSqlLiteral` helper

## Commit 2 — Discovered Services UX + e2e
- Flat table (one row per instance), same-name services merge their name
cell (OTable `pivotRowColumns`); optional per-service collapse with
aggregated summary row; search/filters override collapse
- Fixes an OTable pivot-merge bug (value-keyed merge map blanked
single-column runs) + regression test
- Semantic Field Groups: category tabs with counts + cross-category
search with match highlighting
- Correlation Phase-1 API + Phase-2 UI Playwright suites

## ⚠️ Release note (enterprise + RBAC deployments)
Viewing Discovered Services now requires the **Service Streams**
permission (previously unenforced). Root/admin users are unaffected;
custom roles may need a one-time grant via the role editor (resource
already exists in the OFGA model).

## Checks
- `cargo fmt --all --check` ✅ · `cargo clippy --all-targets` ✅
enterprise + OSS feature sets (0 warnings)
- prettier ✅ · eslint 0 errors · 207 unit tests on touched specs ✅
- Compiles with paired enterprise branch ✅
2026-08-10 12:43:47 +00:00
Shrinath Rao 7ab93c04d5 test(reports): heal reports-regression timezone spec (edit-prefill race + toast selector) (#13724)
## What

Heals the Reports regression spec `reports-regression-bugs.spec.js` →
*"Report save should not shift timestamp by timezone offset"*
(@bug-11231), which was failing deterministically (all 4 retries) in
both the OSS and ENT **Playwright Regression** runs:

- OSS:
https://github.com/openobserve/openobserve/actions/runs/31364820545
- ENT:
https://github.com/openobserve/o2-enterprise/actions/runs/31364817885

This is **test-only** — the product is unaffected (the historical #11231
fix and normal report save both work; a save actually succeeds in the
failing run).

## Root cause (two independent defects)

1. **Wrong success-toast selector (deterministic line-118 failure).**
The save assertion used `page.getByRole('alert')`. `OToast` renders the
**success** variant with `role="status"` — only **error/warning** toasts
get `role="alert"`
([`OToast.vue`](https://github.com/openobserve/openobserve/blob/main/web/src/lib/feedback/Toast/OToast.vue)
`rootRole`). So the assertion could never match a successful save and
timed out. → now asserts on the success toast
`[data-test-variant="success"]`, consistent with the rest of the file.

2. **Edit-prefill race (line-84 failure, the one captured in the
trace).** On the edit flow, step-1 `Continue` could fire before the
async edit prefill (`form.reset()` after the report GET) populated
folder/dashboard/tabs. `goToStep()` then failed validation and silently
stayed on step 1, so the schedule toggle never rendered and the test
timed out looking for it. → `createReportContinueButtonStep1()` now
retries the click until the schedule step is actually reached,
**keeping** the original post-advance settle so downstream step-2
interactions (schedule-later toggle, step-2 Continue) don't race.

Also dedups the spec's inline date/time entry through the existing
`createReportDateTime()` helper (logic unchanged).

## Verification (local, `localhost:5080`)

| Suite | Result |
|---|---|
| `reports-regression-bugs.spec.js` (target) | ✅ stable across repeated
runs (1 + 3× repeat-each) |
| `reportsScheduleLater.spec.js` | ✅ 6/6 |
| `reportsScheduleNow.spec.js` + `reportCsvMediaType.spec.js` | ✅ 13/13
|

The two changed page-object methods are shared with the sibling Reports
specs above; all pass, confirming no regression.

## Scope

The spec lives in OSS `tests/` and is tree-merged into the ENT
regression run, so this single OSS change fixes both pipelines. No ENT
repo change required; the ENT regression will be re-triggered against
this branch.
2026-08-10 11:27:25 +00:00
Prabhat Sharma 087e1498ab feat(slos): add SLO burn-rate alerts (#13704)
design at /slo

## What

Adds **SLO burn-rate alerts** — an alert family that fires off an SLO's
error budget rather than a raw query — and finishes the SLO surface
around it: navigation entry, detail-page fixes, and the flag removal now
that SLOs have shipped.

## Changes

**SLO burn-rate alerts (backend)**
- New burn-rate evaluation (`config/src/meta/slo/burn.rs`,
`condition.rs`) with multi-window burn conditions, and
`core/src/slo/evaluate.rs` extended to evaluate them on the SLO job.
- Alerts carry an `slo_id`; `infra/src/table/alerts` gains the column,
filtering, and query support so an SLO's alerts can be listed and
deleted with it.
- Alert levels (`meta/alerts/level.rs`) and tags extended for the SLO
family; scheduler handlers route SLO alerts through the same
notification path.

**SLO alert UI**
- `SloAlertForm` + `AlertDetailSlo`: create, edit and view burn-rate
alerts from the SLO detail page and the alert list.
- Payload/routing helpers (`utils/alerts/sloAlertPayload.ts`,
`sloAlertRouting.ts`, `alertPayload.ts`) with unit tests.
- `AlertDestinationsField` extracted as the complete Destination form
field — side label, tooltip, combined destinations/workflows picker,
inline error, refresh, and create-in-new-tab. It replaces two verbatim
copies in the generic alert form and is reused by the SLO form and
synthetics `CheckAlerts` (which sets `supportsWorkflows: false`, since a
check has no workflow routing). Fixes the SLO form's previously unwired
refresh and Add Destination buttons.

**SLO detail page fixes**
- Burndown and burn-rate charts drew as bare axes when coverage was
sparse: `showSymbol: false` means an isolated measured bucket between
gaps has no segment and paints nothing. Symbols are now sized per point,
and a sparse-coverage note states how many buckets actually recorded
events.
- Nothing scrolled below ~700px of viewport height — `OPageLayout`'s
fixed flex body plus `OTabPanels` `scroll="none"` clipped the panels.
Panels now grow and scroll; the stat strip and tab bar stay pinned.
Verified at 1024x500, 1280x620, 1512x945.

**Other**
- `ZO_SLO_ENABLED` removed — SLOs have shipped, so the flag and its
status/route gating are gone.
- SLOs surfaced in the main nav.
- Dashboard chart panel header gets its own tint via new
`--color-panel-bar-bg` / `--color-panel-bar-border` tokens, and the bar
is extracted into `PanelBar.vue` (it existed as five hand-copied class
strings that had already drifted).
- `tsconfig.vitest.tsbuildinfo` untracked; third-party vendor name
removed from comments and test names.

## Testing

- New Rust tests across `meta/slo/burn.rs`, `condition.rs`,
`core/src/alerts/alert.rs` and `infra/src/table/alerts`.
- New frontend suites: `sloAlertPayload.spec.ts`,
`sloAlertRouting.spec.ts`, `alertPayload.spec.ts`,
`AlertDetailSlo.spec.ts`, `SloDetailAlerts.spec.ts`,
`SloListDelete.spec.ts`, `SloTimeSlicePreview.spec.ts`.
- `cargo clippy --all-targets` and `web` `lint:ci` clean.
2026-08-10 10:07:38 +00:00
Hengfei Yang 8303be0b4c refactor: rename AlertManager node role to Scheduler (#13696)
## Summary

- rename the `AlertManager` node role to `Scheduler` across Rust
services, jobs, protobuf bindings, UI labels, and tests
- emit `scheduler` as the canonical role name
- continue accepting legacy `alertmanager` and `alert_manager`
configuration values
- keep protobuf enum value `5` unchanged for wire compatibility

## Why

The role schedules and executes alerts, reports, pipelines, synthetics,
and other background work. `Scheduler` describes that broader
responsibility more accurately.

## Compatibility

- existing role configuration values remain accepted through parser and
serde aliases
- the protobuf numeric value remains stable
- coordinated changes are included in the Enterprise, Helm chart, and
documentation repositories

## Verification

- `cargo test -p config test_role`
- `cargo test -p openobserve-node`
- `cargo check -p openobserve-jobs`
- targeted web tests: 307 passed, 13 skipped
- Rust formatting and targeted Prettier checks
2026-08-07 13:30:38 +00:00
Ashish KolheandShrinath Rao 735dc7d58d feat(traces): restore in-page Service Graph / Service Catalog tabs alongside standalone routes (#13698)
## What

#13659 converted Service Graph and Services Catalog from in-page tabs on
the Traces page into standalone routes reached from the nav-rail flyout.
This PR restores the in-page tabs as a **second access path** — both now
coexist, with distinct semantics:

| Path | Semantics |
|---|---|
| Toolbar tabs on `/traces` | In-page view switch (`?tab=service-graph`
/ `?tab=services-catalog`), shares the page's stream + time context —
the pre-#13659 UX, including old `?tab=` bookmarks |
| Nav-rail flyout | Navigates to the standalone routes
`/traces/service-graph`, `/traces/services` — unchanged from #13659 |

Both paths render the same inner components (`ServiceGraph.vue`,
`ServicesCatalog.vue`); only toolbar chrome differs.

## How

A guided **reverse-apply of #13659's diff to `Index.vue` +
`SearchBar.vue` only** (verified byte-identical to the pre-PR files —
both were untouched on main since), with four deliberate adaptations:

- **Re-added `applyHandoffFilter`** + its two hook call sites, which the
revert would have deleted — the standalone pages' "View Traces" handoff
(`?filter=` landing) depends on it. Covered by a new unit regression
test and verified live.
- The restored in-page `view-traces` handlers call
`viewTracesHandoff.ts` (`buildViewTracesFilter` /
`normalizeViewTracesPayload`) instead of resurrecting the inline filter
builders that #13659 extracted from them — no re-duplication.
- **Fixed a latent pre-#13659 bug**: the graph tab's refresh button
called `serviceGraphRef?.loadServiceGraph()`, which was never in
`ServiceGraph.vue`'s `expose()` — now `refresh()`.
- **Flyout highlight now follows the visible view** on both paths: new
`SubnavChild.activeOnTab` alias marks the Service Graph / Service
Catalog flyout items active when `/traces?tab=…` shows their view
in-page. Previously the same view highlighted different flyout items
depending on how you reached it — the only submenu that deviated.

Also:
- Renamed the "Services" submenu / page header / route title to
**"Service Catalog"**, matching the toolbar tab label (single
`menu.services` locale value covers all).
- `global-setup.js` login verification moved to `navbar-main-nav` — the
same stale-selector fix #13659 applied to `enhanced-baseFixtures.js`;
suites fail setup on main without it.

## Tests

| Suite | Result |
|---|---|
| Unit: `SearchBar` 69, `Index` 76, `ONavGroup` 18 (5 new `activeOnTab`
cases), `viewTracesHandoff` 21, `navGroups` 28 | all pass (233 total, 2
pre-existing skips) |
| E2E (new) `Traces/traces-toolbar-tabs.spec.js` — in-page tab switches,
shared-datetime carry, `?tab=` bookmark reload, flyout→standalone-route
coexistence | 4/4 vs live enterprise build |
| E2E `Traces/service-catalog.spec.js` (standalone-route regression) |
18 pass |
| `type-check`, `eslint` (0 errors), `prettier`, `lint:design:strict` |
clean |

Not run: `Traces/service-graph.spec.js` — its `beforeAll` ingests via
`ZO_BASE_URL`, which requires a single-origin instance (CI); the dev
vite+backend split can't satisfy it. Nothing in this PR touches
`ServiceGraph.vue` or the standalone views.

## Notes for review

- `Index.vue` / `SearchBar.vue` restored regions are intentionally
verbatim pre-#13659 (including its 16 pre-existing lint *warnings*) —
deviations are limited to the four adaptations above, so the
diff-vs-pre-PR is auditable.
- Two known-inert quirks restored deliberately, not bugs to fix here:
`hide-splitter-separator` has no CSS rule (was a no-op pre-#13659 too),
and `@update:activeTab` writes to a getter-only computed.

---------

Co-authored-by: Shrinath Rao <shnath@openobserve.ai>
2026-08-07 12:48:14 +00:00
Bhargav 47d510aa16 feat: rebuild the RUM error detail page around issue impact (#13697)
Design at: designs/mobile-rum-sdk-evaluation/design-docs
The page showed a single error event: header, tag strip, stack trace,
replay button, event list. It could not say how bad the error was, who
it reached, whether it was new, or where it concentrated — it never
queried anything beyond that one row.

It is now a two-column triage surface: the diagnosis path (occurrences
chart, stack trace, breadcrumbs) on the left, corroborating evidence
(replay, backend trace, breakdown, who hit it) on the right.

- ErrorHeader: error type as the title with handling/source badges, the
message in an alert banner, and a context line carrying event id, route
and the service/version/env chips. Copy-permalink and copy-event-id
actions.
- ErrorImpactStrip: occurrences, users, sessions, first/last seen. A
dash, not a zero, when a figure is unavailable.
- ErrorOccurrencesChart: this signature over time, marking the bucket
that holds the event the page was opened on.
- ErrorFacetBreakdown: browser / OS / release / page as
share-of-occurrences bars, calling out any value that owns 80%+ of the
traffic — usually the fix.
- ErrorContextCard: identity, device, geo and deployment for this
occurrence. Supersedes ErrorTags, which is removed.

Backed by useErrorDetail + errorDetailQueries: an impact aggregate gates
a histogram and two facet group-bys. Three searches, staged inside the
server's work-group cap and aborted when the view is left. Facets are
split across two group-bys on purpose — one query over all four columns
returns their cross product.

Also fixes the breadcrumb trail: it read an hour of the whole stream
with no session filter, so "what the user did before the crash" could be
other people's clicks. It is now scoped to the session id.
2026-08-07 12:31:19 +00:00
Bhargavandktx-akshay 93cfbee117 fix: Issue ent 2343 alert improvements v3 (#13670)
Co-authored-by: ktx-akshay <akshay.patil@kiara.tech>
2026-08-07 08:00:16 +00:00
dependabot[bot] 06afa242fb chore(deps): bump uuid, @openobserve/browser-logs and @openobserve/browser-rum in /tests/ui-testing/fixtures/rum/npm-app (#13688)
Removes [uuid](https://github.com/uuidjs/uuid). It's no longer used
after updating ancestor dependencies
[uuid](https://github.com/uuidjs/uuid),
[@openobserve/browser-logs](https://github.com/openobserve/browser-sdk/tree/HEAD/packages/browser-logs)
and
[@openobserve/browser-rum](https://github.com/openobserve/browser-sdk/tree/HEAD/packages/browser-rum).
These dependencies need to be updated together.

Removes `uuid`

Updates `@openobserve/browser-logs` from 0.3.4 to 0.4.1
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/openobserve/browser-sdk/commits/HEAD/packages/browser-logs">compare
view</a></li>
</ul>
</details>
<br />

Updates `@openobserve/browser-rum` from 0.3.4 to 0.4.1
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/openobserve/browser-sdk/commits/HEAD/packages/browser-rum">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/openobserve/openobserve/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 06:45:04 +00:00
Abhay Padamaniandktx-akshay 739054c7bd feat: metric chart improvement (#13546)
Design At: designs/dashboards/metric-chart-override-support/


1. For metric chart conditional text color, background color, value
override support.

2. sparkline for metric chart.

---------

Co-authored-by: ktx-akshay <akshay.patil@kiara.tech>
2026-08-07 05:09:40 +00:00
Shrinath Rao 18a53716f6 test(traces): heal service-graph Metrics-tab correlation detection (#13674)
## What

Fixes the one remaining alpha1 Traces failure after the service-graph
route deploy: **`service-graph.spec.js` → P1: Show telemetry correlation
from node panel via Metrics tab**. Test-only, no product change.

## Root cause

The test asserted on two hooks that never reach the DOM:

- `service-graph-side-panel-metrics` sits on an `OTabPanel` whose
`data-test` is **swallowed by `<Transition>`**.
- `service-graph-side-panel-metrics-dashboard` is passed to
`<TelemetryCorrelationDashboard>`, which has **multiple top-level
roots** (dialog `ODrawer` / embedded `div` / `ODialog`) → it renders a
**Vue fragment**, and Vue 3 **drops fallthrough attrs on fragment
roots**, so the `data-test` is never applied.

On alpha (which seeds only traces, no metrics), the correlation returns
a **zero-stream dashboard object**: the dashboard branch renders (so the
parent empty-state is suppressed), but there are no stream rows and the
dashboard hook doesn't exist → the test's `else` branch `expect(hasError
|| hasEmpty)` fails. The test only ever passed before when correlation
happened to return *empty*; it broke as soon as real correlation data
appeared.

Confirmed live on the deployed build: after clicking Metrics, the panel
wrapper `data-test` is absent, while the correlation view's real content
(`correlation-event-header-*`, metric selector) renders fine.

## Fix

Wait on the correlation view's **real rendered content** — the
correlation event header, or a terminal empty/error state — instead of
the swallowed/dropped wrappers, and assert only that the Metrics tab
**resolves** (the helper throws if nothing renders, catching a genuinely
broken tab). Metric-stream rows are asserted only on the happy path when
data is present.

Deploy-independent: every element it waits on ships on the current build
(verified), so it won't reintroduce the main-vs-deployed skew.

## Validation

`--workers=1 --retries=0` against alpha on the previously-failing test:
- 1 run → passed
- `--repeat-each=2` → 2/2 passed

`Metrics tab resolved: streamsPresent=false → resolved with no
correlated streams (acceptable) → Test PASSED`

## Context

The other 9 service-graph failures were the missing
`/traces/service-graph` route (#13659) and cleared on deploying latest
`main`. Sibling fixes from the same alpha1 triage: openobserve#13672
(alerts move flake), o2-enterprise#2370 (per-org cleanup).
2026-08-07 01:26:07 +00:00
Shrinath Rao e1a265d7f0 test(alerts): heal Move-across-folders selection-wipe flake (#13672)
## What

Heals the recurring flake in `alerts-e2e-flow.spec.js:160` — **E2E:
Alert Folder Management - Move, Search, Verify** — which fails with
*"Move drawer failed to open after retries"* (the
`alert-list-move-across-folders-btn` never becomes visible). Also
hardens the sibling caller in `alerts-ui-operations.spec.js`.

**Test-only change. No `web/` product code is touched.**

## Root cause

`AlertList.vue`'s `getAlertsFn()` runs `selectedAlerts.value = []` at
the **start of every folder fetch**. The move / export / pause buttons
are `v-if="selectedAlerts.length > 0"`.

- Switching to a freshly-created (**uncached**) folder kicks off an
async `listByFolderId` fetch.
- `navigateToFolder` returns as soon as the folder tab is active and a
table is on screen — so `select-all` can fire **before** that fetch
resolves.
- When it resolves it **wipes the selection** → the move button
disappears → the drawer never opens.
- **Cached** folders serve synchronously with no wipe.

This precisely explains the observed signature:
- **Always failed attempt 1** (folder uncached), **passed attempt 2**
(retry warmed the cache).
- **Failed all 3 attempts under parallel load** — the slower API
guarantees the wipe lands after `select-all` every time.

`networkidle` doesn't catch it because streaming/websocket
(`ZO_STREAMING_ENABLED=true`) keeps connections open.

## The fix (`pages/alertsPages/alertBulkOperations.js`)

1. Before selecting, wait for the source folder's **own** row to render
(`expectAlertName`) — this proves the fetch resolved (wipe already
happened) and that we're on the right folder, not a stale table. Falls
back to "any row" when no name is passed (backward compatible).
2. Retry **select-all → click Move → assert drawer open** as **one
atomic `expect(...).toPass()`**. A fixed stability window isn't enough
(a second fetch can resolve later and wipe). The drawer is driven by its
own `showMoveAlertDialog` ref and captures the selection **at click
time**, so once open a later wipe cannot close it — the click just needs
to land while the selection is valid, which it always eventually does
once the folder is cached.

Both spec callers now pass `{ expectAlertName: alertName }`.

## Validation

Ran against the alpha cloud env on the exact failing scenario
(freshly-created uncached source folder, `--workers=1 --retries=0`):

- 1 initial run → **passed**
- `--repeat-each=3` → **3/3 passed**

`Move drawer opened with a valid selection` → `Moved all alerts to
target folder` on every run.

## Note

Not related to the concurrent Traces service-graph failures on alpha —
those are a separate main-vs-RC route skew (#13659 not in the deployed
RC build) and resolve on deploying latest `main`.
2026-08-06 13:33:22 +00:00
Ashish Kolhe 32d02d6781 chore(traces): make Service Graph and Services their own routes (#13659)
## What

Service Graph and Services Catalog were tabs on the Traces page,
switched via a `?tab=` query param on a single keep-alive route. They
are now real routes, reached from a hover flyout on the Traces rail
tile:

| Route | Notes |
|---|---|
| `/traces` | Traces search — keeps the **Spans / Traces** toggle |
| `/traces/service-graph` | enterprise-gated; redirects to Traces on OSS
|
| `/traces/services` | |

**Spans stays an in-page toggle** on purpose. It's a view *granularity*
that shares the query, stream and time range — you switch to it
mid-investigation and expect context to carry over. Datadog's Trace
Explorer does the same. Service Graph and Services are destinations;
Datadog, New Relic and Grafana all treat them that way.

## Why this wasn't just cosmetic

As tabs, all three views shared one `meta.title`, one history entry, and
the traces page's `searchObj`. That last part is why Service Graph and
Services Catalog inherited the traces page's selected stream — the
coupling this PR removes structurally.

## UI

Both pages now use the Agent Graph header pattern (`OPageLayout` +
title/subtitle/icon, date picker and last-run refresh in `#actions`),
with a subnav row carrying stream selector + search on the left and
view-type + layout on the right.

## Handoff

`view-traces` no longer mutates the shared store from a sibling tab.
Both views build the same filter string — extracted verbatim into
`viewTracesHandoff.ts` — and navigate to Traces with it as query params,
so the URL survives a reload. (Traces then canonicalises `filter=` to
its own base64 `query=` via existing code in `useTraces.ts:290`.)

## Two bugs this surfaced

Both were things the tab views inherited for free and now have to set
themselves — and both would have hit real users deep-linking these
pages:

- **`organizationIdentifier`** was only assigned inside the Traces
page's `loadPageData()`. Without it the catalog issued requests to
`/api//_search_stream` (empty org segment) and rendered nothing.
- **`?period=` / `?from=`+`?to=`** were only applied by the Traces
page's `restoreUrlQueryParams()`. Without it a shared link carrying a
time range silently opened on the 15-minute default.

## Tests

| Suite | Result |
|---|---|
| `Traces/service-graph.spec.js` | 14 / 14 |
| `Traces/service-catalog.spec.js` | 10 / 10 |
| `RegressionSet/Traces/traces-ent.spec.js` | 1 / 1 |
| Unit (`Navbar` + `plugins/traces`) | 2514 pass |

Also clean: `type-check`, `eslint` (0 errors), `lint:design:strict`.

Test-side changes: navigation POMs go through the rail flyout instead of
the removed toggles; the graph POM pins an explicit window, since on its
own route the graph no longer inherits the traces page's range. The
shared auth fixture now verifies against `navbar-main-nav` — it keyed
off `menu-link-/-item`, which the current rail no longer renders, so
suites failed setup even after a successful login.

Filter-building coverage moved with the code into
`viewTracesHandoff.spec.ts` (21 tests) rather than being deleted
alongside the component tests that asserted the old in-page handlers.

## Known / follow-up

- `RegressionSet/Traces/traces-regression.spec.js` has 2 failures (trace
column sorting, pagination). **Pre-existing** — they fail identically
against production on `main`, and the Service Catalog test in that file
passes.
- Both views still default their stream picker to the traces page's
selected stream. Defaulting Service Graph and Services to **all
streams** is a deliberate follow-up: on our own instance the `default`
stream shows 22 topology nodes vs 71 across all streams, and services
like `o2-ai` span 4 streams. Graph is a one-line change (the API already
supports it); the catalog needs a backend decision, so it's out of scope
here.
2026-08-06 10:30:03 +00:00