Commit Graph
6875 Commits
Author SHA1 Message Date
Huaijin fd2306ffaa fix(search): preserve indexed top-n aggregation results (#13779)
- close: https://github.com/openobserve/openobserve/issues/13363

## Summary

- Prevent `AggregateTopkExec` from truncating file-local indexed
aggregation rows before the final aggregate merges duplicate group keys.
- Keep the Tantivy `SimpleTopN` optimization enabled while removing only
the conflicting downstream top-k node.
- Add a regression test that constructs the conflicting physical plan,
verifies `AggregateTopkExec` is present before leader index
optimization, and verifies it is removed afterward while `SimpleTopN`
remains selected.

## Root cause

`SimpleTopN` emits partial count rows per `(file, group key)`.
`AggregateTopkExec` previously ranked and truncated those individual
rows before the final aggregate could combine identical keys across
files. With enough files, this silently undercounted results and removed
entire groups while `is_partial` remained false.

The rewrite removes the conflicting node bottom-up. This is important
because `AggregateTopkExec` is inside the follower plan already
serialized by `RemoteScanExec`; rebuilding the parent updates those
serialized plan bytes before attaching `SimpleTopN`.

## Verification

Using 121 Parquet files and 121 Tantivy index files containing 93,601
records across 12 groups:

| Result | Before | After |
|---|---:|---:|
| Groups returned | 9 | 12 |
| Sum of grouped counts | 83,201 | 93,601 |
| Baseline `COUNT(*)` | 93,601 | 93,601 |
| `is_partial` | false | false |

Tests run:

- `cargo fmt --all -- --check`
- `cargo test -p search
test_leader_rule_topn_removes_conflicting_aggregate_topk -- --nocapture`
- `cargo test -p search
datafusion::optimizer::physical_optimizer::index_optimizer::tests --
--nocapture`
- `cargo test -p search aggregate_topk -- --nocapture`
- `cargo check -p search`
2026-08-12 07:23:50 +00:00
Amey PawarandHengfei Yang 3d06ed0423 fix(logs): reject LIMIT in filter mode instead of building an invalid query (#13773)
Closes #6915.

Without SQL mode the search bar's contents become the WHERE body of a
generated `select [FIELD_LIST][QUERY_FUNCTIONS] from "[INDEX_NAME]"
[WHERE_CLAUSE]`, and `handleNonSqlMode` splices the input in verbatim
after stripping comments and quoting known field names. Nothing checks
that the input is actually a predicate, so `k8s_namespace_name =
'nginx-ingress' LIMIT 10` produces valid SQL. The results grid runs it
and returns rows, while the histogram query built from the same input is
rejected by `is_eligible_for_histogram` and surfaces
`SearchHistogramNotAvailable`. That is the split described in the issue
— the log list works, the histogram errors.

The server side of this already exists, so the guard belongs in the UI,
before either request goes out.

## What changed

| | |
|---|---|
| `utils/query/nonSqlLimit.ts` | new — `hasLimitClause()` |
| `useSearchQuery.ts` | the guard in `handleNonSqlMode`, using the
file's existing `notificationMsg` + `return null` path |

Placement note: `hasLimitClause` lives in `utils/query/` rather than in
the composable because it holds no state and touches no reactivity — the
same split `doubleQuoteWarnings.ts` and `colorizeQuery.ts` already use.

The check is deliberately narrow. `LIMIT` matches only when followed by
a number, so `limit = 5` and `rate_limit >= 100` pass through. String
literals are blanked first, so `msg = 'rate limit 500 exceeded'` is
unaffected. Line and block comments are stripped after that, so `code =
200 -- limit 10` — which works today — keeps working.

## Tests

- **17** on the derivation — the issue's own query, case variants,
whitespace and newlines before the number, a bare `LIMIT 100`, a field
named `limit`, `rate_limit >= 100`, the word inside a literal, a literal
with a doubled quote, `limit = 'none'`, leading / trailing / block
comments, a real LIMIT alongside a comment, and a dash sequence inside a
literal.
- **6** on `buildSearch` — rejects the reported query and sets the
message, still builds without a LIMIT, allows `limit = 5`, ignores
commented-out LIMITs, and does not fire in SQL mode.

Each rejection test was checked to fail when the guard is reverted. The
negative cases stay green either way, which is what separates them from
tests that merely restate the implementation.

## Verification

- `npx vitest run --root src/ composables/useLogs utils/query` — **1965
passed**, 33 files, 0 failed
- `npx vue-tsc --noEmit -p tsconfig.vitest.json` — clean, and identical
to `main`
- `npx eslint` on all four files — clean
- `npx prettier --check` — clean

## Caveats

- Not exercised in a running app; covered by unit tests only.
- `ORDER BY`, `GROUP BY` and `HAVING` reach the same splice and are
**not** handled here. Happy to extend if you want them, but the issue
asked about `LIMIT` and I didn't want to widen it uninvited.
- `plugins/logs/IndexList.vue` holds an independent copy of the same
`[WHERE_CLAUSE]` splice for field-value lookups. It is reached by
clicking a field rather than running a search, so it sits outside this
path — flagging it rather than changing it.

---------

Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
2026-08-12 06:44:44 +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
Hengfei Yang 9f76ff6388 refactor(search): enhance filtering logic for FTS fields in search function (#13767) 2026-08-11 14:15:33 +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
Bhargav bf27a488d6 fix(ci): stop retrying translation pushes that a merge queue has frozen (#13765)
`auto_merge_enabled` is one of the two events that start this workflow,
and it fires on the same click that enqueues the PR. GitHub freezes a
queued PR's branch, so by the time the run finishes translating, the
push it exists to make is rejected with GH006.

The push loop then made the failure much harder to read than it needed
to be. It was written for one failure mode -- losing a race to a
concurrent push -- so it treated every rejection as a lost race and
rebased. GH006 is a policy rejection, not a race: the rebase is a no-op
("Current branch ... is up to date") and the next two attempts fail
identically, burying the real reason under a log that looks like
flakiness.

- Classify the rejection at push time. GH006 / merge-queue /
protected-branch stops on the FIRST attempt with an error naming the
cause and the fix (dequeue, re-run, re-queue). Non-fast-forward keeps
the existing rebase retry.
- Check for queue membership in the guard, before any API spend. When
the PR is already queued the run cannot land a commit, so translating
would spend the DeepSeek budget building one with nowhere to go.
verify-translations.yml still gates the queue, so a genuinely
untranslated PR is still blocked from main.

Fail-open on the queue lookup: a transient API error falls through to
"not queued" so a blip can never silently stop translations from being
generated. The push-time check backstops it.

No keys are lost in either path -- .translation_state.json still lists
them as pending, so the next run picks them up.
2026-08-11 13:23:20 +00:00
Omkar Kesarkhane 4b9204f69f feat(synthetics): collapse journey advisories into an on-demand suggestions chip (#13759)
## Problem

The browser journey editor stacked two **always-on** advisory cards
between the toolbar and the first step:

- *"This journey does not verify anything"* — no `assert` step in the
journey
- *"No test attributes found in this recording"* — no step produced a
`test_attribute` locator candidate

Both derived their visibility from the journey's own content, and both
conditions are the normal case for a real recording — so they rendered
on every visit to every affected journey. Each was dismissible, but
dismissal lived in a local `ref(false)`, so it died on remount: authors
closed the same two cards on every visit and still began reading their
steps two cards down the page.

## Change

One warning badge in the Steps toolbar — a warning icon and a count, no
prose:

- **Hover** names it ("1 suggestion for this journey"), from the same
string as the trigger's `aria-label` so the two cannot drift.
- **Click** opens a popover with each suggestion in full, separated by
rules, plus a footer stating that suggestions never block saving or
running.
- **Never opens on its own**, on any render.
- **No dismiss.** The chip costs no vertical space, so suppressing it
buys nothing — and a suggestion that can be waved away is one an author
never has to actually decline. A suggestion leaves when the condition
behind it is resolved.

A zero-assertion journey remains accepted with a warning; nothing here
blocks a save.

Transient banners are deliberately untouched — replay
running/passed/failed/stopped, incognito, pre-flight, recorder error.
They appear because something just happened, and a replay result that
does not announce itself is a worse product.

## Implementation

| File | |
|---|---|
| `web/src/utils/synthetics/journeySuggestions.ts` | new — pure
derivation over the steps; one small producer per rule; carries i18n
**keys** rather than text, so it needs neither Vue nor `useI18n` and a
rule is testable without mounting anything |
| `web/src/components/synthetics/journey/JourneySuggestions.vue` | new —
chip, tooltip, popover; presentation only |
| `web/src/components/synthetics/journey/BrowserJourney.vue` | computes
the suggestions, renders the chip in the toolbar, appends the offered
assertion |
| `ZeroAssertionNotice.vue`, `TestIdMisconfiguredNotice.vue` (+ specs) |
deleted — each was a visibility computation plus a card, and both halves
now have better homes |

Adding a future advisory is one producer and no template change.

Placement note: this lives in `utils/synthetics/` rather than a
composable because it holds no state and touches no reactivity — the
same split `deriveLocatorKind.ts` and `locatorStability.ts` already use
for derivations consumed by these components.

## Tests

- **17** on the derivation — both rules, empty journey, navigate-only
journey, empty candidate list, a test attribute outranked *within* a
step, both conditions at once in stable order, and that the offered
assertion is empty (`element_visible` with no selector/locator) rather
than guessed.
- **15** on the component — no chip at zero, count only and no prose,
pluralization, closed on mount, opens on click, tooltip suppressed while
open, action emits and closes, separators between suggestions.
- **5** on the `BrowserJourney` wiring — old hooks gone, suggestions
handed down, nothing offered when `readonly`, the assertion appended to
the tail with prior step order and ids intact, and the suggestion
dropping once the parent commits.

Two things the tests caught that review would not have:

1. Resolving the last suggestion unmounts the popover but not the
component holding its `open` flag — so a panel left open came *back*
open the next time a suggestion appeared. Regression test written,
confirmed failing, then fixed.
2. One component block runs against the **real** `OPopover` and `OBadge`
instead of a stub. `OBadge` declares `inheritAttrs: false` and the
popover trigger is `as-child` — precisely the pairing that silently
drops a click handler and leaves a chip that looks correct and does
nothing. Every other test stubs the popover and would not see it.

## Accessibility

`⚠ 1` has an accessible name of "1", so the trigger's `aria-label` is
load-bearing and carries the full count string. Child-mode tooltips are
pointer-only, so a keyboard user's path is that label plus the popover
they open with Enter.

Accepted trade-off, stated plainly: the old cards carried
`role="status"` and were announced on appearance. Nothing is announced
now — that announcement is what this change exists to suppress.

## Verification

- `npx vitest run src/components/synthetics src/utils/synthetics
src/views/synthetics` — 1147 passed, 58 files
- `npm run type-check` — clean
- `npm run lint` — clean for every touched file (the repo's pre-existing
`eslint.config.js` / `vitest.config.ts` findings are unchanged and
present on `main`)
2026-08-11 12:18:16 +00:00
Ashish Kolhe de0d37e23d fix(alerts): stop losing Slack alerts on undeliverable link URLs (#13742) (#13753)
Fixes #13742
Fixes openobserve/o2-enterprise#2394

## The bug

A content template link with a non-http scheme (`javascript:alert(1)`)
made Slack reject the **entire** notification with `400
invalid_attachments`. The alert was lost outright and `triggers`
recorded `notify_failed`.

## Root cause

`safe_url` neutralises a hostile scheme by substituting `BLOCKED_URL`
(`"#"`). That is **safe** but not **deliverable** — Slack, Teams,
Discord and PagerDuty validate action URLs server-side and reject the
whole payload when one is not an absolute `http(s)` URL. A security
substitution was turning into total message loss.

The two properties are different, and links need both. So the fix splits
by medium:

| renderer | link handling |
|---|---|
| Slack buttons, Teams actions, Discord `embed.url`, PagerDuty `href` |
`dispatchable_url` → **drop** |
| HTML email, email plaintext, SNS plaintext, Discord description |
`safe_url` → substitute `#` |
| Webhook envelope | unfiltered — versioned machine contract, documented
|

Dropping beats rewriting: the alert body is worth more than the button,
and a button pointing somewhere misleading is worse than no button.

`dispatchable_url` requires a **non-empty authority**, not just an
allowlisted scheme — `http:javascript:alert(1)` and `https://` are valid
*opaque* RFC 3986 http(s) URLs that Slack still rejects. It also rejects
raw spaces and control characters, which variable substitution can
introduce.

## Related issues fixed in the same area

- **Empty `ZO_WEB_URL`** (not in the report): `alert_url` becomes a
root-relative `/web/logs?…`, which `safe_url` passes through by design,
and it lands in the always-appended "View in OpenObserve" button. Same
`invalid_attachments`, on deployments that never authored a hostile
template.
- **PagerDuty** validates `links[].href` server-side too — same
lost-alert class, converted.
- **Save-time rejection** (`ContentSpec::validate`): a clear 400 naming
the offending link, instead of a template whose link silently never
works. Also wired into **preview** and **test-send**, which previously
accepted a draft that couldn't be saved and echoed the raw hostile URL
back to the UI.
- **Slack image-fallback misdiagnosis**: removing the image proves the
image was at fault, but only an image pointing at *our* `web_url`
justifies tripping the process-wide chart suppression (1h, across every
alert) and telling the operator to fix `ZO_WEB_URL`. A custom template's
third-party image no longer does either.
- **a11y**: `OInput` links its error message to the control via
`aria-describedby`; the textarea branch also gained `aria-invalid`.

## o2-enterprise#2394 (fixed here, not in the enterprise repo)

Clearing the Monaco body and saving toasted "Please fill required
fields" with no indication of *where*. The schema already produced an
error on the `body` path — a bare Monaco bridged in via `setFieldValue`
just had no `OFormInput` error slot to render it. Now a red border +
message driven by the same field state `OFormInput` reads.

The aria wiring goes on the **shell**, not on `<QueryEditor>`:
`CodeQueryEditor` sets `inheritAttrs: false` and binds `$attrs` to a
non-focusable wrapper, so attributes passed to the component would be
inert — false assurance for a screen-reader user.

> The issue names the title field too. It's an `OFormInput` and already
highlights correctly; I added a regression test pinning that rather than
changing it. This fix is entirely in OSS (`web/`) since the template
form isn't enterprise-gated, so there's no companion enterprise PR.

## Layering note

Save-time validation is a **UX gate, not the security boundary**. Link
URLs are templates — a scheme that only materialises after `{variable}`
substitution can't be judged at save time. The render layer is what
actually stops delivery, and it also covers every template saved before
this validation existed. Both layers are needed; neither is sufficient.

## Testing

- Every fix written test-first (verified RED) and **mutation-tested** —
each new test fails when its fix is reverted in substance.
- Golden files unchanged: legitimate URLs render byte-identically, so no
over-blocking.
- Rust: `alerts::notifications` 159 · `config` 3130 · `api-management`
424 — all passing. `cargo clippy --all-targets` and `cargo fmt --check`
clean.
- Frontend: full suite **41,597 passing** (run because `OInput` is
shared library code). `vue-tsc`, `eslint`, and `npm run format:check`
clean.
- Two independent cold-start review passes; findings from both are
folded in, including a P0 where the first version of `dispatchable_url`
still let the original bug through.

Pre-existing and unrelated:
`alerts::destinations::tests::test_alert_destination_template_is_optional`
fails locally on a DB connection reset — verified failing on unmodified
`main` too.
2026-08-11 12:08:20 +00:00
Abhay Padamani 883fc97046 fix(dashboard): deduplicate panel and layout IDs on import and dashboard load (#13760)
https://github.com/openobserve/openobserve/issues/13104
2026-08-11 12:04:23 +00:00
HuaijinandHengfei Yang 4a837ee4b9 chore(config): remove unused environment variables (#13690)
## Summary

- remove 10 EnvConfig entries that have no runtime consumers
- stop advertising ineffective knobs across SLO, common, runtime, query,
metadata, NATS, S3, and pipeline configuration
- preserve active replacements such as ZO_FAKE_ES_VERSION and
ZO_PIPELINE_BATCH_SIZE_BYTES

These variables were parsed but never read, so this does not change
runtime behavior.

## Testing

- cargo fmt --all
- cargo check -p config
- cargo test -p config config::tests --lib (64 passed)
- cargo clippy -p config --lib -- -D warnings

---------

Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
2026-08-11 11:17:22 +00:00
Huaijin d5fa24711c perf(metrics): choose label timestamp filter automatically (#13756)
## Summary

- replace the `ZO_METRICS_INLIST_FILTER_ENABLED` switch with automatic
timestamp filter selection
- use an exact `IN` filter for up to 1024 timestamps and a bounded
`BETWEEN` predicate above that limit
- sort exact timestamp literals for stable query plans
- add boundary coverage for empty, threshold, and over-threshold
timestamp sets

## Rationale

Large timestamp `IN` lists add planning and evaluation overhead, while
always using `BETWEEN` can admit many unrelated label rows for sparse
timestamp sets. A bounded exact-list strategy keeps the selective path
for manageable sets without allowing query plans to grow indefinitely.

Release benchmarks used 4.2M rows, five label columns, four hash
partitions, and repeated median measurements:

- with a representative sparse range returning about 262K rows through
`BETWEEN`, 1024 timestamps favored `IN` (9.1 ms vs 10.1 ms), while 2048
favored `BETWEEN` (11.7 ms vs 10.2 ms)
- when unique maximum timestamps equaled the series count and both
predicates returned exactly one row per series, `BETWEEN` consistently
won and the gap grew with list size, confirming the need for an upper
bound

## Testing

- `cargo test -p promql series_labels::tests`
- `cargo clippy -p promql --tests --no-deps -- -D warnings`
- `git diff origin/main...HEAD --check`

## Operational note

`ZO_METRICS_INLIST_FILTER_ENABLED` is removed and no longer needs to be
configured.
2026-08-11 11:17:18 +00:00
Abhay Padamani 786f4f9f72 fix: dashboard label overlap issues (#13725)
1.
<img width="727" height="242" alt="image"
src="https://github.com/user-attachments/assets/2a84c1bf-2bc0-4fa7-aae5-e7801b99c93b"
/>

2.
<img width="167" height="380" alt="image"
src="https://github.com/user-attachments/assets/6d937583-eb51-458c-8dac-fcf668f1f145"
/>

3.
<img width="1072" height="150" alt="image"
src="https://github.com/user-attachments/assets/4f98ff15-06e4-45d2-a109-c84064cb644b"
/>

4.
<img width="32" height="289" alt="image"
src="https://github.com/user-attachments/assets/9e4ef37f-2e9a-4259-9c6e-041946e1e380"
/>

5.
<img width="500" height="622" alt="image"
src="https://github.com/user-attachments/assets/c4566094-6f58-4692-9ce8-bbcaf61026d7"
/>

6.
<img width="388" height="644" alt="image"
src="https://github.com/user-attachments/assets/03e14424-a59a-4738-b0aa-1f70c7d207f6"
/>

7.
<img width="388" height="564" alt="image"
src="https://github.com/user-attachments/assets/a1acded3-b2d6-4690-b2ba-47397f6a3fb4"
/>
2026-08-11 11:15:31 +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
Hengfei Yang 238b0d5262 fix: support zstd-compressed HTTP requests (#13728)
## Summary

- enable tower-http zstd request decompression
- add a router middleware regression test using a real zstd-compressed
JSON payload
- update decompression documentation to list zstd support

## Root cause

The Axum router uses `RequestDecompressionLayer`, but the workspace did
not enable tower-http's `decompression-zstd` feature. Requests from
clients such as Vector configured with `compression: "zstd"` were
therefore rejected with `415 Unsupported Media Type` before reaching the
ingestion handler.

## User impact

Vector HTTP sinks can continue using zstd compression when sending
batches to OpenObserve ingestion endpoints.

## Validation

- `cargo fmt --all -- --check`
- `cargo test -p openobserve-api-http
handler::http::router::decompression::tests`
- `git diff --check`

Fixes #13726
2026-08-11 10:33:44 +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
dependabot[bot]andHengfei Yang 220de78fdd chore(deps): bump taiki-e/install-action from 2.85.5 to 2.85.10 (#13732)
Bumps
[taiki-e/install-action](https://github.com/taiki-e/install-action) from
2.85.5 to 2.85.10.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/taiki-e/install-action/releases">taiki-e/install-action's
releases</a>.</em></p>
<blockquote>
<h2>2.85.10</h2>
<ul>
<li>
<p>Update <code>uv@latest</code> to 0.12.2.</p>
</li>
<li>
<p>Update <code>tombi@latest</code> to 1.2.7.</p>
</li>
<li>
<p>Update <code>cosign@latest</code> to 3.1.3.</p>
</li>
<li>
<p>Update <code>coreutils@latest</code> to 0.10.0.</p>
</li>
<li>
<p>Update <code>cargo-rdme@latest</code> to 2.2.0.</p>
</li>
<li>
<p>Update <code>cargo-crap@latest</code> to 0.4.3.</p>
</li>
</ul>
<h2>2.85.9</h2>
<ul>
<li>
<p>Update <code>zola@latest</code> to 0.23.1.</p>
</li>
<li>
<p>Update <code>wild@latest</code> to 0.10.0.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2026.8.2.</p>
</li>
<li>
<p>Update <code>just@latest</code> to 1.58.0.</p>
</li>
<li>
<p>Update <code>jaq@latest</code> to 3.1.1.</p>
</li>
<li>
<p>Update <code>cargo-nextest@latest</code> to 0.9.143.</p>
</li>
<li>
<p>Update <code>cargo-crap@latest</code> to 0.4.2.</p>
</li>
<li>
<p>Update <code>biome@latest</code> to 2.5.7.</p>
</li>
</ul>
<h2>2.85.8</h2>
<ul>
<li>
<p>Update <code>zizmor@latest</code> to 1.29.0.</p>
</li>
<li>
<p>Update <code>typos@latest</code> to 1.49.0.</p>
</li>
<li>
<p>Update <code>trivy@latest</code> to 0.73.0.</p>
</li>
<li>
<p>Update <code>tombi@latest</code> to 1.2.6.</p>
</li>
<li>
<p>Update <code>prek@latest</code> to 0.4.12.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2026.8.1.</p>
</li>
<li>
<p>Update <code>convco@latest</code> to 0.7.1.</p>
</li>
<li>
<p>Update <code>cargo-semver-checks@latest</code> to 0.50.0.</p>
</li>
<li>
<p>Update <code>cargo-crap@latest</code> to 0.4.1.</p>
</li>
</ul>
<h2>2.85.7</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md">taiki-e/install-action's
changelog</a>.</em></p>
<blockquote>
<h2>[2.85.10] - 2026-08-07</h2>
<ul>
<li>
<p>Update <code>uv@latest</code> to 0.12.2.</p>
</li>
<li>
<p>Update <code>tombi@latest</code> to 1.2.7.</p>
</li>
<li>
<p>Update <code>cosign@latest</code> to 3.1.3.</p>
</li>
<li>
<p>Update <code>coreutils@latest</code> to 0.10.0.</p>
</li>
<li>
<p>Update <code>cargo-rdme@latest</code> to 2.2.0.</p>
</li>
<li>
<p>Update <code>cargo-crap@latest</code> to 0.4.3.</p>
</li>
</ul>
<h2>[2.85.9] - 2026-08-06</h2>
<ul>
<li>
<p>Update <code>zola@latest</code> to 0.23.1.</p>
</li>
<li>
<p>Update <code>wild@latest</code> to 0.10.0.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2026.8.2.</p>
</li>
<li>
<p>Update <code>just@latest</code> to 1.58.0.</p>
</li>
<li>
<p>Update <code>jaq@latest</code> to 3.1.1.</p>
</li>
<li>
<p>Update <code>cargo-nextest@latest</code> to 0.9.143.</p>
</li>
<li>
<p>Update <code>cargo-crap@latest</code> to 0.4.2.</p>
</li>
<li>
<p>Update <code>biome@latest</code> to 2.5.7.</p>
</li>
</ul>
<h2>[2.85.8] - 2026-08-04</h2>
<ul>
<li>
<p>Update <code>zizmor@latest</code> to 1.29.0.</p>
</li>
<li>
<p>Update <code>typos@latest</code> to 1.49.0.</p>
</li>
<li>
<p>Update <code>trivy@latest</code> to 0.73.0.</p>
</li>
<li>
<p>Update <code>tombi@latest</code> to 1.2.6.</p>
</li>
<li>
<p>Update <code>prek@latest</code> to 0.4.12.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2026.8.1.</p>
</li>
<li>
<p>Update <code>convco@latest</code> to 0.7.1.</p>
</li>
<li>
<p>Update <code>cargo-semver-checks@latest</code> to 0.50.0.</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/taiki-e/install-action/commit/6c6fd71fe4fb72c3697d269963d0e15df8adedad"><code>6c6fd71</code></a>
Release 2.85.10</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/37cec23487191ef9aef8b1315865bd6dc584bef4"><code>37cec23</code></a>
Update zola manifest</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/4914ea4fea5759852f8cda51753420130b883d65"><code>4914ea4</code></a>
Update <code>uv@latest</code> to 0.12.2</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/0ce64163d455e35fedc5f5925221d4a71f05c990"><code>0ce6416</code></a>
Update <code>tombi@latest</code> to 1.2.7</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/1f89e2fb52c482b53fcf083bf64b5abd5d6563ab"><code>1f89e2f</code></a>
Update osv-scanner manifest</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/65ef13f21e6dd200e402682be3b1bc896f6aa862"><code>65ef13f</code></a>
Update kingfisher manifest</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/9f6a5a8ec701c59d62ac1013b92714e7410b2cae"><code>9f6a5a8</code></a>
Update <code>cosign@latest</code> to 3.1.3</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/df08c38f9c0580a749efefc712ba563ff2107db5"><code>df08c38</code></a>
Update <code>coreutils@latest</code> to 0.10.0</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/dc7bb1f807a876bf372de55372b320860efe4019"><code>dc7bb1f</code></a>
Update <code>cargo-rdme@latest</code> to 2.2.0</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/9970698e35256036b84ecfb8a70b90fe068a934b"><code>9970698</code></a>
Update <code>cargo-crap@latest</code> to 0.4.3</li>
<li>Additional commits viewable in <a
href="https://github.com/taiki-e/install-action/compare/v2.85.5...v2.85.10">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=taiki-e/install-action&package-manager=github_actions&previous-version=2.85.5&new-version=2.85.10)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

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)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
2026-08-11 07:46:13 +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
udevandsai nikhil kethe bbd8601c64 feat(llm): add annotation queues and datasets (#13719)
## Summary

- add annotation queue and dataset storage, management APIs, and review
workflows
- add discovery, dataset, queue, and review user interfaces
- add RBAC synchronization, super-cluster support, tags, version
history, and review progress

## Validation

- CI should pair this branch with the identically named Enterprise
branch for cumulative compilation testing

## Migration note

This is an additional cumulative PR created from the committed ud/ebb3
tip. The existing SPR pull requests and branches are intentionally left
unchanged.

---------

Co-authored-by: sai nikhil kethe <nikhil@openobserve.ai>
2026-08-11 07:28:26 +00:00
ktx-vaidehi 43b531d996 fix: build warning v2 (#13729) 2026-08-11 07:04:13 +00:00
Shrinath Rao 801f96fc8e ci(reaper): label zombie runs distinctly from runner-wait (#13745)
## Problem

The Stuck-Run-Reaper conflated **two different situations** and
described both as "waiting for an EKS runner / freed a runner":

1. **runner-wait** — a job queued with no EKS runner assigned (the real
target).
2. **zombie run** — a run left in a non-completed state with **0 jobs
ever dispatched**. It never held a runner.

A real example: a **2026-05-26** run stuck ~**1842h** with 0 jobs was
cancelled and announced as *"freed a runner stuck for 1842 hours"* —
which is nonsense; nothing was ever running.

## Fix — label the two distinctly

| Case | Threshold | Wording |
|---|---|---|
| runner-wait (job queued, no runner) | **60 min** (unchanged) |
"waiting for an EKS runner that was never assigned" |
| zombie run (0 jobs dispatched) | **24h** (new
`DEFAULT_ZOMBIE_RUN_THRESHOLD_HOURS` / `zombie_run_threshold_hours`
input) | "🧟 abandoned in a non-completed state with no jobs ever
dispatched — **held no runner**, stale-run cleanup" |

- **Cancellation behaviour is unchanged** — zombies are still swept;
they're just described accurately.
- Separate longer threshold so a brief GitHub scheduling blip isn't
labelled a zombie.
- Wait time for zombies uses `run.created_at`; `humanize()` now renders
days (`76d 18h`, not `1842h 0m`).
- Job-summary table gains a **Reason** column; PR comment wording
branches per kind.

## Validation

- YAML parses; embedded `github-script` body passes `node --check`
(async-wrapped as github-script runs it).
- OSS/ENT files differ only in the `WORKFLOWS` list + label comment
(verified by diff).
- Companion PR on the same branch `test/reaper-zombie-run-labeling` in
the sister repo.
2026-08-11 06:48:19 +00:00
Bhargav a9dce87071 ci(i18n): generate translations in the PR that changes en-US.json (#13741)
The translation job ran on `push: main` and opened a separate
`chore/update-translations` PR. That PR had no owner — it was never
assigned to or reviewed by a human — and it left a window where main
carried English strings with no translations.

Move the trigger to `pull_request` and commit the generated locale files
back onto the PR's own head branch, so the author who added the English
strings also owns their translations and both land in one merge.

Notes on the mechanics:

- The commit is pushed with TRANSLATIONS_APPROVE_TOKEN, not
GITHUB_TOKEN. GitHub does not trigger workflows for GITHUB_TOKEN pushes,
so the new head SHA would carry no status checks and required checks
could never pass — the PR would be wedged. Falls back to GITHUB_TOKEN if
unset.
- A guard step skips the run when the head commit is already the
generated translation commit, so the PAT push cannot ping-pong.
- Fork PRs are skipped: their branch lives in the contributor's repo and
no token here can push to it.
- concurrency flips to cancel-in-progress: true. The `false` was
main-specific (merges landed faster than a run finished); on a PR branch
nothing is committed until a run completes and the surviving run
re-derives the pending set from .translation_state.json, so a cancel
costs spend, never progress.
- The push retries with a rebase in case the author pushes concurrently.

Cost does not regress: re-translation across branches is prevented by
the committed per-key hashes in .translation_state.json, not by the
trigger, so each string is still translated once — on whichever branch
first introduces it.
2026-08-11 06:36:04 +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 25fc0baa45 ci(reaper): only inspect active runs, not full history (#13743)
## Problem

The Stuck-Run-Reaper (merged in #13740 / #2391) hung on manual dispatch
and would hit its own 10-minute job timeout **before inspecting any
run**.

Root cause: `listWorkflowRuns` was paginated with **no status filter**,
so `github.paginate` followed every page of the workflow's **entire
history** — **33,321 runs** for `playwright.yml` (~334 sequential API
calls), ×8 workflows on ENT. The active set it actually needs is only ~9
runs.

## Fix

Query only the non-completed statuses at the API level and de-duplicate:

```js
for (const st of ['in_progress','queued','pending','waiting','requested']) {
  const rs = await github.paginate(listWorkflowRuns, { ...wf, status: st, per_page: 100 });
  // collect, skip completed, dedupe by run id
}
```

**Effect:** ~334 calls/workflow → a handful; the reaper finishes in
seconds. The reaping/cancel/comment logic is unchanged.

## Validation

- YAML parses; embedded `github-script` body passes `node --check`
(async-wrapped as github-script runs it).
- Companion PR on the same branch `test/reaper-active-runs-only` in the
sister repo.
2026-08-11 06:05:10 +00:00
Shrinath Rao 11059d767b ci: add Playwright e2e Stuck-Run-Reaper workflow (#13740)
## What

Adds a scheduled **Playwright e2e Stuck-Run-Reaper** workflow
(`.github/workflows/stuck-run-reaper.yml`) that force-cancels CI runs
whose jobs are stuck **waiting for a self-hosted EKS runner that is
never assigned**.

## Why

`timeout-minutes` (job- and step-level) only counts **execution** time —
the clock starts once a job is running on a runner. It does **not**
cover the time a job spends `queued` waiting for a runner to be
assigned. So a job that never gets an `eks-openobserve-*` runner sits
indefinitely; GitHub's only native cap for that is a very lax ~24h
auto-cancel. We recently had several runs stuck 3–11h in exactly this
state.

This reaper closes that gap.

## How

- **Trigger:** cron every 15 min + `workflow_dispatch` (with `dry_run`
and `threshold_minutes` inputs).
- **Runner:** `ubuntu-latest` (GitHub-hosted — deliberately not the EKS
pool it polices).
- **Logic:** scans non-completed runs of the policed workflows → finds
jobs that are `queued`, have **no runner assigned**, request an `eks-*`
label, and have waited **> 60 min** → `force-cancel`s the run. Also
covers the "run never dispatched any jobs" case.
- **Transparency:** on `pull_request` runs it comments on the PR
explaining **which runner was never assigned, when the job was queued,
how long it waited, and when it was identified as stuck**. Non-PR runs
(scheduled/dispatch) are still cancelled to free the queue, with the
details logged to the job summary.

## Coverage (OSS)

Polices every OSS workflow using the `eks-openobserve-*` runners:

| Workflow | Triggers | Reaped? |
|---|---|---|
| `playwright.yml` | pull_request, merge_group | ✅ (comments on PR) |
| `playwright_regression.yml` | schedule, workflow_dispatch | ✅ (cancel
+ log) |
| `release.yml` | push | ❌ excluded — stuck release builds left for a
human |

Job-level `timeout-minutes` in the policed workflows are intentionally
left unchanged — those are correctly-sized execution caps and address a
different failure mode.

### Example reaper comment

> ## 🛑 Playwright run auto-cancelled by the runner-reaper
> A job in this run was **waiting for a self-hosted EKS runner that was
never assigned**, past the 60-minute threshold. The reaper
force-cancelled the run to free the queue.
>
> | Job | Requested runner | Waiting since | Waited | Runner status |
> |---|---|---|---|---|
> | `e2e / Alerts` | `eks-openobserve-standard-8` | 2026-08-11 00:58:04
UTC | 10h 44m | ❌ never assigned |

## Notes

- Safe to merge and observe: run manually via **Actions → Playwright e2e
Stuck-Run-Reaper → Run workflow** with `dry_run: true` to see what it
*would* cancel without acting.
- Companion PR in o2-enterprise on the same branch
`test/stuck-run-reaper` (broader coverage list there).
2026-08-11 05:28:25 +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
Hengfei Yang ca75da8b78 fix(db): scope postgres catalog lookups to the active schema (#13723)
Fixes #11342.

## Problem

Upgrading fails on any deployment using a non-`public` postgres schema:

```
[POSTGRES] Fresh install: creating partitioned table file_list
db init failed: SqlxError# relation "file_list" already exists
```

The DDL and DML we emit are **unqualified** and resolve through
`search_path`, but the catalog / `information_schema` lookups that
decide *what* to emit were pinned to a literal `public`. The two
disagree: `get_table_relkind` reports "table missing" for a table that
plainly exists, we take the fresh-install branch, and `CREATE TABLE`
collides with the table already sitting in the user's schema.

**The panic is not the worst case.** When a stale same-named table
exists in `public` (old deployment, another tenant), the probe returns
`'r'` and we take the *regular-to-partitioned migration* branch against
the wrong schema's table — renaming its indexes. That matches the
index-name conflicts the reporter described.

The reporter found 2 sites; there were 14 in total.

## Approach

Replace the literal `'public'` with `current_schema()` inline in each
catalog query. It evaluates on the very connection that runs the query —
the first existing entry of that connection's `search_path`, i.e.
exactly where its unqualified DDL/DML lands — so a probe can never
disagree with the writes again. No Rust-side state.

Two details:

- `pg_class` lookups join `pg_namespace` on `nspname = current_schema()`
instead of casting to `regnamespace`: the cast parses its input as an
identifier and fails on schema names with special characters (e.g.
`My-Schema.X`).
- `verify_schema_consistency()` at startup checks the read-write,
read-only and DDL pools resolve to the same schema. They come from three
separately configured DSNs, and `ZO_META_DDL_DSN` is documented as a
different **role**, which can carry its own `search_path` via `ALTER
ROLE ... SET search_path` — e.g. `cache_indices` reads on `CLIENT` while
`create_index` writes on `CLIENT_DDL`, so a divergence would silently
relocate the same bug class. Fail loudly instead.

## Sites fixed

| File | Functions |
|---|---|
| `file_list/postgres.rs` | `get_table_relkind`,
`check_partition_exists`, `drop_empty_partitions`,
`get_varchar_column_width`, `apply_autovacuum_tuning`,
`align_identity_sequence` |
| `db/postgres.rs` | `cache_indices` (had **no** schema filter at all),
`add_column`, `drop_column`, meta `start_dt` probe |
| `migration/adapter/postgres.rs` | `list_tables`, `get_columns`,
`get_foreign_keys`, `sync_sequences` |
| 2 sea-orm migrations | postgres branch had no schema filter, while the
mysql branch correctly used `DATABASE()` |

The migration-adapter ones matter independently: under a non-public
schema `list_tables()` returned **zero** rows, so the cross-database
migration tool reported success having migrated nothing.

## Verification

Against a real PostgreSQL 18.4 instance with the app configured with
`search_path=o2`:

- **Fresh install** into schema `o2`: boots clean, all 121 tables land
in `o2`.
- **Forced upgrade path** (`db_schema_version` lowered) **with a stale
regular `public.file_list` planted**: logs `Table file_list already
partitioned, ensuring partitions`, upgrade completes, and the stale
`public` table is untouched. On main this exact setup either panics
(`relation "file_list" already exists`) or migrates the wrong schema's
table.
- Every rewritten query shape additionally verified in psql under schema
`o2test` and under a schema named `My-Schema.X` (stale-table isolation,
partition listing, varchar width, identity-sequence alignment, index
cache, adapter `list_tables`).

`cargo check --bin openobserve`, `cargo fmt --check`, `cargo clippy -p
infra --all-targets` all clean; 108 infra postgres unit tests pass.

## Not covered here

1. **Recovery for already-broken databases.** This prevents the failure
but does not unstick installs that are mid-migration with renamed
indexes. Worth a follow-up (idempotent cleanup, or a documented recovery
procedure).
2. **CI coverage.** There is no job exercising a non-`public`
`search_path`, which is why these 14 sites accumulated across different
changes. A postgres job with `search_path=o2test` would stop this
recurring.
2026-08-10 12:48:29 +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
Omkar Kesarkhane a2f950153e fix(logs): pass the expanded row to View Trace (#13722)
#13708

## Problem

Clicking **View Trace** inside an expanded log row on the Logs page does
nothing and throws:

```
TypeError: Cannot read properties of undefined (reading '_timestamp')
    at na (SearchResult.B_xPyti5.js:7:22212)
    at De (JsonPreview.CX_tRWWx.js:2:9172)
```

The same button in the row-detail drawer works, so the failure reads as
intermittent.

## Root cause

`JsonPreview` emitted `view-trace` with no payload, while the result
grid's expansion slot bound the handler by reference:

```vue
<template #expansion="{ row }">
  <JsonPreview :value="row" … @view-trace="redirectToTraces" />
</template>
```

`redirectToTraces(log)` then read
`log[store.state.zoConfig.timestamp_column]` off `undefined` — and
`_timestamp` is that column's default value, which is exactly the
reported error.

The binding used to work because the table component in between
re-emitted the event with the row attached (`emits("view-trace", row)`).
That component was removed when the logs result grid migrated from
`TenstackTable` to `OTable`, and the payload-less binding was carried
over unchanged. The row-detail drawer resolves the hit in its own
template, which is why only the inline expansion broke.

The correlation drawer's log table (`CorrelatedLogsTable`) had the
identical defect — not reported, same fix.

## Fix

- `JsonPreview` emits the previewed log with the event, matching its
sibling `show-correlation` emit, so every current and future listener
receives the record.
- Both expansion slots pass `row` explicitly, restoring the
pre-migration semantics at the call site.
- Both handlers no-op with a warning toast when the record is missing,
so a visible, enabled button can never again be a silent dead end during
triage.

No change to when the button is shown, to the ±15 min window, or to the
traces route contract.

## Tests

Three regression specs, all failing before the fix with the exact
`TypeError` above:

- `JsonPreview.spec.ts` — the existing test only asserted *that*
`view-trace` fired, never with what; it now asserts the payload, which
is why the regression slipped through.
- `SearchResult.viewTrace.spec.ts` (new) — expanded row emits a
payload-less `view-trace`; asserts navigation to `traceDetails` with the
right `trace_id`, `span_id`, and time window.
- `CorrelatedLogsTable.viewTrace.spec.ts` (new) — same for the
correlation drawer.

Both new files are focused specs because neither existing suite installs
a router or renders `OTable`'s expansion slot.

```
Test Files  8 passed (8)
     Tests  316 passed | 3 skipped (319)
```

`npm run type-check` → clean. `eslint` on the touched files → 0 errors.

## Verification

Logs page with a trace-id-bearing stream and `service_streams_enabled`
off: expand a row inline → **View Trace** lands on the trace with the
right id and window. Same from the row-detail drawer and the correlation
drawer's log table; console clean in all three.
2026-08-10 12:22:24 +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
Omkar Kesarkhane bf494ce1de fix(synthetics): move check variables into a shared resizable panel (#13699)
## What

Check variables lived inside the **Auth & Network** card on the
Configure page, so an author writing `{{token}}` references into journey
steps could not see them while editing. They now live in a
**`CheckVariablesPanel`** mounted as the right-hand pane on **both**
wizard pages (Journey + Configure), over one source of truth —
`check.variables`, **no schema change**.

## Panel behaviour

- **Cards** — name, masked value for secrets (raw value never rendered),
and a usage count computed from exact `{{name}}` matches across each
step's `value`, `selector`, and locator candidates. The "Used in N
steps" line only appears when the variable is actually referenced.
- **Add / edit** — inline, one form at a time, with the existing
validation rules (`^[A-Za-z_][A-Za-z0-9_]*$`, no duplicates). The add
form appends to the end of the list and scrolls into view; its button
stays pinned below the scroll region so it never scrolls away.
Re-pressing it while the form is open re-scrolls instead of discarding a
half-typed draft.
- **Delete** — confirmed via `ODialog`, and the dialog says what's at
stake: *"baseUrl will be removed from this check. It is referenced by 1
step, which will no longer resolve."* Still backed by the ~6s undo that
restores at the original index.
- **Empty state** — `OEmptyState` whose action is the sole Add
affordance while the list is empty.
- **Layout** — resizable via `OSplitter`; width is shared across both
pages through a new `useCheckWizardUi` composable. The Journey toolbar
gets a collapse toggle beside **Record**, following the field-list
pattern in `logs/SearchResult.vue`.

`CheckAuthNetwork` loses its Variables sub-section; its header summary
is now basic-auth only.

## Two OStepper library fixes this depended on

`OStep` is a multi-root component, so a `class` passed to it was
**silently dropped**. Every `h-full` below it then resolved against an
indefinite height — which left the splitter separator **0px tall and
impossible to grab**. (It predates this branch; step 1 already carried a
no-op `h-full!`.)

- `OStep` now forwards attrs explicitly — onto the **whole step** in
vertical mode, where they used to auto-inherit, and onto the content
panel in horizontal mode.
- `OStepper`'s horizontal pane gets `min-h-0` so a height-constrained
stepper clamps its content instead of growing with it (without it the
Configure separator measured 1818px and the panel was not truly pinned).

Verified the other horizontal-stepper consumers
(`CreateDestinationForm`, `OrgStorageEditor`) and the vertical
`BrowserJourneyStepEditor` are unaffected.

## Testing

**Automated** — 1147 tests pass across `components/synthetics`,
`views/synthetics`, `lib/navigation`, and `composables/synthetics`,
including a new 26-test `CheckVariablesPanel.spec.ts`
(add/edit/validation/masking/usage counts/scroll-to-form/confirm+undo
with fake timers). `type-check:app`, `lint:design:strict`, and ESLint
are clean — ESLint warning count is identical to baseline (0 errors).

**Manual, in the running app** — full flow driven end to end: gate →
journey → add with an invalid name rejected → valid add → splitter drag
(separator now 467px tall, drag moves it) → collapse/reopen preserving
the dragged width → step referencing `{{baseUrl}}` flipping the usage
badge to 1 → delete confirmation warning about the broken step → cancel
keeps it → confirm + undo restores it → Configure page showing the same
panel at the same width with the Auth & Network card carrying no
Variables section.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-10 11:01:26 +00:00
Bhargav a338597481 fix(lib): re-place toggle pill after an item reorder (#13721)
The sliding selection pill in OToggleGroup was re-measured only on a
modelValue change or a track resize. A drag-to-reorder moves the active
item but leaves the track exactly the same size, so neither trigger
fired: the pill kept the previous item's position and width, landing
under the wrong item and overlapping its neighbour, while the active
text colour — being data-state driven — correctly followed the moved
item. On the traces detail tab bar that read as the highlight and the
colour coming apart.

Observe the track's childList and snap-measure on a change, mirroring
OTabs, which already did this for its underline. Attributes are
deliberately not observed: data-state flips on a real selection change,
which the modelValue watcher handles with a slide, and snapping there
would cancel it. Also covers items being added or removed, e.g. the
traces Thread tab appearing for a trace with LLM spans.
2026-08-10 10:14:41 +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
d93d599918 chore: update translations from en-US.json (#13694)
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en-US.json`).

🤖 Generated with automated translation workflow

---------

Co-authored-by: github-merge-queue[bot] <118344674+github-merge-queue[bot]@users.noreply.github.com>
Co-authored-by: ktx-vaidehi <vaidehi.akhani@kiara.tech>
2026-08-10 09:43:32 +00:00
Bhargav dd5ff2c131 feat(iam): serve the MCP Server setup page on the OSS build (#13720)
## Summary

IAM > MCP Server was gated to enterprise/cloud, but the endpoint it
documents is not. The backend registers api-endpoint outside the ent
block and it initialises the MCP tools "for all editions" — only the
OAuth *discovery* endpoints are enterprise-only. OSS users could reach
the MCP server, just not the page that tells them how.

Removes the build/runtime gates so the page ships everywhere, and hides
the one part that genuinely is enterprise-only.

### Gates removed

- `useEnterpriseRoutes.ts`: the `mcpServer` child was pushed inside the
`isCloud || isEnterprise` branch, so on OSS the route did not exist at
all. Moved into the base `children` array.
- `IdentityAccessManagement.vue`: the tab's `visible` was `isEnt &&
zoConfig.ai_enabled`. Dropped. `ai_enabled` is `enterprise_value!(false,
o2cfg.ai.enabled)`, i.e. always false on OSS, so that condition could
never have let the tab through.
- `useIngestionRoutes.ts` / `Recommended.vue`: the Ingestion >
Recommended cross-link carried the same gate, explicitly to match where
`mcpServer` existed. Un-gated to stay consistent.

### Card corrections

- Drop the "MCP server is an Enterprise feature." note and delete the
now-dead `ingestion.mcp.enterpriseNote` key from all 15 locale files.
- OAuth *is* enterprise-only: `oauth_authorization_server_metadata` and
`oauth_protected_resource_metadata` are `cfg(not(enterprise))` 404
stubs. The OAuth tab is now hidden on OSS and `authMode` defaults to
`token`, rather than landing users on a flow that cannot complete. With
one method left the tab picker is hidden too.

The token path works unchanged on OSS: the snippets authenticate with
`Basic base64(email:passcode)`. "Generate read-only credential" stays
hidden there — `useMcpCredential` requires `rbac_enabled` to attach the
read-only role — which degrades cleanly to the quick-start passcode and
produces identical snippets.

### Tests

Updated the OSS IAM child-count assertion (5 -> 6), the only test the
change broke. Added OSS-presence coverage in `useEnterpriseRoutes.spec`,
`useIngestionRoutes.spec`, `IdentityAccessManagement.spec` (with
`ai_enabled` off) and `Recommended.spec`.

Verified with `format:check`, `verify` (type-check:app + 5 lint gates, 0
errors) and 457 tests across the 7 affected specs.

Refs #13717
2026-08-10 09:13:05 +00:00
ktx-vaidehi e1aad76283 fix(dashboards): use bookmark-add for the panel annotation button (#13718)
The panel header's Add Annotation toggle rendered the same 'edit' glyph
that the kebab menu uses for Edit Panel, so one shape stood for two
unrelated actions on the same panel. Annotations are markLines pinned to
a point on the x-axis, which bookmark-add states without borrowing the
pencil.
2026-08-10 07:45:18 +00:00
Bhargav 9cf4933f15 fix: Browser sdk test 0.4.2 (#13584) 2026-08-10 07:37:14 +00:00
Omkar Kesarkhane 62528b4c12 fix(synthetics): sequential recorder setup checklist with state-safe refresh and incognito failure recovery (#13684)
## Summary

Follow-up to #13658 — redesigns the recorder setup flow as a
sequential-gated checklist and closes the UX holes around refresh, stale
connections, and incognito failures.

### Sequential-gated setup checklist (shared by the create-page setup
phase and the journey dialog)

- Only one task is actionable at a time; later tasks render locked, done
tasks collapse to a green check row.
- **Task 1 (install)**: Web Store button + an "I've added it to Chrome"
attestation (the install happens in another tab the page can't observe).
Live bridge detection auto-completes it ("Detected automatically");
checkbox-only completion offers Undo.
- **Task 2 (incognito)**: "Do this in Chrome" callout with inline
Chrome-UI icons + first-person attestation, collapsing to "Allowed in
Incognito" with Undo.
- **Task 3 (connect)**: completes only on the real probe signal —
attestations can't skip into a recording that would hang. Icon-click is
the primary instruction (injects without reloading); refresh is the
secondary link.
- Journey dialog: per-action subtitle, "n of 3 done" badge, full-width
CTA reading "<action> — finish the tasks above first" while locked, a
hint naming the exact blocking task, skip link (record only).
- Attestations are session-only by design — persisting them would keep
tasks pre-completed after the extension is removed.

### State-safe refresh + gate bypass

- Entering the setup phase mirrors url/name into the query (`setup=1`);
mount restores them, so a mid-setup F5 returns to the checklist with
fields intact.
- The checklist's "refresh this page" strips the setup flag; when
url+name are present and the warm probe detects the extension, the gate
auto-commits straight into the editor (recording stays a deliberate
click). `?url=&name=` deep links work as a byproduct.

### Incognito failure recovery

- Both the replay preflight classification and the recording-start
refusal (the extension's raw "Recording needs incognito access…"
message) now revoke the disproved attestation and auto-open the setup
dialog on its incognito task, with the failed action as the CTA. The
warning card slims to Retry / Dismiss / Show setup steps (its
chrome://extensions walkthrough duplicated the checklist, and
`window.open("chrome://…")` is blocked by Chrome anyway).

### Compulsory re-verification after the incognito toggle

- Toggling "Allow in Incognito" **reloads the extension and orphans the
tab's bridge** (per the takeover-handshake contract in the extension's
content script), so giving the ack invalidates `extensionReady` and
re-probes — the connect task stays open until a fresh probe passes. The
install fact is latched first so task 1 doesn't regress.

## Test plan

- 141 tests across the four specs: ExtensionSetupChecklist 18,
ExtensionSetupDialog 19, BrowserJourney 60, CreateBrowserTest 44 —
including the deferred-probe assertions that the CTA follows the *fresh*
probe verdict, not stale state
- `npm run lint` / `npm run type-check` / `npm run lint:design:strict`
clean
- Manual: full install → incognito → connect flow against the published
extension, both failure paths (record + replay without incognito),
refresh at each task
2026-08-10 06:37:17 +00:00
Recoordinate 93ee50ca17 docs: fix occured typo in alert and folder doc comments (#13705)
`occured` should be `occurred`. Four occurrences, all in rustdoc
comments on error variants:

- `src/core/src/alerts/alert.rs:235,240`
- `src/db/src/folders.rs:83,88`

No identifiers, error messages or code paths change.

The remaining two hits in the tree are in
`src/config/src/utils/async_walkdir/`, which is a vendored copy of the
`async_walkdir` crate and carries its upstream copyright headers, so I
left those untouched.
2026-08-10 03:41:40 +00:00
Hengfei Yang 4d23d85fcb fix(i18n): improve Simplified Chinese translations (#13707) 2026-08-10 11:41:12 +08:00
Pinguladora 575e8ea4d5 feat: add zstd compression on OTLP gRPC ingest (#13703)
Design at: #13702 / inline in PR description

Add the `zstd` feature to tonic and enable `accept_compressed(Zstd)` on
the OTLP logs, metrics and trace gRPC services in both `run_common` and
run_router`.

Closes #13702 

## Out of scope
- Response compression  on `send_compressed(Zstd)`
- OTLP/HTTP path (tower-http zstd features)
- snappy would be the better choice under CPU pressure, but tonic has no
snappy support.
2026-08-09 06:02:22 +00:00
Omkar Kesarkhane f0e12bb913 fix(traces): scope "View Logs" correlated query to the clicked span (#13700)
## Problem

In enterprise, the span sidebar's **View Logs** goes through
`navigateToCorrelatedLogs()`, which built its WHERE clause purely from
the per-stream `filters` returned by `_correlate`. The result was a
service/namespace-wide log query with no reference to the span the user
actually clicked.

The OSS path (`buildQueryDetails`) pins both `span_id` and `trace_id`.
The enterprise path did not.

## Change

Append `span_id` and `trace_id` conditions in
`navigateToCorrelatedLogs()`:

- **Field names** from the org's `span_id_field_name` /
`trace_id_field_name` settings
- **Values** from `searchObj.data.traceDetails.selectedSpanId` and
`selectedTrace?.trace_id` — the same sources `buildQueryDetails` reads,
and `selectedSpanId` is what drives the sidebar's `:span` prop, so it
always matches the clicked span
- Each condition is missing-value guarded and routed through the
existing semantic-group keying, so an id field that is also a tracked
dimension yields one condition rather than two — with the exact id
winning over a stream's alias

## Deduplication

The new code would otherwise have been a third copy of logic already
present twice, so:

- Extracted `getSpanIdField()` / `getTraceIdField()`;
`buildQueryDetails` and `navigateToCorrelatedLogs` now share them. This
also fixes a latent bug — `buildQueryDetails` did `String(spanIdField)`
with no fallback, emitting a column literally named `undefined` when the
org setting was absent. Defaults now mirror `stores/index.ts`.
- Extracted `groupIdFor()` / `setCondition()` so the stream-filter loop
and the id loop share one implementation.
- Dropped the `usedGroups` Set — every `add()` was paired with a
`conditions.set()` on the same key, making `usedGroups.has(x)` always
equivalent to `conditions.has(x)`.
- Replaced the hand-rolled `String(v).replace(/'/g, "''")` with
`quoteSqlLiteral()` from `utils/telemetryCorrelation` (byte-identical
output). Deliberately *not* `buildSqlCondition()` — it wraps identifiers
via `quoteSqlIdentifier` (always quotes), which would change emitted SQL
from `field = 'v'` to `"field" = 'v'`.
2026-08-08 03:14:13 +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
ktx-vaidehi 6c43e01823 fix: px to rem (#13541) 2026-08-07 11:45:59 +00:00
Abhay Padamani eb2ab67f5a fix: dashboard table column formatting via column header (#13695) 2026-08-07 10:47:00 +00:00