mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 16:09:05 +00:00
3fda083f51d06b5ccf7520b5a5299c331dd84c2b
3003
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 |
||
|
|
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> |
||
|
|
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. |
||
|
|
0ed5336bf1 | chore: adding HA mode for o2-ai agent (#13525) | ||
|
|
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
|
||
|
|
d1f8941512 | test: stabilize test cases for the Alpha environment (#13551) | ||
|
|
e4503f7fcb | fix: Fix/match all pipe query corruption (#13764) | ||
|
|
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> |
||
|
|
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) |
||
|
|
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> |
||
|
|
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). |
||
|
|
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`)
|
||
|
|
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. |
||
|
|
883fc97046 |
fix(dashboard): deduplicate panel and layout IDs on import and dashboard load (#13760)
https://github.com/openobserve/openobserve/issues/13104 |
||
|
|
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" /> |
||
|
|
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> |
||
|
|
43b531d996 | fix: build warning v2 (#13729) | ||
|
|
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 ✅ |
||
|
|
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. |
||
|
|
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)
|
||
|
|
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. |
||
|
|
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. |
||
|
|
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> |
||
|
|
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 |
||
|
|
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. |
||
|
|
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 |
||
|
|
4d23d85fcb | fix(i18n): improve Simplified Chinese translations (#13707) | ||
|
|
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'`. |
||
|
|
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 |
||
|
|
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> |
||
|
|
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. |
||
|
|
6c43e01823 | fix: px to rem (#13541) | ||
|
|
eb2ab67f5a | fix: dashboard table column formatting via column header (#13695) | ||
|
|
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> |
||
|
|
79fbd566b3 | fix: ai page improvements (#13689) | ||
|
|
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
|
||
|
|
93cfbee117 |
fix: Issue ent 2343 alert improvements v3 (#13670)
Co-authored-by: ktx-akshay <akshay.patil@kiara.tech> |
||
|
|
d18f21e51f | fix: update enterprise feature (#13679) | ||
|
|
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> |
||
|
|
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. |
||
|
|
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> |
||
|
|
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
|
||
|
|
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. |
||
|
|
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. |
||
|
|
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).
|
||
|
|
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. |
||
|
|
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) | |
||
|
|
7888f5c5fd |
feat: add hyperlink support for URL cells in (#13650)
Design At: hyperlink support for dashboard table cell if value is link |
||
|
|
bbf2aa37af |
feat: Issue ent 2289 mobile rum sdk error tracking v2 (#13645)
Design at: designs/tree/main/mobile-rum-sdk-evaluation/design-docs |
||
|
|
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. |