Commit Graph
95 Commits
Author SHA1 Message Date
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
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 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 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
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
ktx-vaidehiandKirtan Thakkar 3285a31e6f fix: i18n lint enforcement (#13439)
Co-authored-by: Kirtan Thakkar <kirtan@kiara.tech>
2026-08-05 10:16:00 +00:00
ktx-vaidehi 6e870594f4 fix: logs page freezes when wrap is enabled (#13638)
Every virtual row called the virtualizer's global measure() from
onMounted whenever row heights could vary, which includes wrap mode.
measure() throws away the entire itemSizeCache, so with wrap on each
mounting row wiped all measured heights, the rows fell back to the
estimate, the ResizeObserver reported the real (taller) heights, offsets
shifted, more rows mounted, and the cycle repeated — "Maximum recursive
updates exceeded in <OTable>" plus ResizeObserver loops until the tab
locked up.

Fixed-height mode converges instantly (the re-measure returns the same
constant), which is why only wrap froze.

Skip the global measure() in dynamic-row-height mode: measureRowElement
already hands each <tr> to the virtualizer, which measures and observes
it individually. The expanded-row path (wrap off) is unchanged.

Measured on a 1000-row table with the logs table's props, scrolling one
viewport with wrap on: 1 rAF frame in 3s and a 4260ms worst frame gap
before, 179 frames and 51ms after — matching wrap-off (180 frames /
42ms).
2026-08-05 09:20:15 +00:00
ktx-vaidehi 5525fa48f1 fix(alerts): bring External Alert Sources page up to the listing standard (#13625)
## What

Brings the **External Alert Sources** page in line with the house
listing-page standard (`ui-architect` skill + `page-recipes`), following
the patterns already used by `SyntheticsTokens.vue` and
`AlertsDestinationList.vue`.

### List page — `ExternalAlertSourcesList.vue`

- **Layout**: `OPageLayout scroll pad-y` → `bleed` + the full-height
flush chain, so the table scrolls internally instead of the whole page.
- **Toolbar** (all three were missing): search box in `#toolbar`,
refresh moved from the page header into `#toolbar-trailing`, and the
column show/hide toggle (via `persist-columns` + `table-id` + `hideable`
columns).
- **Empty state**: the table was hidden entirely behind
`v-if="defaultSource"`, so a fresh org saw a blank page. It now always
renders, with one `OEmptyState` (new `no-alert-sources` preset) that
swaps to "no results / clear filters" via `:filtered`.
- `:loading` bound (no skeleton before), footer row count via
`:footer-title`, and `n` / `r` / `/` shortcuts registered + bound.
- **Name column resize**: it used `meta.autoWidth`, which `useTableCore`
marks `enableResizing: false`, so the Name│Status boundary had no drag
handle. Switched to `meta.flex` (same recipe as the synthetics
`MonitorTable`) — still absorbs leftover width, but resizable.
- Removed a hardcoded `⚠` glyph from the no-destination tag.

### Add/Edit drawer — `AddExternalAlertSource.vue`

- Converted the hand-rolled `form` object + disabled-Save validation to
`OForm` + a colocated zod schema, with fields addressed by `name=` only
and wired to the drawer footer via `form-id`. Save is no longer disabled
on invalid and the manual `saving` flag is gone.
- `form-id` is dropped once the source exists, so the "Done" button
closes instead of resubmitting.
- Select width moved to a wrapper (`OSelect` self-applies `w-full`) and
its hand-rolled `<p>` label became the `label` prop.

## Testing

- `ExternalAlertSourcesList.spec.ts` — 38 pass (8 new: search filtering,
empty state, clear-filters, footer count, resize handles). Binding
`:loading` made DOM assertions hit OTable's minimum-hold skeleton, so
they now go through a `mountAndSettle()` helper.
- `AddExternalAlertSource.spec.ts` — 22 pass, rewritten off `vm.form`
onto the validated-values API, plus two tests that submit the real
`<form>` end-to-end.
- Full `src/components/alerts/` suite green (1997 tests). `npm run
type-check`, ESLint and `lint:design:strict` all clean.
2026-08-04 13:31:30 +00:00
ktx-vaidehi 601ef75e2e fix: IAM page UI changes (#13618) 2026-08-04 12:43:36 +00:00
Ashish Kolhe b31ff6c7fe chore: incident destinations, source rename, and Alert Sources UX redesign (#13612)
## Summary
- Adds a typed `destinations` field to incident_integrations (migration
+ entity + table ops), threaded through create/list/event-ingestion.
- Adds a PATCH endpoint to rename an alert source, including the org's
`default` source (rename allowed; delete stays blocked for it).
- Moves Alert Sources under the Reliability nav menu.
- Redesigns the Alert Sources list and Add/Edit drawer:
- Table-only landing page; Add Source opens a drawer instead of a route.
- Single row-level Edit action (name + destinations together), matching
the Destinations/Templates list pattern instead of per-field popovers.
- Fixed-layout, horizontally scrollable table with a pinned Actions
column; wrapped/truncated cells confined to their own columns.
  - Separate "copy token" action distinct from "copy URL".
- "Never resolves" warning moved from an unbounded banner list into a
tooltip on the status tag/icon.
- Removed the confusing "Shared token" badge and reworded the
no-senders-yet caption.
- All 3 setup steps shown together in the drawer (checklist-style)
instead of hiding steps 2-3 behind a post-save view.
- Destination creation now routes to the real Destinations page instead
of a fake local-only option.
- Fixed the test curl snippet: no stray `#` comment in the copyable
text, and a payload the backend's generic-format detector actually
accepts.

## Test plan
- `cargo build`/`clippy -D warnings` clean on `infra`,
`openobserve-api-management`, `openobserve-api-http` (enterprise
feature) and OSS-only workspace check.
- 151 frontend vitest tests passing (stable across repeated runs).
- Manually verified against a local enterprise build + Vite dev server.
2026-08-04 05:16:32 +00:00
Prabhat Sharma 0cfa518433 feat(slos): SLO measurement UI — trend charts, previews, and slice defaults (#13579)
## What this is

The SLO measurement UI: the Reliability nav entry and SLO routes, the
list and
detail pages, the create/edit form, and the trend charts that make an
error
budget readable.

## The charts

The detail page draws **burndown** and **burn rate** side by side. They
read the
same slices and differ only in the window they aggregate over, which is
what
makes the pair legible: burn spikes above ×1, budget bends downward.

Each panel carries a **second y-axis** that relabels the first through a
fixed
affine map — a burn multiple *is* an SLI, a share of budget *is* a
number of
errors. This is not the dual axis that makes crossing points an
artifact: there
is one series, and the right axis is the same axis in the unit people
speak in.
The SLO is written as "99.9%", and "we can afford 12 more errors" is a
sentence
an on-call engineer can act on.

That demands exact gridline alignment, so `chartScale.ts` pins the left
axis and
*derives* the right one. Two consequences worth knowing:

- **Precision follows the axis step, not the unit.** A four-nines target
leaves a
0.01-wide budget, so its SLI axis moves in thousandths — two decimals
would
  print every gridline as "100.00%".
- **The burndown's top is pinned at 100**, not rounded up to a multiple
of its
step. A window 1,500% overspent takes a 500-point step, and rounding the
top
squashed the whole healthy stretch of the line into the top fifth of the
panel.

An info tooltip on each panel gives the plain reading, the formula, and
how to
read the threshold line. The formulas mirror `toBurndownSeries` (and so
`config::meta::slo::math`) exactly — two statements of "burn rate" that
disagree
by a rounding convention is a support ticket.

## Previews in the form

A count SLI is a **predicate** and the doubt is whether it parses; a
time-slice
SLI is a **number** someone has to guess (232 ms) and nothing told them
whether
that makes 1% or 40% of slices bad. Each now gets the preview that
answers its
own question, and the time-slice one puts the verdict in the header —
"83.3%
good — 5/6 slices" — beside the target just typed.

The threshold never reaches SQL, mirroring the ingest pass, which
aggregates
first and classifies after so a threshold edit re-reads stored slices
instead of
contradicting them. Dragging it therefore rescores instantly with no
query.

The aggregate field moves from a bare input to the same expression
editor scope
and good-when already use — it names a column, and a mistyped column is
invisible until the ingest query fails.

## Defaults

**5-minute slices are now the form default.** For a count SLI the slice
width does
not touch the arithmetic — the window SLI is Σgood/Σtotal and
repartitioning the
same events changes neither sum — so 60s returned bit-identical numbers
for five
times the stored rows (139,680 per series at the 97-day horizon against
27,936).
60s stays available because for a time-slice SLI the width *is* the
definition:
at 99.9% over 7 days a single bad 5-minute slice spends about half the
budget.
Backend validation is untouched; both widths remain legal.

## Verification

Beyond unit tests, the charts were rendered against a live instance,
which caught
three defects the tests could not:

- The burndown's second axis counted **seconds** for time-slice SLOs
while
labelling them slices — off by 300× (2,340 shown where the honest answer
is
  7.5). Now converted via a tested `budgetUnitsFor`.
- `histogram()` returns `slice_start` as an ISO string without a zone
marker.
`Number()` gave NaN (empty panel forever); parsing as local time would
have
  shifted the series by the viewer's offset.
- The preview's result set is not the range — a slot with no rows
produces no row
at all, so the tally read over an undisclosed sample (4 of 12 slots on a
7.3%-coverage SLO). The gap count is now stated, and the pass/fail
colour is
withheld below ten measured slices rather than calling 66.7% a failure
on the
  strength of three.

`npm run lint`, `vue-tsc`, `lint:design:strict` and the `utils/slos`
suites all pass.

## Known follow-up, deliberately not in here

SQL function autocomplete renders its whole call as the label, uses the
"abc"
(Text) icon, and inserts a literal `'undefined'` for any function taking
more than
one argument — the provider passes one argument to a label builder that
takes
several. It is app-wide (`useSuggestions.ts` + `CodeQueryEditor.vue`),
not
SLO-local, so it wants its own change.
2026-08-02 16:06:05 +00:00
Prabhat Sharma d99ecff3e8 feat(nav): add Reliability menu, restore deferred SLO routes (#13577)
Groups the alerting surface under one left-rail tile, and un-defers the
SLO backend that shipped disabled.

> **Lands with** openobserve/o2-enterprise#2321, which adds
`ROUTE_PERMISSIONS` for the restored SLO endpoints — the route-coverage
test pairs the two, so this one alone fails it.

## Reliability menu

New `reliability` nav group absorbing **Alerts, SLOs, Incidents**, plus
**Notification Destinations** and **Templates**, which move out of
Settings — they are alerting configuration, not deployment
configuration. Settings keeps Pipeline Destinations (that one really is
pipeline config); its group is renamed "Destinations".

Their routes are **top-level and flat** (`/alert-destinations`,
`/alert-templates`) rather than nested under `/alerts`. They are
siblings of Alerts, not sub-pages of it — nesting them made the URL
claim otherwise and the rail believed it, highlighting Alerts alongside
them.

- Route **names** are unchanged, so the ~15 call sites that navigate by
name are untouched.
- Old `/settings/*` paths redirect, with the query preserved —
`?action=import` deep links keep working.

## SLO routes

`/api/{org}/slos` answered **404**. The routes were deliberately
unregistered before the last release (`6977b4020d`, `ac78a622e1`) while
the rest of the feature — `core/src/slo/*`, the infra tables, both
migrations, the `slo_maintenance` job, all seven handlers — stayed in
the tree. This restores the five registrations and the OpenAPI entries,
recovered from the pre-strip commit rather than rewritten.

`ZO_SLO_ENABLED` still **defaults to false**. It is now published as
`slo_enabled` on `/config`, so the menu entry follows the flag instead
of offering a page the API answers with 501.

## Nav active-state

Active state was decided per child, so any section whose path prefixed
the current route lit up alongside it. It is now resolved once per
flyout: exact route-name match wins, else the **deepest** path prefix —
so a genuine drill-down like `/alerts/detail/:id` is still attributed to
Alerts. `placeAfter` also accepts a group key, letting Data anchor on
the Reliability tile it follows rather than on an item that tile
absorbs.

## Tests

`tests/ui-testing` referenced `[data-test="alert-destinations-tab"]` /
`alert-templates-tab` in six files — those data-tests were on the
Settings rail items removed here, so the locators would have matched
nothing and hung until timeout. Those paths now go through the existing
`openNavFlyoutChild` helper, extended with the Reliability group. Two
URL regexes that would have silently mismatched on the hyphen are fixed
(one ended in `.catch(() => {})`, so it would have degraded to a silent
15s stall).

## Verification

- 3597 unit tests pass; `vue-tsc` and eslint clean; enterprise build
compiles.
- `playwright test --list`: all 141 tests across 23 files load, no
module errors.
- Against a live server: `GET /slos` → 200, `POST /slos/move` → 422 (so
the literal wins over the `{slo_id}` catch-all), `GET /slos/x` →
handler-level `{"code":404,"message":"SLO not found"}`.
- In the running app: `/alert-destinations`, `/alert-templates` and
`/alerts` each highlight exactly one row;
`/settings/alert_destinations?...&action=import` redirects with the
query intact; rail order unchanged.
2026-08-02 05:44:01 +00:00
Hengfei Yang 72f7bff378 perf: optimize AI session list queries (#13574)
## Summary

- Page sessions by `session_id` before computing the expensive summary
fields.
- Aggregate only the selected page of sessions with exact
`count(DISTINCT trace_id)`, removing trace ID materialization and
Rust-side per-trace rollups.
- Generate direct Agent predicates and evaluate them as aggregate
membership conditions without a session-membership subquery.
- Support efficient server pagination by fetching one extra session to
determine `has_more`, without counting every distinct session on every
page.

## Why

The previous endpoint first materialized `array_agg(DISTINCT trace_id)`
for every candidate session and then issued another query using those
trace IDs. A single-pass full rollup was also slower because it
aggregated every summary field for all matching sessions before applying
TopK.

This change keeps the inexpensive session page selection separate and
bounds the expensive rollup to the requested page size. Page selection
and display both use the latest `end_time` across the complete session;
Agent predicates only decide whether a session is a member of the result
set.

The endpoint previously returned `total = hits.len()`. With a 20-row
page, OTable therefore calculated one total page and permanently
disabled Next even when more sessions existed. An exact `count(DISTINCT
session_id)` query scanned the full data set and took about 1.18 seconds
by itself on this benchmark. Instead, the page query now requests `size
+ 1`: while another page exists, `total` is a documented lower bound and
the response includes `has_more=true` / `total_is_exact=false`. The
final page returns the exact total. OTable displays lower bounds with
`+` and hides Last-page navigation until the exact total is known.

## Performance

Measured locally against `bench_traces` (167 files, about 99.9 million
records), using five runs and ignoring the first two:

| Query shape | Mean wall time |
| --- | ---: |
| Original trace-ID flow | 6.829 s |
| Page session IDs + membership subquery | 3.219 s |
| Page session IDs + Last activity | 0.850 s |

The optimized query shape is about 87.6% faster than the original flow.
The second-phase rollup returned 20 rows with about 74 KB of
intermediate output and about 2.45 MB peak memory in the query plan.
Fetching the extra pagination sentinel did not introduce a meaningful
page-size-dependent regression in the final runtime sweep.

## Behavior note

The Agent filter controls membership through `HAVING max(CASE WHEN
<filter> THEN 1 ELSE 0 END) = 1`, while the final rollup includes all
spans for each selected session. Both pagination and display use the
complete session's `max(end_time)`, ordered by `session_last_activity
DESC, session_id DESC` for stable offset pagination. The UI labels this
value `Last activity`; session start and duration remain available from
the rollup.

Current clients send direct Agent predicates. For compatibility, the
endpoint also recognizes the previous narrow same-stream `session_id IN
(SELECT session_id ... GROUP BY session_id)` shape and safely extracts
its inner WHERE predicate; unrelated subqueries are left unchanged.

This assumes every relevant span has a `session_id`.

## Validation

- `cargo build --features mimalloc --profile release`
- `cargo test -p openobserve-api-search traces::session --lib` (18
passed)
- `cargo test -p openobserve-api-search user::tests --lib` (13 passed in
the earlier query-change validation)
- `cargo fmt --all`
- `git diff --check`
- targeted Vitest: Sessions composable, SessionsList, and OTable (169
passed)
- `vue-tsc --noEmit -p tsconfig.vitest.json --composite false`
- targeted ESLint and Prettier checks
- runtime pagination check against the supplied legacy-filter curl:
  - page 1: 20 hits, `total=21`, `has_more=true`, `total_is_exact=false`
  - page 2: 20 hits, `total=41`, `has_more=true`, `total_is_exact=false`
- final page: 3 hits, `total=184443`, `has_more=false`,
`total_is_exact=true`
- pages 1 and 2 returned 40 unique sessions in descending `end_time`
order across the page boundary
2026-08-01 08:35:55 +00:00
ktx-vaidehi 77d62ddee2 fix(logs): bound the last selected column to the viewport (#13573)
`body` — and any last selected field — sized itself to its longest value
instead of taking the leftover width, dragging the row into thousands of
pixels of horizontal scroll. Two separate causes:

- the absorber marked the column `meta.autoWidth`, which in OTable means
"no width at all", and a horizontalScroll table carries `min-w-max`,
which sizes every column to its max-content;
- sized columns in a scrolling table had no `max-width`, so as soon as
another field was added and `body` stopped being the absorber, its own
text stretched it to ~3000px.

Split the two behaviours the pre-migration table kept separate: plain
`autoWidth` stays content-sized (the default `source` column was `width:
auto` and scrolled), while the new `meta.fillRemaining` takes the
leftover width but stays inside the container and ellipsis-truncates
(pre-migration `flex: 1 1 auto; overflow: hidden`). A bounded filler
drops the table's `min-w-max` and pins its sized siblings at their own
size in both directions.

Opt-in, so the other horizontalScroll tables (traces, dashboard panels,
monitors, search history) keep their existing layout untouched.

Verified against the pre-migration build (044f561506) running side by
side on identical data: column widths and scroll offsets match exactly.
2026-07-31 14:12:12 +00:00
073c5c2680 feat(ui): calm-signal pass over Streams, Pipelines and Cluster Nodes (#13562)
Continues the "Calm Signal" colour language shipped on the Alerts list:
a calm neutral canvas, with saturated colour spent only on the one
signal each screen exists to surface. These three pages were picked from
a ranked audit of the remaining list screens.

## Streams — `web/src/views/LogStream.vue`

Signal: **ingestion liveness**.

- **Last Ingested** column — relative time from `stats.doc_time_max`,
with a dot on streams taking data within the hour.
- **Liveness row rail** — green (ingested today) / amber (was ingesting,
quiet for over a day) / grey (never ingested). Grey rather than amber
for "never": a brand-new or schema-only stream is legitimately empty, so
amber there would cry wolf on every fresh stream. No full-row wash — a
stream list has no outright failure state, and washing "quiet" or
"empty" would tint most rows in a normal org.
- **Compression** column from ingested/compressed size, coloured only
when it drops below 1x (the compressed copy is *larger* than the raw
data — compression is doing nothing for that stream).
- **Org footprint strip** in `OPageLayout`'s `#subnav`, deliberately
**outside** the table: the list is server-paginated and these totals
cover every stream type, so in the table's `#subheader` they would read
as a summary of the visible rows. Same figures, labels, icons and
formatters as the Home → Usage tiles.

**Bug fixed along the way:** `compressed_size` / `index_size` were
declared outside the row `map`, so a stream without `stats` inherited
the previous row's numbers. Rows now carry raw per-row values and render
`—` when a stat is absent, instead of a misleading `0 MB`.

## Pipelines — `web/src/components/pipeline/PipelinesList.vue`

Signal: **operational state**.

- **State** column (Errored / Paused / Active) built the same way as the
Alerts reference, plus a selectable state strip and a matching row rail.
Errored rows also show when they last failed — recency on the exception
only.
- The state facet is orthogonal to the existing type toggle group, so
the two never express the same filter.
- "Errored" is driven by `last_error`, which the backend expires via
`ZO_PIPELINE_ERROR_RETENTION_MINS` (default 60 min), so the state clears
itself and cannot get stuck.

## Cluster Nodes — `web/src/components/settings/Nodes.vue`

Signal: **health**.

- Health strip doubling as the status facet, **Status** chips, and a new
**Role** column via a `nodeRole` badge group (roles were filterable but
never displayed).
- Node health previously relied on `status-row` / `status-*` row classes
that **exist nowhere in the repo**, so status rendered no colour at all.
Replaced with a real rail plus an exception tint.
- CPU/memory bars now go amber at 70% and red at 85%, and a hardcoded
literal colour (`bg-[lightgrey]`) is gone.
- Replaced the fake `#` data column (a real, draggable, resizable column
whose numbers stopped matching the rows once sorted or filtered) with
`OTable`'s `show-index` gutter, and the positional `columns.splice(2,
1)` with a filter by column id.

## Shared

- `OStatCard`: the **value no longer truncates** — a clipped `499.51…`
is a useless number, so the label absorbs the squeeze instead.
`OStatStrip` wraps a tile earlier so a five-tile strip stops squeezing
on a laptop.
- `formatEventCount` moved out of `UsageTab.vue` into
`utils/formatters.ts`, so Home and Streams cannot disagree about the
same number (`2.9B` vs `2,900,000,000`).
- Streams list endpoint: `stream_comparator` gains `doc_time_max` and
`compression` sort arms, with unit tests.

## Note on the two new sorts

`Last Ingested` and `Compression` are intentionally **not sortable in
the UI yet**. The table is `sorting="server"`, and an unknown sort key
falls through to `_ => name` with no error — a sort control would
silently order by name. Sorting them client-side isn't possible either:
server pagination means the page holds 20 of N rows. The backend arms
ship in this PR; enabling the columns is flipping `sortable: false` →
`true` once a build carrying them is deployed (the column ids already
match the keys).

## Testing

- `npm run lint`, `lint:design:strict`, `lint:tokens`,
`lint:token-purity`, `lint:styles`, `type-check` — all pass.
- 285 unit tests across LogStream, PipelinesList, Nodes, UsageTab,
formatters and badgeGroups pass.
- The Rust arms and their tests have **not been compiled locally** (no
cargo toolchain on the dev machine) — first real compile is CI.
- Cluster Nodes needs an enterprise build **and** the meta org to be
reachable (Settings → Nodes is gated `isEnterprise && isMetaOrg`).

---------

Co-authored-by: ktx-vaidehi <vaidehi.akhani@kiara.tech>
Co-authored-by: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com>
2026-07-31 10:57:51 +00:00
Abhay Padamani 1112423ea8 fix: dashboard table chart width issue and color styling issue (#13567) 2026-07-31 10:40:03 +00:00
83d12fff04 feat(web): inline-rename page titles with auto-generated default names (#13520)
## What changed

Naming an alert, panel, workflow, pipeline or function meant inventing a
name up front in a form field before anything else could be configured.
Two pieces replace that.

**Inline rename** — the page title *is* the input. `OInlineEdit` /
`OFormInlineEdit` (`web/src/lib/forms/InlineEdit`): click to rename,
Enter or blur commits, Escape restores the pre-edit value. A `#trail`
slot carries the "Auto" badge in display mode only.

**Auto-generated names** — `utils/autoName.ts` builds the name,
`composables/useAutoName.ts` decides when it's allowed to win. A name is
AUTO until the user types, then it's theirs for good; clearing the field
and blurring re-arms auto. The generators describe only what is actually
configured, so a half-built panel yields a shorter name rather than a
speculative one, and an empty string when there is nothing to say.

- Panel names read as prose — `Avg of duration by service`, `Record
count`, `k8s_logs overview`. Both live panel-field shapes are handled
(builder `functionName` + `args`, and legacy flat `column` +
`aggregationFunction`); the time column is read from
`zoConfig.timestamp_column` rather than hardcoded. Hand-written queries
fall back to the stream, since naming a panel after `x_axis_1` is worse
than saying nothing.
- Alert names are identifiers — `anomaly_k8s_logs`,
`k8s_logs_status_gte_500` — built to survive the backend's
`RE_OFGA_UNSUPPORTED_NAME` rule, with operators spelled out and a
`slugify` backstop. A leading underscore is preserved so internal
streams (`_rumdata`) keep their names.

### Supporting library changes

| Component | Change |
| --- | --- |
| `OPageHeader` / `OPageLayout` | New `titleOverflow="visible"` — the
`<h1>`'s `truncate` clips an interactive title's focus ring and the
error message it floats below itself |
| `OSelect` | New `appearance="inline"` — borderless, no fixed height,
sized to content, for a value that reads as a word inside running text
("Add Alert **in KTX**") |

All new strings go through i18n (`common.inlineEdit.*`,
`dashboard.autoName.*`, `alerts.autoName.*`, per-module `renameHint`).
Interpolated identifiers — column names, SQL functions, stream names —
stay verbatim.

## Wired into

AddPanel, AddAlert (+ `useAlertForm`), WorkflowEditor, PipelineEditor,
Functions / FunctionsToolbar, IncidentDetailDrawer,
InlineSelectFolderDropdown.

## Tested

244 unit tests across the 5 affected suites, all passing — including new
specs for `OInlineEdit`, `useAutoName`, and `autoName`.

One pre-existing expectation in `autoName.spec.ts` was updated: it
asserted `Avg of duration, max of duration +1 more`, capitalising only
the first word of a multi-measure name. `measureLabel` capitalises per
measure by design, so the expectation now reads `Avg of duration, Max of
duration +1 more`.

---------

Co-authored-by: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com>
Co-authored-by: ktx-vaidehi <vaidehi.akhani@kiara.tech>
2026-07-31 08:50:19 +00:00
4772df1723 feat: per-group and per-series alerting, and SLO measurement (#13547)
Design at: alerts_2.md

Per-group and per-series alerting, SLO measurement, and the UI for both.

## Alerts

**Multi-alerts (Feature 3).** A grouped alert can opt in to evaluating
and paging **per group** rather than collapsing to one verdict. Opt-in
only (`multi_alert`), so no existing alert changes behaviour: the flag
cannot be present in JSON written before it existed. Per-group state,
transitions, silence fingerprints, disappearance/reaping and a
cardinality cap, with the count-gate rules that make "most severe group"
and the legacy collapsed level provably the same verdict.

**Per-series alerting for PromQL.** The same feature for the metrics
family, where a group is one returned **series** keyed by its full label
set — that is a series' identity in Prometheus, and the expression's own
`by (…)` clause is already where the label set is chosen, so a second
picker could only disagree with it. One additive nullable column; NULL
means off, so stored alerts are untouched.

**Alert detail page**, replacing the row-click side panel: stat strip,
evaluation chart with threshold marklines, per-group table and history.
Simple alerts get a real history too — evaluations (one record per run)
by default, level changes alongside.

## SLOs

Count / time-slice / alert SLIs, error-budget and burn-rate arithmetic,
rolling windows, per-group slices with an exact rollup, coverage gating,
backfill, and SLO-based alerts.

Two capabilities the model could not express, both added tests-first:

- **Metrics-native counting** (`CountSource::PromQl`) — counter metrics
have no rows for a predicate to classify, so "good" exists only as
arithmetic between series, and correct counter arithmetic is
`increase()`.
- **Freshness semantics** (`absent_is_bad`) — for a pipeline SLO,
silence IS the failure, which the default gap rule can never report. A
*failed* query still freezes rather than pages; only a query that PROVED
emptiness counts as bad.

**SLO form**: live good/bad preview so a wrong predicate is caught
before saving rather than days later, real query editors with field
autocomplete, folders shared with alerts, and a move endpoint.

## Notes for review

- The SLO and composite features are **deferred (TODO)**:
`ZO_SLO_ENABLED` stays default **false** and the SLOs menu entry is
hidden until they ship; the SLO backend and UI remain in the tree behind
the flag, and composites stay pure logic + tests.
- One migration (schema 61): a nullable column for the PromQL opt-in. No
backfill.
- Two fixes outside the feature that it surfaced: query-editor
completions were duplicated per mounted editor (Monaco registers
providers per *language*), and the alert detail chart had no PromQL
branch, so it charted row counts against the series-count gate.
- Lands together with the matching o2-enterprise PR — `QueryType::Slo`
is new here and the enterprise dedup matches are exhaustive over it.
- `ratelimit::test_add_batch_missing_rule_id` fails intermittently on
`main` as well; unrelated to this branch.

---------

Co-authored-by: Shrinath Rao <shnath@openobserve.ai>
Co-authored-by: hengfeiyang <hengfei.yang@gmail.com>
Co-authored-by: sai nikhil kethe <nikhil@openobserve.ai>
2026-07-31 07:23:22 +00:00
6fb5eecdc5 fix: table migration to same components (#13451)
## What changed

Every table in the app now renders through the shared `OTable`
(`web/src/lib/core/Table`). Both legacy implementations are deleted —
`components/TenstackTable.vue` and `plugins/logs/TenstackTable.vue` —
for ~7,000 lines removed and one table behaviour contract instead of
three.

To get there, `OTable` gained: per-cell hover actions, per-column value
filter, opt-in column close "x", sticky pivot total row + right-pinned
total columns, pivot row-field cell merge, caller-owned pagination bar,
delegated scroll (shared scroller with the logs/traces histogram), and
variable-height virtual rows.

## Tables migrated

| Area | Tables |
| --- | --- |
| Logs | Search results grid, correlated logs |
| Traces | Search results grid, trace details sidebar (attributes /
events / links), service graph node panel |
| Dashboards | Table panel + pivot (incl. PromQL), panel pagination
controls, import error panel |
| RUM | Session player traces tab |
| Pipelines | Scheduled pipeline SQL preview |

## Tested

775 unit tests across the 12 affected suites, all passing.

Manually verified per table:

- **Columns** — resize, reorder, close "x", persistence across reload;
horizontal scroll on wide grids; wrap toggle with virtual scroll; client
+ 3-state server sort
- **Pagination** — custom records-per-page, enabling pagination after
mount, scroll-to-top on page change, no duplicate pager
- **Logs** — FTS highlighting, row expand → JSON preview and its
actions, hover actions + AI button, cell font / row density / status
spine, histogram pinned while scrolling sideways
- **Dashboards** — pivot multi-level headers, cell merge, sticky totals
staying aligned under horizontal scroll, cell colouring (auto palette /
value mapping / conditional rules / overrides), CSV + JSON export
- **Traces, RUM, Pipelines** — events table expansion, error-row border,
row click → trace navigation, SQL preview copy + send-to-AI

---------

Co-authored-by: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com>
Co-authored-by: ktx-vaidehi <vaidehi.akhani@kiara.tech>
Co-authored-by: Shrinath Rao <shnath@openobserve.ai>
2026-07-30 12:18:23 +00:00
ktx-vaidehi c9fc8d3b55 fix: dashboard builder redesign (#13419) 2026-07-28 10:59:27 +00:00
Omkar Kesarkhane 640e0647c6 fix: group Synthetics and RUM under an Experience menu (#13474)
Collapse the two top-level RUM and Synthetics rail items into a single
Experience group tile, mirroring how Data groups Streams/Pipelines and
Dashboards groups Reports. Driven entirely by a new NAV_GROUPS entry in
navGroups.ts; when Synthetics is disabled the group auto-collapses and
RUM falls back to a plain link.
2026-07-27 07:23:34 +00:00
ktx-vaidehi 3200c202c7 fix: emptystate animation migration (#13440) 2026-07-27 05:21:41 +00:00
ktx-kirtan 17884df10f fix(ui): summary stat strips & Calm Signal polish (Alerts, Incidents, AI evals) (#13444)
Applies the **Calm Signal** colour language and summary stat strips
across the monitoring + AI-evaluation pages.

- Colourful summary stat strips (`OStatStrip`) with selectable filter
tiles + left row rails / exception washes on **Alerts, Incidents, Eval
Jobs, Scorers, Score Configs**.
- Tile tones match their `OTag` chip colours; critical-first (left)
ordering; re-click a tile to clear the filter.
- AI Observability: consistent section headers, detail drawer, and
per-session signals.
- Migrate `ScorerTypeDialog` to the standard `ODialog`; token-compliance
+ vertical separators on the eval form pages.
- Table perf: lazy tooltips, delegated row shortcuts, virtualization.
2026-07-24 09:13:08 +00:00
Abhay PadamaniandClaude Opus 4.8 5ec9b89ff8 build: FE code format check for improved maintainability (#13351)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 07:11:39 +00:00
ktx-vaidehiandClaude Opus 4.8 50d3fda602 fix: UI token change (#13315)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 15:58:24 +00:00
ktx-vaidehiandClaude Opus 4.8 8886a03b99 feat(dashboards): redesign panel config sections (#13371)
- Collapse all config sections by default (was expanded)
- Add open-state accent to OCollapsible sidebar variant: left accent
bar, tinted header, and accent-colored icon/chevron when expanded
- Move sidebar chevron to the right of the label (rotates on open)
- Give every config section a leading icon via SECTION_ICONS
- Add theme-aware collapsible open-state tokens (light + dark)

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:14:16 +00:00
Abhay Padamani a237306208 feat: copy paste support for date time picker (#13374)
Design At: ui/datetime-picker-copy-paste.md

<img width="399" height="561" alt="image"
src="https://github.com/user-attachments/assets/d4c11056-ffe2-451f-8d1b-ddbcf7c59336"
/>
2026-07-23 10:38:35 +00:00
Omkar KesarkhaneandClaude Opus 4.8 287f17f41c fix: In synthetics replaced chrome runtime with window post message (#13346)
## What changed

Replaces `chrome.runtime.*` direct messaging (which requires a static
`externally_connectable` manifest entry) with a `window.postMessage`
bridge transport for the synthetics recorder extension. This removes
`externally_connectable` from the extension manifest so the recorder
works with self-hosted OpenObserve instances on any domain. Also
includes fixes for recording tab lifecycle handling, replay proxy
deserialization, and tab removal edge cases.

## Why

The `externally_connectable.matches` field in the manifest is static —
it cannot include arbitrary self-hosted domains, so users hosting
OpenObserve on their own URLs couldn't use the extension recorder. The
bridge transport (content script + `window.postMessage`) decouples the
web app from the extension's manifest, letting any origin communicate
with the recorder.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 20:38:19 +00:00
Bhargav 960d092a8d fix: type check fix (#13382) 2026-07-22 15:28:29 +00:00
Abhay Padamani db1da2f096 fix: MetricCardChart to use PanelSchemaRenderer (#13380)
- Replaced ChartRenderer with PanelSchemaRenderer in MetricCardChart.vue
for consistent rendering.
- Updated MetricCardChart.spec.ts to assert the panel schema and
injected data correctly.
- Introduced applyMeasuredYAxisLeftInset utility to adjust y-axis label
spacing based on content width.
- Enhanced convertPromQLData to call applyMeasuredYAxisLeftInset before
returning options.
- Added tests for applyMeasuredYAxisLeftInset to ensure correct behavior
with various configurations.
- Implemented compact preview adjustments in heatmap charts to optimize
rendering in metrics explorer cards.
2026-07-22 14:24:46 +00:00
cbaf6042fb fix(navbar): prevent left-rail selection jumping to wrong tile on load (#13332)
Config APIs resolve after mount and insert/remove menu tiles in
linksList, shifting the active tile. The selection pill's ResizeObserver
never fires because the flex-constrained list doesn't resize, leaving
the pill over the wrong tile (e.g. Dashboards while on Streams).

- Gate the rail behind menuReady so it paints once its item list is
settled, avoiding config-driven pop-in and layout shift.
- Re-measure the selection pill when the rail's items change.

---------

Co-authored-by: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com>
Co-authored-by: ktx-vaidehi <vaidehi.akhani@kiara.tech>
2026-07-22 11:45:24 +00:00
Bhargav e0fe62a9a0 fix: traces page default flame graph and drag-drop option added (#13347) 2026-07-22 11:37:24 +00:00
Abhay Padamani 4d58a1efe4 build: add UI code quality checks and enhance linting processe (#13174)
chore: update package.json scripts for improved type-checking
fix: adjust frontmatter regex to handle BOM characters
refactor: streamline error handling in dashboard data conversion
style: clean up comments and formatting in various files
2026-07-21 12:47:06 +00:00
9ede45bbf7 fix: Agent Behavior signals (loop/failure taxonomy) + Agent Graph fixes (#13279)
## What

Adds **Agent Behavior signals** — a PB-safe rollup that surfaces two
behavioural questions the platform couldn't answer before: *is an agent
stuck in a loop?* and *why is it failing?* Plus a batch of Agent Graph /
AI Observability UX and correctness fixes found while building it.

Companion changes land in **o2-enterprise**
(`feat/agent-signals-enterprise-split`): the SQL builders, mappers,
config-driven failure taxonomy, the service-graph agent-classification
fix, and the RBAC route entry.

## Backend (agent-signals)
- Bounded-key rollup co-located in the service-graph job
(alert-manager-only, reuses its watermark + self-ingest). Groups only on
agent/tool/class — never trace_id — so it stays PB-safe.
- Config-driven **failure taxonomy** (rules as data, evolvable without
code) + config-driven error-detail columns; ancestor-agent resolution so
error spans attribute to the owning agent.
- Read API `GET /{org}/traces/agent_signals` (dual `#[cfg]`, reads the
derived `_agent_signals` stream) + RBAC route entry (enterprise
`route_permissions`) mapping it to `traces` read.
- OSS/enterprise split respected: pure SQL/mapping in enterprise, thin
gated I/O in OSS.
- Fix: `traces/latest` only narrows to a UUIDv7 trace_id's embedded time
when that time is inside the caller's window (fixes "trace not found"
for time-shifted/re-ingested traces).

## Frontend (AI Observability)
- New **Agent Behavior** page under Monitor: looping-agents +
failure-taxonomy tables, each row opening an actionable detail drawer
(real grouped error messages / worst-repeat traces), click-through to
Trace Details in a new tab.
- Agent-node **Behavior tab** on the Agent Graph node drawer (reuses the
same signals).
- Nav rename: LLM Insights → **Insights**, LLM Sessions → **Sessions**.
- **Agent** is the default scope across the AI pages, with the
Agent|Stream toggle placed consistently (left scope bar); explicit
URL/localStorage choices still win.
- Removed the duplicate in-table refresh on Sessions.

## Agent Graph fixes
- **Agent classification**: agents that also call tools now correctly
classify as `service_type: "agent"` (they glow), fixing a latent
precedence bug where the tool/model client-role overrode the agent role
and `is_real_service` forced agents to plain services. Rust
`classify_entity` brought in line with the (already-correct) TS twin.
- **Drag-to-pan restored**: the shared ChartRenderer armed
`dataZoomSelect` cursor unconditionally, hijacking drag away from roam
pan. Disabled for this graph (no dataZoom).
- **Nodes scale on zoom** (`nodeScaleRatio: 1`) so zooming enlarges
nodes/labels, not just spacing.

## Testing
- Enterprise unit tests: agent-signals SQL/mappers/config,
classification precedence, RBAC route resolution — all pass.
- FE: type-check + lint clean.
- Verified end-to-end on real re-ingested introspection data: taxonomy
unclassified rate ~100% → ~1–3%, agents resolved, both agents
classify/glow, trace-open works.

## Notes
- `Cargo.toml`/`Cargo.lock` and unrelated Slack-bot files were
intentionally excluded from the commits (local build-config drift).

---------

Co-authored-by: ktx-vaidehi <vaidehi.akhani@kiara.tech>
Co-authored-by: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com>
2026-07-21 10:45:05 +00:00
fd85566742 feat: workflows version 1 (#13156)
This PR introduces workflows 

design at:
https://github.com/openobserve/designs/blob/main/infrastructure/workflows/design.md

---------

Signed-off-by: Yashodhan Joshi <yjdoc2@gmail.com>
Co-authored-by: Yashodhan Joshi <yjdoc2@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 05:33:46 +00:00
f15e27e5fd refactor: design token migration + CI guards (#13173)
# Design System Unification — one visual language, enforced end-to-end

Establishes **one source of truth for every visual decision**, wires the
whole app to it, and **locks it in with CI** so it can't drift back.

### Foundations
- **Design-token system ("one knob per decision").** Every colour, text
size, spacing, corner radius, and shadow is a named token defined in
exactly one place — change the token, every screen updates. Collapsed
hundreds of near-duplicate definitions and removed "fake" duplicates
that pretended to be different but weren't.
- **Shared component library (O2).** Buttons, tables, dialogs, drawers,
form fields, badges, etc. already know the correct colours, spacing, and
dark-mode behaviour; new screens compose them instead of hand-styling.
Added new primitives on this branch (`OContent`, `KpiCard`) and
standardized existing ones.
- **One page structure everywhere** (`PageLayout` / `AppPageHeader` /
`AppPage` / `OContent`) — titles, breadcrumbs, spacing, and content
edges line up identically across every screen.
- **One table implementation** — all tabular data uses the same table
(sorting, density, sticky headers, empty states).
- **Styling consolidated onto Tailwind; old systems removed** —
hand-written CSS driven to the practical minimum, the legacy **Quasar**
framework fully removed, surviving stylesheets required to justify
themselves.
- **Two-tier corner radius** — a tight **4px** for controls and a soft
**12px** for surfaces (dialogs, cards, the app shell), plus a
full-circle option — replacing five radius names that all secretly meant
the same thing.

### Detailed improvements across screens
**Page structure & alignment** — unified page shell rolled across ~40+
screens (IAM, Settings, Alerts, Dashboards, Functions, Pipelines, RUM,
Metrics, Synthetics, Billing, editors, lists); one header height; one
page-edge grid driving headers, tables, rails, and tabs; unified
side-rail widths; inset rail selection/hover pills; every tab strip on
the grid line; tightened title/subtitle spacing.

**Tables (cross-screen)** — action columns consistently centered (incl.
Organizations); unified row-number column with no "#" truncation;
non-standard tables restyled to match; loading skeletons made
geometrically identical to the loaded table; symmetric edge inset; IAM
first-column grid alignment fixed.

**Traces** — flame graph + span-detail correlation panel fixes;
correlated logs/metrics placeholder fit; sticky headers; removed a
double scrollbar + a 1px sliver; aligned table fonts/badges; polished
the service detail drawer; stream-selector + correlation alignment;
aligned loading spinners; floating overlay arrows for the
disabled-services strip.

**Logs** — field-list controls aligned (focus ring no longer clipped);
detail-drawer spacing + metrics-tab overflow fixed; Run Query toolbar
tidied; listing sizing + stray border fixed.

**Metrics** — search added to the "Type" facet rail and aligned with the
other rails; proper empty states for the explorer and facet rail.

**Dashboards** — removed inconsistent card shadows; tightened
borders/resizers/field-list spacing; aligned View Dashboard chrome to
the grid; chart colours via the token helper.

**Home** — redesigned the Usage tab; added reusable KPI stat cards so
summary strips stop drifting.

**Dark mode** — new theme-switch animation; fixed a 1px content shift on
toggle and a white flash on load; fixed AI-chat text and assorted
light/dark mismatches; completed the token-driven dark migration.

**Empty states, controls & small polish** — restored 3 broken
empty-state animations; adopted the standard empty state; added the
sliding indicator to the toggle control; fixed the Quality-page inset +
drawer badge; refreshed Search History / Schedulers; fixed the
alert-history drawer scrollbar + timeline hover; removed a stray
menu-bar border.

### Dead code & cruft removal
- **Deleted the entire legacy `utilities.css`** global stylesheet (well
over a thousand lines) — evacuated in waves, roughly half of it dead
once traced.
- **Removed Quasar entirely**, including dead `.q-*` styles and ~25
phantom `--q-*` variables that were silently breaking bits of the UI.
- Stripped hundreds of **dead CSS classes and tokens that compiled to
nothing** — e.g. 46 uses of a colour class with no backing token, a
no-op shadow class on 51 elements, dead layout/utility classes, a
leftover `card-container` block, a dead table-actions token, and a
mis-registered gradient that rendered nothing.
- Cut **unused keyframe animations (19 → 3)** and removed **unused
components** + a stray build artifact.
- Net effect: a much smaller, leaner styling surface — less to maintain,
fewer dead ends to copy.

## Safety & verification
Large but low-risk: this is overwhelmingly a **styling and structure**
change, not product behaviour or data. Guarded by the
design-token/consistency/type checks above plus the existing test suite,
and reviewed screen-by-screen.

---------

Co-authored-by: Kirtan Thakkar <kirtan@kiara.tech>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: ktx-mihir <mihir.patel@kiara.tech>
2026-07-20 18:06:21 +00:00
9063b4c943 fix: add check type picker before builder (#13248)
## What changed

Adds a `CheckTypePicker` component that presents 5 check types (Browser,
HTTP, TCP, TLS, SSH) as action cards before the builder flow. Updates
`OEmptyState` with a `columns` prop and `:filtered` state for contextual
empty messaging. Adds `hideChevron` support to `EmptyStateActionCard`
for inline variants. The empty state in `MonitorTable` now passes
`:filtered` and `columns` to `OEmptyState`.

## Why

Synthetic monitoring needs a type-selection step between the listing
page and the check builder. Previously, the empty state was a static
custom inline block; now it respects active search/filter state via the
`:filtered` convention and uses the same `OEmptyStateActionCard`
component as the type picker.

---------

Co-authored-by: Claude <claude@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-20 10:06:46 +00:00
Prabhat Sharma eaba77fa34 fix(i18n): DeepSeek translation pipeline, BCP-47/en-US locales, +ru/pl/vi, full backfill (#13263)
## Summary

Replaces the AWS Translate i18n pipeline with a DeepSeek (LLM) backend,
standardizes locale files to BCP-47 naming with an `en-US` base, adds
new languages, and backfills **every locale to 100%**.

## What changed

### Translation engine (DeepSeek)
- Swap AWS Translate → DeepSeek via its OpenAI-compatible API
(`scripts/translations/`). Auth via the `DEEPSEEK_API_KEY` secret;
per-request 60s timeout.
- **Batched** translation (50/call) with **per-string validation**:
interpolation placeholders (`{count}`, `%s`, `@:key`), vue-i18n
**pluralization pipes** (`{n} Day \| {n} Days`), and literal escapes
(`{'@'}`, `{'{'}`, `{'\|'}`) are preserved or the string is rejected and
retried.
- Robust recovery: index-aligned batches, split-on-mismatch to isolate a
single bad string, temperature-perturbed retries, and rejection of
empty/whitespace output (never blanks a label). Removed the old
`TRANSLATION_MAX_KEYS` safety cap.

### Locale naming + base language
- All 15 locale files renamed to **BCP-47 hyphen** form (`en-US.json`,
`zh-CN.json`, `zh-TW.json`, `fr-FR.json`, …) — matching
`navigator.language` / `Intl` / HTML `lang`.
- **English base switched `en-gb` → `en-us`** (app code,
`fallbackLocale`, BCP-47 map, selector, defaults). British-Pound
currency and date-formatting `en-GB` usages left untouched. Existing
`en-gb` cookies fall back cleanly to English — no migration needed.

### New languages
- **Russian (`ru`), Polish (`pl`), Vietnamese (`vi`)** — fully generated
and wired into `localeFileMap`, `APP_LOCALE_TO_BCP47`, and the language
selector.
- **Traditional Chinese (`zh-TW`)** now maintained by the translator
alongside Simplified (`zh-CN`).
- RTL languages (`ar`, `fa`) intentionally **deferred** until the app
has `dir="rtl"` support.

### Full backfill (one-time)
- Every pre-existing locale filled from ~5.9k up to the full **8,269
keys** (~26.9k strings). `.translation_state.json` rebuilt complete, so
future runs are incremental.

### Nav i18n bug fix
- The **Dashboards** and **Data** nav group labels were hardcoded
English (bypassing i18n) and stayed English in every locale. Now
resolved via `t()` (`menu.dashboard`, new `menu.data`), localized and
re-resolving on language change.

## Verification
- Full web unit suite: **39,035 passed / 414 skipped / 0 failed** (1,167
files).
- `type-check` clean (0 TS errors).
- Locale registry sync spec green; all 15 files = 8,269 leaves.
- Translation engine verified live against `deepseek-v4-flash`.

## Notes
- Since all locales are now 100%, the post-merge `update-translations`
workflow will find nothing pending (no follow-up translation PR).
- `ar`/`fa` to be added with RTL support in a follow-up.
2026-07-19 05:29:01 +00:00
Prabhat Sharma 7123cb2384 fix(web): migrate hardcoded UI strings to i18n; RUM & MCP UX fixes (#13261)
## Summary

Three pieces of frontend work from one session, all verified green.

### 1. i18n migration (the bulk)
Replaced **~1,250 hardcoded user-facing strings** across **157
components** with `vue-i18n` `t()` calls and added **1,373 English
keys** to `web/src/locales/languages/en.json`. Other languages are
populated by CI.

Coverage by area: dashboards (36), traces (26), logs (24), settings
(23), IAM (21), metrics/promql (10), RUM error tracking (8).

- Parameterized messages use named interpolation (`t('key', { n })` →
`"Deleted {n} item(s)"`).
- Keys are namespaced by area + component to avoid collisions.
- Deliberately **not** translated: copy/paste code/config snippets,
query-syntax examples (Syntax Guides), technical enum tokens (OTEL
statuses, metric types), config-default fallbacks, and download
filenames.

### 2. RUM Error Tracking UX
- **Auto-size the Issue column** so Trend / Events / Users /
First-Last-seen / Status stay visible at any screen width; long messages
and file paths truncate with tooltips instead of pushing columns
off-screen.
- **Removed the redundant refresh button** (it called the same
`runQuery` as Run query); folded its loading state and keyboard shortcut
into the Run query button.

### 3. MCP Server (IAM)
- Added **Codex** to the supported MCP clients (TOML config for
`~/.codex/config.toml`).
- Added **one-click "Add to …" install buttons** for **Cursor**, **VS
Code**, and **Claude Desktop** via editor deep links (respecting the
selected OAuth / access-token auth mode).

### Tests
Components that newly call `useI18n()` broke specs that mounted them
without an i18n provider (or with an inline message subset / a
key-returning mock). Fixed **33 spec files** by providing the real app
i18n — via `config.global` plugin, a full-locale `createI18n`, or a
key-resolving `vue-i18n` mock — **without weakening any assertion or
deleting any test**.

## Verification
- ✅ Full frontend unit suite: **39,007 passed**, 414 skipped, **0
failed** (`vitest run`).
- ✅ Type-check clean: **0 errors** (`vue-tsc`).
- ✅ `en.json` valid, canonical, and every `t()` key referenced by
changed files resolves (only 3 pre-existing dangling keys remain,
unrelated to this change).

## Notes
- 3 **pre-existing** broken key refs surfaced during validation but
predate this work and were left alone: `common.loading` (AiToolsets),
`search.vrlOnlyForTable` (SearchBar),
`settings.correlation.identityConfigNoSets` (ServiceIdentitySetup).
- An unrelated pre-existing local change to `OIcon.icons.ts` was
intentionally excluded from this PR.
2026-07-18 23:15:05 +00:00
Prabhat Sharma d9d128d61c fix: Serviceaccount1 (#13257)
Improve UI for creating service accounts.
Improve UI for accessing MCP server.
2026-07-18 09:06:26 +00:00
4031f76426 feat: synthetic monitoring (#12849)
Design at : https://github.com/openobserve/designs/tree/main/synthetics

ref: https://github.com/openobserve/o2-enterprise/issues/2095

---------

Co-authored-by: omkark06 <omkar@zinclabs.io>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Claude <claude@users.noreply.github.com>
2026-07-17 08:58:47 +00:00
ktx-akshayandktx-vaidehi f913ada104 fix: empty-state and chip/drawer UI consistency in AI Observability & logs (#13203)
## Summary

A pass of UI-consistency and empty-state fixes across the AI
Observability, Logs, and Settings surfaces. Brings several one-off /
hand-rolled UI patterns in line with the app's shared components
(`OEmptyState`, `OTable` selection, badge groups).

## Changes

| Area | Change | Files |
|---|---|---|
| LLM Model Pricing | Replaced the small faded `$` icon empty state with
the standard `OEmptyState` illustration; added `no-model-pricing` preset
and made it filter-aware (no-results + Clear filters vs. first-run card)
| `ModelPricingList.vue`, `presets.ts`, `en.json`, `hi.json` |
| Scorers list | Moved the "no LLM providers" empty state into the
table's `#empty` slot so the search bar + column header stay visible
(was hiding the whole table) | `ScorerList.vue` |
| Correlation → Traces | "No Traces Found" now uses `OEmptyState` with
the `trace` illustration instead of a small icon + text (both embedded
and drawer paths) | `TelemetryCorrelationDashboard.vue` |
| Logs "No events found" | Added `h-full` so the empty state centers
vertically in the results panel instead of sitting near the top |
`Index.vue` |
| Eval Jobs | Added multi-select checkboxes + bulk **Delete** action
with confirm dialog; selection pruned after reload | `EvalJobList.vue`,
`OnlineEvals.vue`, `en.json`, `hi.json` |
| Score Config → "Not declared" chip | Cramped custom `xs` badge →
default `sm`/`pill` sizing, matching sibling `Categorical` / `Active`
chips | `badgeGroups.ts` |
| Search History → More Details | Added spacing between the tab strip
and the JSON editor | `SearchHistory.vue` |

---------

Co-authored-by: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com>
2026-07-15 11:55:24 +00:00
ktx-kirtan 00b910a382 fix: remove quasar leftovers (#13180) 2026-07-14 11:35:46 +00:00
ktx-akshay 1c24d459b4 fix: remove stray decorative cursor from stream-select empty state (#13177) 2026-07-14 11:18:11 +00:00
3f03b77320 feat: Revamp metrics explorer (#13144)
Revamp the metrics explorer to enable faster visualization and
exploration of metrics.
Design at: metrics_cards_prd.md file

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: ktx-abhay <abhay.padamani@kiara.tech>
Co-authored-by: Abhay Padamani <141122205+ktx-abhay@users.noreply.github.com>
2026-07-14 07:50:56 +00:00
ktx-vaidehi 67aec54d27 fix: ui header inconsistency (#13150) 2026-07-13 11:33:39 +00:00
ktx-vaidehi 5ec3e960c7 fix: ui improvement (#13106) 2026-07-09 11:00:22 +00:00
Ashish Kolhe 37a92fa87b chore: service graph fixes (#13070) 2026-07-08 05:49:13 +00:00
2c087d6953 fix: empty states (#13058)
Co-authored-by: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com>
Co-authored-by: ktx-vaidehi <vaidehi.akhani@kiara.tech>
2026-07-07 15:38:39 +00:00
BhargavandClaude Opus 4.8 2a3416ee35 fix: Rum automation (#13064)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 10:09:16 +00:00