Commit Graph
3003 Commits
Author SHA1 Message Date
Hengfei Yang 3fda083f51 feat(traces): add trace time index (#13827)
Design at:
[https://github.com/openobserve/designs/blob/main/infrastructure/file-list/trace-time-index.md](https://github.com/openobserve/designs/blob/main/infrastructure/file-list/trace-time-index.md)

## Summary

- add an hourly Metadata time index per trace stream to resolve a trace
ID to its full time range
- add external time-range and trace-details APIs, including bounded
locate/expand behavior and standard search responses
- integrate index generation into trace ingestion and preserve query
visibility through the synchronous Metadata write path
- synchronize retention and deletion for associated Metadata streams
- reuse the shared trace stream permission check across trace APIs

## Why

Trace detail lookup currently depends on a caller-provided time range,
which can omit spans outside that window. The time index provides a
bounded, best-effort range lookup for `stream + trace_id` without
scanning the full trace stream.

Closes #13823

## Environment variables

| Variable | Default | Effect |
| --- | --- | --- |
| `ZO_TRACE_TIME_INDEX_ENABLED` | `true` | Enables per-trace-stream
Metadata indexes, index writes during trace ingestion, and indexed
time-range lookup. Only traces ingested after the feature is enabled are
indexed; there is no historical backfill. |
| `ZO_QUERY_TIMEOUT` | 600 seconds | Existing setting reused as the
cumulative Trace Timer locate/expand deadline and as the default timeout
for the trace-details search. |

When enabled, each traces stream can create a related Metadata stream
named `trace_time_index_{stream}`. Its retention settings follow the
source stream, and it is removed with the source stream.

## API changes

All timestamp parameters and returned time ranges use microseconds.

### Get a trace time range

```http
GET /api/{org_id}/{stream_name}/traces/{trace_id}/time_range
```

Query parameters:

- `hint_ts` (optional): timestamp hint used to start the index lookup

Returns the source stream, trace ID, and the resolved `start_time` /
`end_time`. The `range` field is `null` when the index cannot resolve
the trace.

### Get trace details

```http
GET /api/{org_id}/{stream_name}/traces/{trace_id}/details
```

Query parameters:

- `start_time` and `end_time` (optional, but must be supplied together):
caller-provided range
- `hint_ts` (optional): timestamp hint for the time-index lookup
- `timeout` (optional): trace-details search timeout in seconds

The endpoint unions the caller range with the indexed range, so it never
narrows a caller-provided range. If neither the caller range nor the
index can provide a range, it returns `400`. The response uses the
standard search response and includes the effective range in
`new_start_time` and `new_end_time`.

## Validation

- `cargo test -p openobserve-api-search --lib` — 145 passed
- `cargo test -p openobserve-core traces::time_index::tests --lib` — 7
passed
- `cargo test -p infra
db::trace_time_index::tests::test_coverage_start_cache -- --exact
--nocapture` — passed
- `cargo check -p openobserve-api-search`
- `cd web && npm run type-check:app`
- end-to-end ingestion and query validation with the data generator

## Validation notes

- targeted ESLint could not start because the local web dependencies are
missing `@eslint/css`
- enterprise-only compilation could not be completed from this OSS
checkout because the local enterprise overlay is unavailable
2026-08-14 15:29:23 +00:00
dbee572722 fix: slo bugs (#13784)
closes https://github.com/openobserve/o2-enterprise/issues/2397

---------

Signed-off-by: Yashodhan Joshi <yjdoc2@gmail.com>
Co-authored-by: Yashodhan Joshi <yjdoc2@gmail.com>
Co-authored-by: sai nikhil kethe <nikhil@openobserve.ai>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-14 10:31:32 +00:00
Omkar Kesarkhane 6927c2f335 fix(synthetics): collapse the variables panel by default (#13825)
## What

The variables panel on the browser-test **Journey** step now starts
collapsed.

## Why

The journey is what that page is for, and the panel was taking a share
of the width before the author had asked for it. The toolbar toggle sits
right next to the journey and is labelled, so bringing the panel in is
one click — and the splitter position is still shared with Configure, so
nothing about the panel's own behaviour changes.

## Scope

One line plus a comment. `variablesPanelOpen` is journey-only by design
(the existing comment above it explains why it is not shared with
Configure), so this cannot affect the Configure step.

```diff
-const variablesPanelOpen = ref(true);
+const variablesPanelOpen = ref(false);
```

## Testing

`npx vitest run src/views/synthetics/CreateBrowserTest.spec.ts` — 44
passing.

Split out of a larger synthetics branch so it can land on its own.
2026-08-14 09:43:24 +00:00
Harsh Mahajan 0ed5336bf1 chore: adding HA mode for o2-ai agent (#13525) 2026-08-14 16:14:03 +08:00
ktx-vaidehi e490a5c347 fix(logs): load the stream list on Visualize when no stream is selected (#13812)
Entering Visualize from the logs page with no stream selected left the
Stream dropdown on "No options found" — enabled, openable, and empty, so
there was no way to pick one.

PanelFieldList defers its initial fetch in edit mode until a stream is
set, exempting only "metrics". The Visualize tab ("build") needs the
same exemption: its select is the user's own, and the list is keyed on
stream_type alone, so waiting for a stream buys nothing.

Also arms the stream_type watcher at mount, so changing Stream Type with
a blank stream now reloads the list instead of doing nothing.

- https://github.com/openobserve/o2-enterprise/issues/2165
2026-08-13 13:01:21 +00:00
ktx-akshay d1f8941512 test: stabilize test cases for the Alpha environment (#13551) 2026-08-13 06:41:03 +00:00
Bhargav e4503f7fcb fix: Fix/match all pipe query corruption (#13764) 2026-08-12 08:50:33 +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
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 Sharmaandgithub-actions[bot] 360b989272 feat(slos): PromQL support for count and time-sliced SLOs, plus SLO/alert regression fixes (#13761)
Supersedes #13738, whose commit is included here as the first of four.

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

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

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

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

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

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

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

## PromQL support

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

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

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

## Metrics streams choose SQL or PromQL

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

## Validation

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

## Also fixed

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

## Verification

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

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

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

---------

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

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

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

Verified: FunctionsToolbar.spec.ts 18/18 pass, vue-tsc clean. ESLint
could not be run locally (@intlify/eslint-plugin-vue-i18n missing from
node_modules).
2026-08-11 13:25:45 +00:00
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
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
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
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
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
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
Hengfei Yang 4d23d85fcb fix(i18n): improve Simplified Chinese translations (#13707) 2026-08-10 11:41:12 +08: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
74805cf8ae chore(deps): update web npm dependencies (2026-08-06) (#13673)
## 📦 Weekly NPM Dependencies Update

This automated PR updates `web/` npm packages to their latest versions
and regenerates `package-lock.json`.

### Updated Packages

- **@rudderstack/analytics-js**: 3.6.0 → 3.31.6
- **@tanstack/vue-form**: ^1.29.1 → ^1.33.3
- **@tanstack/vue-table**: ^8.21.3 → ^9.0.0
- **@tanstack/vue-virtual**: ^3.13.16 → ^3.13.35
- **@vue-flow/core**: ^1.48.1 → ^1.48.2
- **acorn**: ^8.15.0 → ^8.18.0
- **acorn-walk**: ^8.3.4 → ^8.3.5
- **cron-parser**: ^5.4.0 → ^5.7.0
- **date-fns**: ^4.1.0 → ^4.4.0
- **dompurify**: ^3.4.12 → ^3.4.13
- **gridstack**: ^12.4.2 → ^13.0.2
- **js-beautify**: ^1.15.4 → ^2.0.3
- **js-cookie**: ^3.0.7 → ^3.0.8
- **js-yaml**: ^4.3.0 → ^5.2.3
- **lucide-vue-next**: ^0.562.0 → ^1.0.0
- **marked**: ^17.0.1 → ^18.0.9
- **moment-timezone**: ^0.6.0 → ^0.6.3
- **monaco-editor**: ^0.52.2 → ^0.56.0
- **node-polyfill-webpack-plugin**: ^4.0.0 → ^4.1.0
- **reka-ui**: ^2.9.6 → ^2.10.1
- **reodotdev**: ^1.0.0 → ^1.1.0
- **sql-formatter**: ^15.6.12 → ^15.8.2
- **uuid**: ^14.0.0 → ^14.0.1
- **vue**: ^3.5.26 → ^3.5.41
- **vue-i18n**: ^11.2.8 → ^11.4.8
- **vue-router**: ^4.6.4 → ^5.2.0
- **@cypress/vue**: ^6.0.2 → ^6.0.3
- **@cypress/webpack-dev-server**: ^5.4.1 → ^5.6.2
- **@iconify-json/majesticons**: ^1.2.4 → ^1.2.5
- **@iconify-json/material-symbols**: ^1.2.72 → ^1.2.88
- **@iconify-json/octicon**: ^1.2.25 → ^1.2.31
- **@intlify/core-base**: ^11.2.8 → ^11.4.8
- **@rushstack/eslint-patch**: ^1.15.0 → ^1.16.1
- **@tailwindcss/postcss**: ^4.1.18 → ^4.3.3
- **@tailwindcss/typography**: ^0.5.19 → ^0.5.20
- **@tailwindcss/vite**: ^4.1.18 → ^4.3.3
- **@types/jsdom**: ^27.0.0 → ^28.0.3
- **@types/leaflet**: ^1.9.21 → ^1.9.22
- **@types/luxon**: ^3.7.1 → ^3.7.3
- **@types/node**: ^25.0.3 → ^26.1.2
- **@vitejs/plugin-vue**: ^6.0.3 → ^6.0.8
- **@vitejs/plugin-vue-jsx**: ^5.1.3 → ^5.1.6
- **@vitest/coverage-v8**: ^4.0.16 → ^4.1.10
- **@vitest/ui**: ^4.0.16 → ^4.1.10
- **@vue/eslint-config-typescript**: ^14.6.0 → ^14.9.0
- **@vue/test-utils**: ^2.4.6 → ^2.4.11
- **@vue/tsconfig**: ^0.8.1 → ^0.9.1
- **autoprefixer**: ^10.4.23 → ^10.5.4
- **c8**: ^10.1.3 → ^12.0.0
- **cypress**: ^15.19.0 → ^15.20.0
- **cypress-localstorage-commands**: ^2.2.8 → ^2.3.0
- **dotenv**: ^17.2.3 → ^17.4.2
- **eslint**: ^9.39.2 → ^10.8.0
- **eslint-plugin-cypress**: ^5.2.0 → ^7.0.0
- **eslint-plugin-vue**: ^10.6.2 → ^10.10.0
- **fs-extra**: ^11.3.3 → ^11.4.0
- **happy-dom**: ^20.8.9 → ^20.11.1
- **jsdom**: ^27.4.0 → ^30.0.1
- **msw**: ^2.12.7 → ^2.15.0
- **postcss**: ^8.5.10 → ^8.5.26
- **postcss-html**: ^1.8.1 → ^2.0.0
- **prettier**: ^3.7.4 → ^3.9.6
- **rollup-plugin-visualizer**: ^6.0.5 → ^7.0.1
- **sass**: ^1.97.1 → ^1.102.0
- **sass-loader**: ^16.0.6 → ^17.0.0
- **start-server-and-test**: ^2.1.3 → ^3.0.12
- **stylelint**: ^17.14.0 → ^17.14.1
- **vite**: ^7.3.2 → ^8.2.0
- **vite-plugin-istanbul**: ^7.2.1 → ^9.0.1
- **vitest-canvas-mock**: ^1.1.3 → ^1.1.4
- **vue-tsc**: ^3.2.1 → ^3.3.9
- **typescript**: ~5.9.3 → ~7.0.2
- **vitest**: ^4.0.16 → ^4.1.10

### Validation

This workflow does **not** run any checks — type check, unit tests and
build all run as normal PR checks below. Review the results there before
merging; a red check usually means one of the upgrades above needs to be
pinned back or accompanied by a code change.

---

🤖 This PR was automatically created by the Weekly NPM Update workflow

---------

Co-authored-by: Bhargav <551527+bjp232004@users.noreply.github.com>
Co-authored-by: Bhargav <BJP232004@GMAIL.COM>
Co-authored-by: ktx-abhay <abhay.padamani@kiara.tech>
Co-authored-by: Abhay Padamani <141122205+ktx-abhay@users.noreply.github.com>
2026-08-07 10:44:33 +00:00
Abhay Padamani 79fbd566b3 fix: ai page improvements (#13689) 2026-08-07 10:02:15 +00:00
ktx-vaidehi e420432f2c chore: bundle the SIL OFL 1.1 license for the vendored Geist fonts (#13691)
Geist and Geist Mono are vendored under web/src/styles/fonts/ and, like
the rest of web/dist, are compiled into the binary by rust-embed. Both
are licensed under the SIL Open Font License 1.1, which requires the
copyright notice and license text to be redistributed with the fonts.
Neither was present.

- OFL.txt, copied verbatim from vercel/geist-font, next to the .woff2
files.
- A font-license Vite plugin copying it to dist/fonts/OFL.txt, since
Vite only emits assets something imports and the license has to reach
the binary too.
- README.md recording family, version, upstream and how to re-vendor.

DejaVu Sans (src/core/src/alerts/notifications/chart/font/) already
ships its license and needs no change.

Also drops the fonts.googleapis.com / fonts.gstatic.com allowances from
the serve.json CSP and its commented twin in vite.config.ts — dead since
9e9c8c713d moved the fonts local.
2026-08-07 09:57:16 +00:00
Bhargavandktx-akshay 93cfbee117 fix: Issue ent 2343 alert improvements v3 (#13670)
Co-authored-by: ktx-akshay <akshay.patil@kiara.tech>
2026-08-07 08:00:16 +00:00
Hengfei Yang d18f21e51f fix: update enterprise feature (#13679) 2026-08-07 06:03:14 +00:00
Abhay Padamaniandktx-akshay 739054c7bd feat: metric chart improvement (#13546)
Design At: designs/dashboards/metric-chart-override-support/


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

2. sparkline for metric chart.

---------

Co-authored-by: ktx-akshay <akshay.patil@kiara.tech>
2026-08-07 05:09:40 +00:00
Ashish Kolhe bafc2be4f0 fix(correlation): P0/P1 fixes from service-streams correlation review (#13668)
## Summary

OSS half of the correlation P0/P1 fix set from the line-by-line review
(`docs/ak_review/correlation-line-by-line-review-2026-08-06.md`).
Companion enterprise PR: same branch name in o2-enterprise (required for
CI manifest pairing).

### Fixes (by review finding)

| Finding | Fix |
|---|---|
| **F14** | `StreamInfo.dropped_dimensions` contract field
(`#[serde(default, skip_serializing_if)]`, backward-compatible wire
format) + TS type |
| **F8** | Shared coverage-based identity-set ranking
(`resolve_best_set_by_coverage`) in `config::meta::correlation` — full
coverage > ratio > count, declaration-order ties; ingest and read now
rank identically |
| **F10** | `save_identity_config` purges rows of removed/changed sets
(`obsolete_set_ids`) + cache clear + org reload event |
| **F6** | `_reset` routes through enterprise
`ServiceStorage::delete_all` (cache clear + super-cluster replication)
and emits an org-scope reload; super-cluster Delete consumer emits org
reload instead of per-service delete |
| **F19** | `infra::table::service_streams::put`/`delete_subset_orphans`
return deleted orphan disambiguations so callers can evict cache keys |
| **F2** | `config::utils::sql::validate_where_fragment` (sqlparser)
applied to the `filter` param at both `traces/latest*` interpolation
sites — statement smuggling and comment-truncation payloads → 400 |
| **F2/F38** | FE: shared
`quoteSqlIdentifier`/`quoteSqlLiteral`/`buildSqlCondition` adopted by
all six SQL builders (two trace paths were fully unescaped) |
| **F35** | `applyFilterOverlay`: filter edits resolve per-stream via
semantic groups instead of literal key identity; dead
`removeFilter`/`resetFilters` removed; `semanticGroups` threaded to
`CorrelatedLogsTable` parents |
| **F36** | `applyDimensionEditsToFilters`: dimension-bar edits work in
both key spaces (raw-field and semantic-ID), case-insensitive shared
field→group map |
| **F31** | `mergeSubjectOverrides`: subject overrides replace the
same-group key (never union two aliases into contradictory WHERE),
schema-less field guessing removed |
| F14 (FE) | Warning banner shipped, then removed by reviewer decision
(`dropped_dimensions` stays in the API) |

### Verification

- `cargo test -p config` correlation/service_streams/sql: 121 tests
green; enterprise-feature and OSS-only cfg builds both compile
- FE: 263 vitest tests green across the correlation suites; vue-tsc
clean
- **Live-verified** against a local build (fresh DB): 19/19 API checks
(raw-value emission, set filing, obsolete-set purge, reset cache
invalidation, orphan eviction, filter validation) plus browser checks
(escaped SQL on the wire: `"environment" = 'Prod-EU'`)

Deliberately out of scope (per review decision): F15, F17/F37, F9.
2026-08-06 11:49:39 +00:00
Omkar KesarkhaneandClaude d171d98873 fix(synthetics): recorder extension install link, record/replay setup gate, configurable store URL (#13658)
## Summary

The OpenObserve Recorder Chrome extension is now published on the Chrome
Web Store. This PR wires the browser-test UI to it:

- **Install link + setup checklist** — the CreateBrowserTest gate links
to the Web Store listing and walks through install → Allow-in-Incognito
→ connect, with the connect step verified live via the extension probe.
The checklist is extracted into a shared `ExtensionSetupChecklist`
component.
- **Record/replay setup dialog** — `BrowserJourney.vue` now gates both
Record and Replay behind an `ExtensionSetupDialog` when the extension is
not detected (previously Replay was ungated and would hang in "running"
until the 15-min watchdog).
- **Loader on Record journey** — the gate's Record button shows a
spinner while the extension probe awaits its ~4.5s worst-case timeout.
- **Configurable store URL** — the Web Store URL is served by `/config`
as `synthetics_recorder_extension_url`, sourced from the new env var
`O2_SYNTHETICS_RECORDER_EXTENSION_URL`. The frontend falls back to the
built-in constant when the field is absent or empty, so the UI works
against older backends and OSS builds.

## Test plan

- `ExtensionSetupChecklist.spec.ts` (8), `ExtensionSetupDialog.spec.ts`
(11), `BrowserJourney.spec.ts` dialog gating tests — all passing
- `npm run lint` / `npm run type-check` clean (3 pre-existing
`eslint.config.js` duplicate-key errors untouched)
- Manual: record + replay gating verified against the published
extension

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-06 11:46:28 +00:00
Bhargav 1fd0bc5a32 fix: Fix/traces view logs url restore (#13662)
Two reports, same visible failure — clicking a "View Logs" affordance in
Traces either did nothing at all, or landed on the Logs page with no
stream selected, no filter, and the previous session's results still
showing:

Span-level "View Logs" in the trace-detail sidebar (enterprise).
"View Related Logs" on a service-graph node.
**Root cause**
Both trace back to one contract on the Logs side, hit by two different
callers.

The shared contract. restoreUrlQueryParams bails out entirely when the
stream param is absent
([useLogs.ts:343](vscode-webview://1natcp3amfqrfqrsn6p0sf7iek84lktqhb8p7td2pk978hr7cg9s/web/src/composables/useLogs.ts#L343)):


if (!queryParams.stream && queryParams.sql_mode !== "true") { return; }
That returns before applying the query, the time range and the stream —
so a missing stream doesn't degrade the navigation, it discards all of
it. Two further gates decide whether that function runs at all:
setupLogsTab() only restores when logs.isInitialized is false, and on a
keep-alive repeat visit handleActivation() restores only on the type ===
"trace_explorer" branch.

Trigger A — span "View Logs" (commit a7cbf9cfdd). viewSpanLogs read
correlationProps.value without awaiting loadCorrelation(). Correlation
is loaded lazily, so on a fast click that value was still null, and
navigateToCorrelatedLogs(null) threw on correlationProps.logStreams —
the button appeared dead. When the lookup did resolve but matched
nothing, logStreams was [], so the stream param came out empty and the
Logs page discarded everything per the contract above. The tree-view
handler had the same gap with a different failure mode: if
(correlationData) returned silently.

Trigger B — service graph "View Related Logs" (commit 1b02f512a1). The
stream was pushed under the wrong key — stream_value instead of stream —
so the early return fired every time. Two restore-path gaps compounded
it: isInitialized was left set (and isRouteChanged() clears it only on a
stream or org change, not a filter change), so setupLogsTab() skipped
the restore and re-showed the stored session; and the URL didn't carry
type=trace_explorer, so the keep-alive onActivated path had no branch
that would read it.

**Fix applied**
a7cbf9cfdd — fix(traces): warn when no correlated logs found

viewSpanLogs now awaits loadCorrelation() before reading the result.
Navigation is gated on correlationProps?.logStreams?.length — an empty
correlation result no longer navigates to an empty Logs page.
When there's nothing to open, the user gets a toast instead of silence.
A new correlationFailed ref separates "the lookup failed" (surface its
own reason) from "the lookup succeeded and matched nothing"
(traces.noCorrelatedLogsFound) — needed because correlationError is set
in both cases.
handleTreeViewCorrelatedLogs gets the same non-empty check and toast,
replacing the silent return.
Two sidebar specs that asserted the old navigateToLogs fallback now
assert the toast.
1b02f512a1 — fix(traces): restore logs URL params from service graph

stream_value → stream, so the restore no longer early-returns.
Added type: "trace_explorer" so the keep-alive onActivated path restores
on repeat visits.
Explicit store.dispatch("logs/setIsInitialized", false) so the URL is
the source of truth rather than the stored session.
Spec coverage for both paths in ServiceGraphNodeSidePanel.spec.ts.
2026-08-06 10:30:32 +00:00
Ashish Kolhe 32d02d6781 chore(traces): make Service Graph and Services their own routes (#13659)
## What

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

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

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

## Why this wasn't just cosmetic

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

## UI

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

## Handoff

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

## Two bugs this surfaced

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

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

## Tests

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

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

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

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

## Known / follow-up

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

## Bug

On an **existing custom (raw-payload) template** — which is every
template saved before templates v2 — the two editor-mode tabs share the
single form `body` field, and `onModeChange` overwrote it destructively:

1. Open existing template → `contentSpec` initializes to
`emptyContentSpec()`.
2. Click **Template** tab → `body` is overwritten with the serialized
*empty* spec → tab renders blank, original payload destroyed in form
state.
3. Click **Raw payload** back → nothing restores it → the editor shows
empty-ContentSpec JSON ("raw payload resets").
4. Worse: hitting **Save** on step 2 persisted the empty spec over the
stored template — silent data loss.

The banner path ("Start a content version") had the same latent
overwrite via the `contentSpec` watcher.

## Fix

- Stash the raw body when leaving custom mode; restore it (including
unsaved raw-mode edits) when returning. The stash takes priority over
the new-template starter payload.
- When an existing custom template enters content mode with a
still-empty spec, prefill the spec body from the payload's detected
`{vars}` (same logic as `startContentVersion`) so the Template tab never
opens blank.
- `startContentVersion` stashes too.
- `applyTemplateData` clears the stash so the kept-alive component never
restores a previous template's payload.

## Tests

4 new regression tests (TDD — all watched failing first with the exact
reported symptoms):
- Template → Raw payload round trip restores the original payload
- unsaved raw-mode edits survive the round trip
- Template tab prefills from detected `{vars}` instead of opening blank
- "Start a content version" → Raw payload restores the original payload
too

`AddTemplate.spec.ts` 42/42 green; neighboring template specs (151
tests) green; ESLint, `vue-tsc`, Prettier clean.
2026-08-06 10:17:51 +00:00
Ashish Kolhe 58c7dafa55 fix: notification templates v2 — rich content templates + alert chart images (#13640)
## Summary

Two coupled alerting features:

**1. Notification Templates v2 ("content" templates)**
- New `kind: content` template type: a structured spec (title, body
markdown, fields, links, rows, per-channel title overrides,
severity-filtered visibility) rendered natively per channel — existing
`custom` (raw body) templates are untouched and byte-identical on the
send path.
- Per-channel renderers: Slack Block Kit, Teams (Adaptive Card +
MessageCard), Discord embeds, PagerDuty Events v2, Opsgenie, ServiceNow,
SNS, HTML/text email, and a stable machine `webhook` envelope.
- Escaping is renderer-owned (mrkdwn entities, HTML encoding, markdown
stripping) with a shared URL-scheme allowlist; golden-fixture tests per
channel.
- Template editor UI: guided content form (fields/links row tables,
compact rows config, channel title overrides), live preview panel with
per-channel preview + payload tab, test-send (rate-limited,
`[TEST]`-marked).
- Migration `m20260802_000001_add_template_kind`; prebuilt default
template + org backfill.

**2. Alert chart images (stateless, storage-free)**
- Alerts can attach a metric-history chart PNG rendered from the
triggers self-reporting stream (`actual_value` per evaluation), with
warn/crit threshold lines and the firing point appended.
- Zero storage: the chart URL *is* the data — deflate+base64url payload
signed with truncated HMAC-SHA256 (org-bound, expiry embedded), rendered
on fetch by the HTTP-terminating node (`GET
/api/v2/{org}/alerts/charts/render`). Signature verified before inflate
(decompression-bomb guard), uniform 404s, per-node render semaphore +
per-minute query budget + small LRU.
- Renderer: plotters + embedded DejaVu Sans (Bitstream Vera license
included), gap-broken line segments so sparse evaluation history doesn't
draw false plateaus.
- Channel delivery: Slack image block, email inline CID (no remote
fetch), Discord multipart attachment, Teams/PagerDuty image URLs;
per-destination opt-in via template chart flag.
- Slack layout live-A/B'd on a real channel: content + buttons/chart
split across two same-colored attachments so the severity stripe spans
everything while staying under Slack's "Show more" fold; clickable link
title; "View in OpenObserve" keeps a reserved primary-button slot.
- Slack image-delivery fallback: Slack validates image URLs server-side
and rejects the ENTIRE message with `400 invalid_attachments` when its
proxy can't fetch the chart (VPN-only/private `ZO_WEB_URL`) —
live-verified. On that rejection the send strips the image blocks and
resends once (alert always delivers), sets a process-wide flag so later
sends pre-strip (reachability is deployment-wide, not per-destination),
and re-probes hourly. Zero config; observable via `slack_image_rejected`
/ `slack_image_pre_stripped` metric events.

## Testing
- 147 notification unit tests (resolve/render/preview/test-send incl.
per-channel goldens and hostile-input/injection coverage), 8 chart
payload tests, renderer PNG tests.
- Live-verified end to end: Slack (Slackbot chart fetches ~100ms,
cache-immutable semantics), SES email inline image, multi-severity
firing scenario (warning→critical) with correct threshold lines.
- FE: AddTemplate/ContentTemplateForm/preview specs; UX audit pass with
fixes.
- `cargo clippy -D warnings` clean in both OSS and enterprise
arrangements.

## Enterprise pairing
Must merge together with openobserve/o2-enterprise#2353 (workspace
twin-manifest mirror for the new OSS deps).
2026-08-06 04:47:02 +00:00
Shrinath Rao c9f68754df fix(logstream): show local timezone in stream detail time range (#13652)
## Problem

The **Stream Detail** header shows the stream's document time range with
a timezone badge that is **hardcoded to \`UTC\`** (i18n key
\`logStream.utc\`), while the timestamps next to it are rendered by
date-fns \`format()\` in the **browser's local timezone**. So the badge
and the value disagree for any non-UTC user — the range reads as local
time but is mislabeled "UTC".

## Fix

- Add \`formatTimestampInTimezone(us, format, timezone)\` in
\`web/src/utils/date.ts\` — same as \`formatTimestamp\` but renders in a
given IANA zone (via \`date-fns-tz\` \`formatInTimeZone\`, already a
dependency), matching the pattern already used across dashboards.
- In \`schema.vue\`, add a \`displayTimezone\` computed =
\`store.state.timezone || <browser zone>\` (never a hardcoded "UTC"),
render it in the badge, and format the \`doc_time_min\`/\`doc_time_max\`
range in that same timezone.

Now the badge label and the displayed range always use the same timezone
— the user's selected timezone, falling back to their browser zone.

## Scope note

The separate **Extended Retention** date picker further down still uses
UTC intentionally (retention day-boundaries are computed in UTC on the
backend), so its \`(UTC Timezone)\` label is left unchanged.

## Validation

Verified \`formatInTimeZone\` behavior against the installed
\`date-fns-tz@3.2.0\`:

| timezone | rendered value |
|---|---|
| UTC | \`2025-08-03T20:03:22:99\` |
| Asia/Kolkata | \`2025-08-04T01:33:22:99\` |
| America/New_York | \`2025-08-03T16:03:22:99\` |

Label and value now agree in every case.
2026-08-06 04:26:06 +00:00
Shrinath Rao 13acdd61da fix(web): hide LLM Model Pricing settings on OSS builds (#13651)
## What

Hide **LLM Model Pricing** on OSS builds and make it an
enterprise/cloud-only feature, end to end (UI + e2e coverage). Paired
with o2-enterprise PR on branch `fix/hide-llm-model-pricing-oss`.

## Why

Model Pricing was surfaced on OSS (settings rail + navbar flyout), gated
only on the `model_pricing_enabled` backend flag with **no**
enterprise/cloud check — unlike its sibling AI feature **LLM Providers**
(`(isEnterprise || isCloud) && online_evals_enabled`). Product decision:
it should not appear on OSS. This brings it in line with that sibling.

## Changes

**Web (hide on OSS)**
- `web/src/components/settings/index.vue` — settings rail item now
`visible: (isEnt || isCloud) && !!z.model_pricing_enabled`.
- `web/src/lib/core/Navbar/navGroups.ts` — `modelPricing` gate predicate
now requires enterprise/cloud (mirrors `llmProviders`), so the
collapsed-navbar flyout matches the rail.
- `web/src/composables/shared/useManagementRoutes.ts` — `model_pricing`
+ `model_pricing/edit` routes registered only inside the `isEnterprise
|| isCloud` block (defense-in-depth; matches `llm_providers`), so the
route isn't reachable by direct URL on OSS.

**E2E (move coverage to ENT)**
- `tests/ui-testing/ci-matrix/ci_matrix.json` — drop
`model-pricing.spec.js` from the OSS `GeneralTests` shard. It's added
back on ENT via the enterprise overlay (`ci_matrix.ent.json`, in the
paired ENT PR), so the merged ENT matrix runs it exactly once (verified
with `build-ci-matrix.js`).
- `.github/workflows/playwright.yml` — remove `ZO_MODEL_PRICING_ENABLED:
true` (OSS no longer exercises the feature; ENT `playwright.yml` keeps
it).
-
`tests/ui-testing/playwright-tests/GeneralTests/settings-form-validation.spec.js`
— gate the *ModelPricingEditor* describe block to a clean SKIP on OSS,
mirroring the existing Domain Management / Org Storage / Correlation
blocks.
- `tests/ui-testing/pages/generalPages/settingsFormValidationPage.js` —
`navigateToModelPricing()` now returns a boolean so the suite can detect
the OSS build and skip (mirrors `navigateToDomainManagement()`).

## Not changed (deliberately)

The backend `ZO_MODEL_PRICING_ENABLED` default (`true`,
[config.rs](src/config/src/config.rs)) is **left as-is**. It lives in
the shared config crate used by both editions, so flipping it to `false`
would also regress real ENT/Cloud deployments (feature off by default).
The UI gate already ensures the page never shows on OSS regardless of
the flag. Making the backend itself enterprise-only would be a separate,
backend-owned change.

## Behavior

| Build | Model Pricing UI | e2e |
|-------|------------------|-----|
| OSS | Hidden | `model-pricing.spec.js` not run;
settings-form-validation block SKIPPED |
| Enterprise / Cloud | Shown (if flag set) — unchanged |
`model-pricing.spec.js` runs (via ENT overlay) |
2026-08-06 01:54:37 +00:00
Abhay Padamani 7888f5c5fd feat: add hyperlink support for URL cells in (#13650)
Design At: hyperlink support for dashboard table cell if value is link
2026-08-05 12:57:48 +00:00
Bhargav bbf2aa37af feat: Issue ent 2289 mobile rum sdk error tracking v2 (#13645)
Design at: designs/tree/main/mobile-rum-sdk-evaluation/design-docs
2026-08-05 12:18:13 +00:00
Omkar Kesarkhane a24cd706af fix: Updated synthetics locations UI in configure location (#13475)
Redesigns the **check-locations picker** (`CheckLocations.vue`) used by
the browser-test and protocol-check create/edit forms, and tightens how
the two parent views feed it.

## Locations picker (`CheckLocations.vue`)

**Finding & choosing locations**

- Search box (matches label and region) with an explicit **Refresh**
button; the row stays mounted during loading (disabled input + spinner)
so a refresh never flickers the UI away.
- Location options render **two per row** (`grid-cols-2`, collapsing to
one column on narrow viewports), with public and private sections
labeled and explained via info-icon tooltips.
- Loading shows skeleton rows in place of the list.

**Private-location status at a glance**

- Each private location resolves to a status tier — **Ready / Connecting
/ Offline / Down** (down = no agent for ≥24h) — shown as an icon +
outline badge per row.
- Not-ready rows carry an always-visible guidance line (tooltip-only
messaging was unreachable on touch/keyboard) and a per-row **Add agent /
Install agent** action that opens the agent-setup drawer pre-scoped to
that location.
- Live agent count with first agent name inline; additional agents
collapse into a `+N` badge with a tooltip listing all names.
- Rows sort by resolved tier (ready → connecting → offline → down,
alphabetical within a tier), so a dead location never outranks one that
dropped minutes ago.

**Empty & no-match states**

- Search miss in one section → section keeps its header and shows "No
public/private locations match your search".
- Search miss in both sections → the list collapses to a single inline
empty state with a **New private location** action (omitted for browser
tests, which are Lambda-only).
- The private section's creation empty state (with CTA) appears only
when the org truly has no private locations — never as a filter
artifact. It remains reachable in a zero-locations org, since it is the
only way to bootstrap one.

## Parent views (`CreateBrowserTest`, `CreateProtocolCheck`)

- Agent-setup drawer is now **location-scoped**: `add-agent` passes the
clicked location's id/name through; `new-location` opens it unscoped.
Scope clears on close.
- No auto-refetch of locations on drawer close — the explicit Refresh
button covers it, so dismissing the drawer no longer triggers a
pointless fetch and skeleton flash.
- Unified `fetchLocations` error handling: silent when the endpoint 403s
(not available on this build), error toast on real failures. Previously
the browser view swallowed every failure and the protocol view toasted
every failure — including the expected 403.

## Cleanup

- Removed i18n keys orphaned by the rewrite (`privateBadge`,
`setupAgent`, `offlineWarning`, `liveAgentsNamed`) from all 15 locale
files, plus never-wired keys (`viewAgents`, `hideAgents`,
`lastAgentSeen`, `summaryNoneHealthy/Mixed/AllHealthy`) from en-US. The
synthetic-monitoring section's own `setupAgent` key is untouched.

## Testing

- `CheckLocations.spec.ts`: 48 tests covering search filtering,
per-section and combined no-match states, status tiers, guidance
visibility, agent display, tier-aware sorting (including a case where an
alphabetical tie-break would give the wrong order), loading, and empty
states. Also fixes a stub bug where `OButtonStub` declared `emits:
["click"]` and silently swallowed every click, letting a hollow test
pass.
- Both view specs cover the 403-silent / toast-on-failure split.
- `npm run lint` and `npm run type-check` clean on all touched files.
2026-08-05 11:58:08 +00:00