mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-05 16:10:43 +00:00
ee71df7ce73cec00a9dba13dbe4b2e910e16ebfc
3106
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
88051c7ec1 |
chore(deps): bump js-yaml from 4.3.0 to 4.3.1 in /web (#13678)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.3.0 to 4.3.1. <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/nodeca/js-yaml/blob/4.3.1/CHANGELOG.md">js-yaml's changelog</a>.</em></p> <blockquote> <h2>4.3.1 - 2026-07-31</h2> <h3>Security</h3> <ul> <li>[backport] Remove quadratic complexity from <code>!!omap</code> duplicate key detection.</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/nodeca/js-yaml/commit/86e91b815b8794c3c73a179c1770871e37ec2df8"><code>86e91b8</code></a> 4.3.1 released</li> <li><a href="https://github.com/nodeca/js-yaml/commit/c3cc4b0bb9ddb9af2dd9b61e0d56f5ce7983cd4a"><code>c3cc4b0</code></a> Backport quadratic complexity fix for !!omap</li> <li>See full diff in <a href="https://github.com/nodeca/js-yaml/compare/4.3.0...4.3.1">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/openobserve/openobserve/network/alerts). </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com> |
||
|
|
0b853837ca |
chore(deps-dev): bump postcss from 8.5.19 to 8.5.25 in /web (#13610)
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.19 to 8.5.25. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/postcss/postcss/releases">postcss's releases</a>.</em></p> <blockquote> <h2>8.5.25</h2> <ul> <li>Fixed 8.5.17 visitor regression.</li> <li>Fixed <code>list.split()</code> for non-string values (by <a href="https://github.com/amir-rezaei"><code>@amir-rezaei</code></a>).</li> </ul> <h2>8.5.24</h2> <ul> <li>Preserve the BOM after the processing (by <a href="https://github.com/hdimer"><code>@hdimer</code></a>).</li> </ul> <h2>8.5.23</h2> <ul> <li>Do not load source map without <code>opts.from</code> for security reasons.</li> </ul> <h2>8.5.22</h2> <ul> <li>Fixed custom property losing semicolon before a comment (by <a href="https://github.com/sarathfrancis90"><code>@sarathfrancis90</code></a>).</li> </ul> <h2>8.5.21</h2> <ul> <li>Fixed childless at-rule losing semicolon before comment (by <a href="https://github.com/sarathfrancis90"><code>@sarathfrancis90</code></a>).</li> <li>Fixed docs (by <a href="https://github.com/isker"><code>@isker</code></a>).</li> </ul> <h2>8.5.20</h2> <ul> <li>Fixed missing space if <code>AtRule#params</code> is set after (by <a href="https://github.com/sarathfrancis90"><code>@sarathfrancis90</code></a>).</li> <li>Fixed mixing AST error on warnings (by <a href="https://github.com/MahinAnowar"><code>@MahinAnowar</code></a>).</li> </ul> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/postcss/postcss/blob/main/CHANGELOG.md">postcss's changelog</a>.</em></p> <blockquote> <h2>8.5.25</h2> <ul> <li>Fixed 8.5.17 visitor regression.</li> <li>Fixed <code>list.split()</code> for non-string values (by <a href="https://github.com/amir-rezaei"><code>@amir-rezaei</code></a>).</li> </ul> <h2>8.5.24</h2> <ul> <li>Preserve the BOM after the processing (by <a href="https://github.com/hdimer"><code>@hdimer</code></a>).</li> </ul> <h2>8.5.23</h2> <ul> <li>Do not load source map without <code>opts.from</code> for security reasons.</li> </ul> <h2>8.5.22</h2> <ul> <li>Fixed custom property losing semicolon before a comment (by <a href="https://github.com/sarathfrancis90"><code>@sarathfrancis90</code></a>).</li> </ul> <h2>8.5.21</h2> <ul> <li>Fixed childless at-rule losing semicolon before comment (by <a href="https://github.com/sarathfrancis90"><code>@sarathfrancis90</code></a>).</li> <li>Fixed docs (by <a href="https://github.com/isker"><code>@isker</code></a>).</li> </ul> <h2>8.5.20</h2> <ul> <li>Fixed missing space if <code>AtRule#params</code> is set after (by <a href="https://github.com/sarathfrancis90"><code>@sarathfrancis90</code></a>).</li> <li>Fixed mixing AST error on warnings (by <a href="https://github.com/MahinAnowar"><code>@MahinAnowar</code></a>).</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/postcss/postcss/commit/08c989c43cc87edb1ed71408c2f5164c54fc21df"><code>08c989c</code></a> Release 8.5.25 version</li> <li><a href="https://github.com/postcss/postcss/commit/24f681471645cd960ee760ab7f9e348fbabfd42c"><code>24f6814</code></a> Fix 8.5.17 visitor regression</li> <li><a href="https://github.com/postcss/postcss/commit/f2fa53f11daab3a16c7eb8bcaf5a945142341df3"><code>f2fa53f</code></a> Add supply chain security requirement to PostCSS plugin guide</li> <li><a href="https://github.com/postcss/postcss/commit/10edf0b0606f97b1510e040c27bfd078c48d6ea7"><code>10edf0b</code></a> fix: return empty array for empty string in list.split (<a href="https://redirect.github.com/postcss/postcss/issues/2121">#2121</a>)</li> <li><a href="https://github.com/postcss/postcss/commit/0ebe8ad591621ab4e48311da47a76974617571f9"><code>0ebe8ad</code></a> Release 8.5.24 version</li> <li><a href="https://github.com/postcss/postcss/commit/73218c64245be53e25d58150e0cc7e984f1d162d"><code>73218c6</code></a> Update dependencies</li> <li><a href="https://github.com/postcss/postcss/commit/9a114f62b0deb37be859102f93b414b49385805a"><code>9a114f6</code></a> Preserve the BOM when stringifying (<a href="https://redirect.github.com/postcss/postcss/issues/2119">#2119</a>)</li> <li><a href="https://github.com/postcss/postcss/commit/90692619125cb9424f5eafd8c64bc76b2da23db1"><code>9069261</code></a> Fix types check</li> <li><a href="https://github.com/postcss/postcss/commit/eb9e1fe793740bb3280bdf5bf98147f857f011bd"><code>eb9e1fe</code></a> Release 8.5.23 version</li> <li><a href="https://github.com/postcss/postcss/commit/9d19c78ac91108b3f7d7130e55c6fa806c0efb84"><code>9d19c78</code></a> Update dependencies</li> <li>Additional commits viewable in <a href="https://github.com/postcss/postcss/compare/8.5.19...8.5.25">compare view</a></li> </ul> </details> <br /> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com> |
||
|
|
a5c0249e65 |
chore(deps-dev): bump brace-expansion from 1.1.13 to 1.1.18 in /web (#13609)
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.13 to 1.1.18. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/juliangruber/brace-expansion/releases">brace-expansion's releases</a>.</em></p> <blockquote> <h2>v1.1.15</h2> <ul> <li>Backport v5.0.6 change to v1 (<a href="https://redirect.github.com/juliangruber/brace-expansion/issues/111">#111</a>) 0b09384</li> </ul> <hr /> <p><a href="https://github.com/juliangruber/brace-expansion/compare/v1.1.14...v1.1.15">https://github.com/juliangruber/brace-expansion/compare/v1.1.14...v1.1.15</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/juliangruber/brace-expansion/commit/758fcd6d188a95c2342818519c77b8c06794552b"><code>758fcd6</code></a> 1.1.18</li> <li><a href="https://github.com/juliangruber/brace-expansion/commit/27fbeed22b4fdf2c5f732f66bcf84d43f4a26c6e"><code>27fbeed</code></a> Merge commit from fork</li> <li><a href="https://github.com/juliangruber/brace-expansion/commit/5c57cc2519dfb067e188b7cb0733fffbd02946bf"><code>5c57cc2</code></a> 1.1.17</li> <li><a href="https://github.com/juliangruber/brace-expansion/commit/d757f1dde7808bcbcd7a4628ab913e5185ed3d57"><code>d757f1d</code></a> npm ignore <code>.claude</code></li> <li><a href="https://github.com/juliangruber/brace-expansion/commit/cb4b9e47cc2ec777c14b2b4492fb431a56f6a031"><code>cb4b9e4</code></a> fix: backport GHSA-mh99-v99m-4gvg (<a href="https://redirect.github.com/juliangruber/brace-expansion/issues/129">#129</a>)</li> <li><a href="https://github.com/juliangruber/brace-expansion/commit/447763a91a613cfa67ac73096cbc1de9a2304f97"><code>447763a</code></a> 1.1.16</li> <li><a href="https://github.com/juliangruber/brace-expansion/commit/d74e63030c012e3b7ae81657b8d665619cd51b95"><code>d74e630</code></a> fix: v1 backport for CVE-2026-13149 (<a href="https://redirect.github.com/juliangruber/brace-expansion/issues/122">#122</a>)</li> <li><a href="https://github.com/juliangruber/brace-expansion/commit/2203f4f4895eba16c4d408b4219ce1b8e5f6ff24"><code>2203f4f</code></a> 1.1.15</li> <li><a href="https://github.com/juliangruber/brace-expansion/commit/0b0938410732370559704230724ca4a44d1b29fd"><code>0b09384</code></a> Backport v5.0.6 change to v1 (<a href="https://redirect.github.com/juliangruber/brace-expansion/issues/111">#111</a>)</li> <li><a href="https://github.com/juliangruber/brace-expansion/commit/10c05fcf3699b1a29ef5e611c011af3d3c97e6e3"><code>10c05fc</code></a> 1.1.14</li> <li>Additional commits viewable in <a href="https://github.com/juliangruber/brace-expansion/compare/v1.1.13...v1.1.18">compare view</a></li> </ul> </details> <br /> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com> |
||
|
|
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. |
||
|
|
dafc1ce7f7 | feat(ui): dashboard query editor redesign (#13637) | ||
|
|
3285a31e6f |
fix: i18n lint enforcement (#13439)
Co-authored-by: Kirtan Thakkar <kirtan@kiara.tech> |
||
|
|
95e1ee83c3 | fix(search): improve loading behavior on stream change (#13636) | ||
|
|
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). |
||
|
|
e525ad4b17 | ci: fix windows build | ||
|
|
a05eba7ae0 | ci: fix build on window (#13631) | ||
|
|
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. |
||
|
|
2a68900e12 | feat(ui): dashboard tab reorder rename (#13572) | ||
|
|
601ef75e2e | fix: IAM page UI changes (#13618) | ||
|
|
375a7360de |
fix: alert v4 issues (#13601)
This pr fixes the issue on #13571 |
||
|
|
232b38351d | fix: search history routes change (#13512) | ||
|
|
76f1df06fa | fix: alert timezone fix on main (#13519) | ||
|
|
1ec3b5d0dc |
fix(synthetics): make journey validation errors reach the step they name (#13616)
## Problem
Saving a browser check with an invalid step showed only the toast **"Fix
the highlighted fields before saving"** — no step expanded, no field
highlighted. Two separate faults produced that symptom.
### 1. In create mode, nothing reached the journey at all
`OStepper` runs in **wizard mode** here (no `expanded` prop, default
`false`), so `OStep` renders its panel behind `v-if="isActive"` —
**`BrowserJourney` exists only while `currentStep === 1`**. Create
mode's only Save button lives in the `currentStep === 2` footer, so
`journeyRef.value` was `undefined` when `persist()` ran:
```ts
currentStep.value = 1; // not flushed yet
journeyRef.value?.validateStepSelectors?.(); // no-op
journeyRef.value?.setStepFieldErrors?.(result.error.issues); // no-op
toast({ variant: "error", … }); // the only survivor
```
A `nextTick` would not have been enough: a **freshly mounted**
`BrowserJourney` starts with an empty error map, so an imperative push
cannot survive the remount. Edit mode worked because it has a Save on
the Journey step — which is why this read as create-mode-only.
### 2. Even when they arrived, only 2 of 7 rules expanded anything
Auto-expand lives in `validateJourneySteps()`, which knows two rules.
The other five — `stepNameRequired`, `retiredAction`, the navigate URL,
`typeTextRequired`, `expectedRequired` — live in the zod schema and
reached the journey through `setStepFieldErrors()`, which recorded the
message and returned. `getRowStatusColor()` ignored them too, so
"highlighted" was literally false for those rules.
## Fix
- **Issues are parent-owned state passed as the `fieldIssues` prop**,
applied by an `immediate` watcher. Mount order stops mattering — issues
raised while the journey is unmounted wait, and rows open whenever it
next renders (including the Details-error + journey-error case, where
the author lands on Configure and finds the journey already expanded on
switching tabs).
- **Both paths route through `revealErroredSteps()`** — opens every
errored row in journey order, clears an active filter (a filtered-out
row isn't rendered, so expanding it shows nothing), scrolls to the
first. `getRowStatusColor()` reads schema errors too.
- **Stale errors clear as the author edits.** The editor emits no
per-field event for name/value/expected, so those stayed red over
corrected input and kept re-opening a correct row.
- **Stale errors clear on a successful save** — `setStepFieldErrors` was
only ever called on the failure branch.
- **Duplicate error toast gone.** `persist()` no longer calls Path A at
all; the schema already emits both of its rules as field issues.
Removed as dead: `setStepFieldErrors` from `defineExpose`, and the
`silent` option added mid-fix for the duplicate toast.
## Tests
846 passing. Validation had large untested surfaces — which is how
coverage decayed without a single failure. Closed here:
- monitor-level `name`/`url`/`locations` and the **entire gate schema**,
neither ever exercised (`form()` supplies valid values; the view spec
replaces the schema module with a stub)
- `validateJourneySteps`' auto-expand — **untested since the first
commit**
- the `expectedErrorMessage` binding, `.trim()` on type text and
expected, the `\S+$` part of the navigate regex, `element_not_visible` /
`page_title` / `element_attribute`, and the `scroll`/`screenshot`
retired actions
The view spec's `OStep` stub was `v-if="$parent.$parent || true"` — it
rendered **every** panel, keeping `journeyRef` alive on the Configure
step, which is why this spec could not have caught fault 1. It now
mirrors wizard mode, and a create-mode save with a journey error is
asserted end to end.
**Every rule verified by mutation** — disabled at source, suite re-run,
reverted. **33 mutations, all caught.** Three initially ran green and
led to stronger tests: `min(1)` on `url` only governs *which message*
appears (the protocol refine rejects `""` anyway), nothing reached
`\S+$`, and `validateJourneySteps`' expand had no test at all.
## Not fixed — filed for follow-up
Found in the same sweep, deliberately out of scope:
- **`select` / `press` / `upload` accept a blank value.** `v2Value()`
maps all of them to the wire, so they save, run, and **pass** — the
false-green `typeTextRequired` exists to prevent. `type` isn't special;
it just got a rule.
- **A zero-step journey saves.** No `.min(1)`, and the first-step check
treats an empty array as valid.
- **`element_attribute` has no attribute rule**, and no error binding
for that field.
- **`timeout` / `settle.budget_ms` warn but never block.** Needs
`Advanced` to become controlled first, or the rules land inside a
collapsed section and reproduce this same bug one level deeper.
- **Continue and Replay use the 2-rule gate**, not the schema — so a
journey with a blank type text replays and false-greens.
- **A whitespace-only monitor name is accepted** (`.min(1).trim()` vs
the step rule's `.trim().min(1)`), so a check named three spaces saves
and displays blank.
Full write-up with the regression timeline, the mutation table, and why
each layer's tests stayed green is in
`docs/synthetics/002-journey-step-validation-audit.md` (untracked —
`docs/` is gitignored in this repo).
## Verification
- `npx vitest run src/components/synthetics src/views/synthetics` → 43
files, **846 passed**
- `npm run type-check` → clean
- `npx eslint` on changed files → 0 errors (4 pre-existing warnings,
none introduced)
- `npx prettier --check` → clean
|
||
|
|
6f0b2dcb07 |
fix: workflow history (#13588)
This enhances the workflow history , stores all runs instead of just failed ones and also some UX improvements on FE --------- 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> |
||
|
|
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. |
||
|
|
624425a08a |
feat(editor): rebuild query autocomplete, and fix the value path behind it (#13586)
Design at autocomplete/autocomplete.md
Rewrites the query editor's autocomplete across every surface that has
one, and
follows the field-value path far enough to fix the bugs it was hiding.
53
commits, in six phases; each phase was written test-first, reviewed
adversarially, and checked in a browser before the next one started.
## What was wrong
The completion providers were wired inconsistently. Six surfaces bound a
resolver that was never defined, so their value completion could not
resolve at
all — SLO, dashboards and anomaly detection among them. Traces never
bound
`:suggestions`. Alerts never called `updateStreamKeywords`. PromQL ran a
second
editor runtime purely for its grammar, and asked
`/prometheus/api/v1/series`
— the heaviest endpoint available — for data the stream schema already
has.
## Phases
**1 — icons, staleness, quoting.** Correct `CompletionItemKind` glyphs,
stream
keywords refreshed when the stream changes, `spath` quoting.
**2 — the function catalog.** Custom functions get one tab stop per
argument,
org VRL functions stop appearing twice, column types read from
`dataType` as
well as `type`.
**3 — signature help, hover, async completion.** Providers return real
`CompletionList`s with `incomplete: true`, so a list that arrives late
is
re-queried rather than pinned.
**4 — numeric ranking.** Numeric columns sort first inside a numeric
aggregate.
**5 — values for streams nobody has searched.** The value cache only
ever held
what a search had already returned, so a stream you had not queried
offered
nothing. Values are now fetched on demand, bounded (10s timeout, 60s
cooldown,
one in-flight request per key), and shared between the editor and the
builder.
PromQL joins the same path: labels from the stream schema, values from
the
shared cache. `/series` now has zero production callers.
**6 — one way to fetch a value.** Item 22 only, in this PR.
## The dashboard filter 400
Clicking the ✕ beside "Select Field" in a filter's dropdown sets the
column to
`{}`, and the watcher on `condition.column` then asks for that column's
values.
`loadFilterItem` built `fields: [row.field]` out of `undefined`,
JSON.stringify
turned that into `null`, and the server answered
```
400 {"code":400,"message":"SerdeJsonError# invalid type: null, expected a string"}
```
`_values_stream` answers 400, not an empty result, for each way a piece
of the
payload can go missing — and every one is reachable: an unresolved
stream alias
(`.find(…)?.stream`), a stream not yet chosen (`""`), a range not yet
set. The
range fails differently: `meta.dateTime` starts as `{start_time: ""}`
and
`""?.toISOString()` does not short-circuit, so it throws and both
callers
surface "Something went wrong!" for a lookup the user never asked to
fail.
Guarded at the two callers, where the payload is built and where the
throw
happens. Not at `fetchFieldValues` — a guard there is too late to stop
the
throw and would duplicate validation the callers need anyway. The
trigger is
left alone: re-picking a field must still load its values.
## Notable removals
CodeMirror is gone — it was still bundled for the PromQL grammar alone.
Bundle 26.3 MB → 25.43 MB; the PromQL chunks 442.7 KB → 20.6 KB. The
PromQL
term catalog went from 7 entries to 113, generated from Prometheus's own
tables
and vendored with its MIT notice.
## Verification
- 76 files, +10.5k/−1.4k, ~9,100 tests passing across the touched areas.
- `vue-tsc` clean, `npm run build` exit 0.
- Every phase checked in a running browser against a live instance,
including
the 400 above: the ✕ now produces no request and no toast, adding a
filter and
re-picking a field each still fetch once, and the value list still
populates.
## Known, deliberately out of scope
- A filter still fetches twice (once on add, once on open) — that is
item 23,
the cache read, not this PR.
- `useMetricsExplorer.loadFilterItem` has the same unguarded
`toISOString()`
pattern on a different surface.
- `services/metrics.ts` and `services/search.ts` still carry dead
`get_promql_series` wrappers now that nothing calls `/series`.
|
||
|
|
021d2135b5 |
fix(folders): stop the move dialog offering the folder you are already in (#13599)
## The bug Selecting checks in Synthetic Monitoring and pressing **Move** opened a dialog reading: ``` Current Folder: Foo Select Folder: Foo ``` The same folder name twice — which reads as "move this to where it already is". Nothing could actually go wrong: `primary-button-disabled` already blocked a same-folder submit. The dialog simply gave no clue *why* Move was greyed out, because the destination it had chosen for you was the one destination it would not accept. ## Cause `MoveAcrossFolders` seeded `selectedFolder` from `activeFolderId`, and `SelectFolderDropDown` listed every folder including that one. ## Fix - `SelectFolderDropDown` gains an optional `excludeFolderId` prop: it filters that folder out of the options and refuses to seed the selection with it. - `MoveAcrossFolders` passes `:excludeFolderId="activeFolderId"`, opens with no destination chosen, and additionally disables Move while the destination is empty — *"not the active folder"* is not the same as *"somewhere to move to"*. - The folder-list watcher now keeps a still-offerable choice. Creating a folder from the **+** button arrives here as a store list change, and the re-seed would otherwise clear the folder just created and selected. ## Why the prop is opt-in `SelectFolderDropDown` has six other consumers — ImportAlert, AlertList's clone dialog, CreateReport, AddSlo, SloList, and the synthetics *duplicate* dialog. Every one of those picks a folder to file something **into**, where opening on the active folder is correct. Unset, the filter matches every folder (`!== undefined`) and the seeding is untouched, so those call sites are byte-for-byte unchanged. ## Scope This dialog is shared, so the change reaches **alerts, pipelines, reports, SLOs and synthetics**. Behaviour is strictly narrowed: the option removed was already a guaranteed no-op behind the disabled-submit guard. `MoveDashboardToAnotherFolder` uses a separate `SelectFolderDropdown` under `components/dashboards/` and has the same behaviour, but it is a different component tree and is deliberately left alone. ## Tests 5 new cases on the dropdown (default offers everything, excluded id dropped, blank start, normal seeding still works, choice survives a list change) and 1 on the dialog. Three existing tests asserted the old seeding and are updated. **One deserves review:** `should compute primaryButtonDisabled=false when selectedFolder value is null` asserted that a *null* destination **enables** Move — executing that submits `dst_folder_id: null`. I read it as wrong rather than intentional and inverted it. If that null path is load-bearing somewhere I have not found, this is the thing to challenge. ## Verification Run on this branch, based on `main`: - `src/components/common/sidebar/` — 291 passed (6 files) - Consumers: SyntheticMonitoring, AlertList, ImportAlert, MoveDashboardToAnotherFolder — 216 passed, 10 pre-existing skips - `eslint` on all four changed files — clean - `npm run lint:design:strict` — OK, no regressions - `npm run type-check` — exit 0 |
||
|
|
aedcc83a24 |
fix(synthetics): stop a browser replay without stranding a step in progress (#13592)
## Problem
Recording a browser test and replaying it, then pressing **Stop** in the
journey editor:
1. The step the replay was on stayed stuck in the in-progress (spinner)
state forever.
2. The extension did not stop promptly — it kept going for another step
or two.
## Root cause
One defect produced both symptoms, and it lives in the recorder
extension's player
(`CrxPlayer.stop()` in `openobserve/playwright-crx`): it did not abort
the in-flight
Playwright call. It only raised a flag that `_checkStopped()` reads at
the **start** of an
action — never while a `click` / `fill` / `expect` is pending, and those
carry a 60s timeout.
So after Stop: the current step ran to completion, then the loop
advanced and emitted
`stepStarted` for the *next* step before unwinding. That step could
never report a result.
`useSyntheticsRecorder` set `activeStepId` from it and `stepDotState`
rendered it as
`"active"` — a permanent spinner.
The player fix is a companion PR in `openobserve/playwright-crx`. **This
PR is the O2 half**,
which is also what makes the UI honest while the stop is in flight.
## Changes
- **`ReplayPhase` gains `stopping`** — the interval between the click
and the extension
confirming. Stop becomes a disabled, loading button in the same slot (no
layout shift) and
the banner reads "Stopping replay…", so the UI no longer claims the run
is over while it is
still winding down.
- **`useSyntheticsRecorder.stopReplay()` owns the `running → stopping →
stopped` transition**,
the way `replay()` already owns `running → passed/failed`. It clears
`activeStepId`, since
the interrupted step never reports a result and nothing else would ever
clear it. Bounded by
the existing `COMMAND_TIMEOUT_MS`, so `stopping` cannot strand the
journey.
- **A replay generation counter** orphans the in-flight replay promise
on stop. Otherwise the
abandoned run answers later and knocks a newer `running` back to
`stopped`, or resurrects a
banner the user just dismissed. Latent before — making stop fast is what
would start firing it.
- **`stepDotState` renders `active` only while `running`**, so an
unresolved step falls through
to `pending` — an empty circle, which is the truth: it never completed.
- **Stream gating** — a late `stepReplayStarted` is ignored outside
`running`; a
`stepReplayResult` already in flight still counts toward "completed X of
N", because that step
genuinely ran.
- `CreateBrowserTest.onStopReplay` collapses to pure delegation; its
route guard treats
`stopping` as live.
Also on this branch: a small `cursor-help` → `cursor-pointer` change in
`MonitorTable.vue`
(separate commit, unrelated to the above).
## Deliberately unchanged
The incognito replay window is **not** closed on Stop, so window reuse
across
record → replay → re-run is untouched.
(`crxApp.close({closeWindows:true})` was not an option
anyway — it removes *every* incognito window, including the user's own.)
`stopReplayAndForget` (route guards, `beforeunload`), recording
start/stop, and the
passed/failed replay paths are untouched.
## Testing
Regression tests in `useSyntheticsRecorder.spec.ts`,
`BrowserJourney.spec.ts` and
`CreateBrowserTest.spec.ts` — **each verified to fail with its guard
removed**, including the
headline one: `replayPhase: "stopped"` with a non-null `activeStepId`
must render `pending`,
not `active`.
The end-to-end proof lives in the companion PR: a browser test drives
the real extension in
real Chrome, blocks a step for 40s, presses Stop, and asserts prompt
acknowledgement,
`stopped: true`, and that no later step was announced. It passes in ~5s
with the player fix;
without it the extension never acknowledges the stop within 15s at all.
## Verification
- `npm run lint` — 0 errors in changed files (4 repo-wide errors are
pre-existing in
`eslint.config.js` and `TraceDAG.spec.ts`)
- `npm run type-check` — clean
- `npx vitest run src/components/synthetics src/views/synthetics
src/composables/useSyntheticsRecorder.spec.ts`
— **817/817 passing across 44 spec files**
## Note for reviewers
This needs the companion `playwright-crx` player fix to actually stop
promptly; without it the
UI is honest but the extension still winds down slowly. The extension
`dist/` must be rebuilt
and reloaded in `chrome://extensions`.
|
||
|
|
f16e8e53ff |
perf(synthetics): config caching, run-budget validation, HA correctness, step-grain Steps tab (#13580)
Synthetics performance and correctness work. Four areas: caching the
config reads on the job path, a false-alarm bug in validation, four HA
correctness bugs found by auditing what happens with more than one alert
manager, and the Steps tab moving to a step-grain stream.
Pairs with o2-enterprise#2325 — **merge this first**, that one calls
functions introduced here.
---
## Caching
- [x] Probe-token auth cache — 10s TTL, negative results cached so a bad
token can't force a query per request
- [x] Locations whole-table cache — 30s, serves `get` / `find_by_pool` /
`list_visible` from one load
- [x] Check definition cache via `get_cached()` — was read twice per job
(resolve + ack)
- [x] Agent capabilities cache via `get_cached()` — was read on every
lease poll
- [x] Org ingest-token cache — `find_default_enabled()` replaces three
hand-rolled `list_by_org().find(enabled)` copies
- [x] `list_by_locations()` — one batched agents query, replacing one
per location
## Cross-node invalidation
- [x] `coordinator::synthetics` — event invalidation following the
alerts/pipelines pattern
- [x] Write paths call `invalidate_and_publish()`, the watcher calls
plain `invalidate_cache()` so events don't echo
- [x] Deletes emit a delete event, so coordinator keys don't accumulate
one per check ever created
- [x] Ingest-token invalidation reuses the existing
`/org_ingestion_tokens/` watch — no second event stream
- [x] SQLite write mutex released before emitting — `get_lock()` and the
coordinator's `put()` take the same lock, so emitting while holding it
deadlocked the process
## HA — correctness with more than one alert manager
Confirmed with @subhradeep that a region can run multiple alert
managers. The synthetics scheduler, dispatcher, reaper and staleness
watcher all run on **every** one of them.
- [x] **Duplicate runs** — `claim_due()` claims due checks with `SELECT
… FOR UPDATE SKIP LOCKED`, advancing the schedule inside the same
transaction
- [x] **Duplicate "location down" pages** — the one-shot flag moved from
an in-process `HashSet` to `synthetics_locations.down_notified_at`,
claimed with a CAS
- [x] **Duplicate run completion** — `increment_jobs_done` claims
completion via `completed_at IS NULL`; two acks of the same run could
both be told they completed it
- [x] **Inert dedup index** — `scheduled_ts` is now the schedule slot,
not each node's tick clock, so the existing `synthetics_jobs_dedup_uq` +
`ON CONFLICT DO NOTHING` actually fires
- [x] Migration `m20260803_000001` adds `down_notified_at`, defaulted so
no backfill is needed
- [x] `DB_SCHEMA_VERSION` 62 → 63 — without it the migration never runs
on an already-upgraded deployment
## The false-alarm fix
- [x] Validate against the run budget (840s), not the job lease (900s)
- [x] The three bounds are deployment-configurable:
`O2_SYNTHETICS_MAX_CHECK_BUDGET_SECS` / `_JOB_LEASE_SECS` /
`_MAX_NET_TIMEOUT_MS`
- [x] Rejected limits are logged, not fatal — nothing installed means
validation keeps the conservative defaults
- [x] Browser retries capped at 2; protocol types stay at 3; default
stays 0 for every type
## Steps tab — the step-grain stream (B10)
- [x] Three `GROUP BY`s over the step stream replace downloading 5000
execution rows and tallying their JSON in the browser
- [x] Falls back to the old tally when the stream is empty, so the tab
does not blank for checks whose agents have not been upgraded
- [x] Gated on the `step_id` column rather than stream existence — a
partially-created stream would otherwise cause four failed queries
## Overview KPIs
- [x] Count tiles summed from the cached histogram; `buildKpiSql`
deleted
## Rename
- [x] `synthetics_monitors` → `synthetics_checks`, `monitor_type` →
`check_type`
- [x] Three UI call sites that read the old field names, including a
delete guard that silently disabled itself
---
**Why validation moved off the lease.** Three bounds stack:
```
check worst case <= max_check_budget_secs < job_lease_secs
840s 840s 900s
```
Validation used the lease, so the server accepted checks the execution
environment cannot finish. On the managed path the probe is a Lambda,
and a function that hits its timeout is killed mid-run — lease headroom
is irrelevant once the process is gone. The net probe's function timeout
had drifted to **60s** while validation accepted up to 900s, so a plain
`timeout_ms=30s, retries=2` (worst case 100s) was accepted and then
killed, reporting a failure the target never had.
**Why the limits are ENT-declared but OSS-consumed.** Synthetics is
enterprise-only, so the values live in `SyntheticsConfig`. The
validation they bound lives in `config`, which cannot depend on
`o2_enterprise`. `SyntheticsLimits` + `init_limits` is the seam, falling
back to built-in defaults in OSS builds and tests. A bad limit is logged
rather than fatal: refusing to start ingest, search and dashboards
because a probe ceiling is misconfigured would be the worse outage.
**The duplicate-run bug.** `fetch_due` was a plain `SELECT` and the
advance was an unconditional `UPDATE … WHERE id = ?`, so nothing stopped
two alert managers firing the same check. Each inserted its own
`synthetics_runs` row under a fresh KSUID, which made the resulting job
rows genuinely distinct — `lease_batch`'s `status = 0` guard cannot
collapse them, because they are not contending for one row. N alert
managers meant every check ran N times.
`SKIP LOCKED` rather than a compare-and-swap because it *distributes*: a
second scheduler skips the rows the first holds and picks up different
due checks, so N replicas split the backlog. A CAS gives the same
exactly-once guarantee but every node reads the same candidates, one
wins them all, and the losers each issue a doomed `UPDATE`. The lock and
the advance share a transaction because the locks only live until
`COMMIT`; the fan-out stays outside it so config rows are not locked
across N job inserts.
**Duplicate run completion needed no second alert manager.** The
increment was atomic, but the completion decision re-read the row in a
second statement and returned "complete" to whichever caller saw
`jobs_done >= job_count` — which both acks of a 2-job run do. Acks are
served on ingesters, of which every environment runs two, and the probes
of one run finish independently. Effect: `consecutive_failures` counted
one run twice on every check, plus a genuinely duplicated notification
on checks left at the default `cooldown_mins = 0`.
**The Steps tab was truncating silently.** It downloaded up to 5000
execution rows and tallied `last_attempt_steps` in the browser. On the
busiest check measured — 18,079 executions over 7 days — that cap
covered 4 of the 7 days and reported a **56.3% fail rate against a true
33.7%**. The win is correctness; the ~18MB payload reduction is
secondary.
## Verification
`cargo check --workspace`, clippy, fmt clean. Config 98, infra 33
synthetics tests pass. Frontend: **839 synthetics tests pass**. Three
frontend spec files fail to import on `useLocalTimezone` — verified
identical on clean `main`, unrelated to this branch.
|
||
|
|
4fa71dd15c |
feat(ui): calm-signal pass over IAM and Synthetics Monitors (#13568)
Second batch of the "Calm Signal" pass (after #13562: Streams, Pipelines, Nodes). ## Users - Summary strip that doubles as the role facet. Tiles are the roles **actually present in the data**, so custom roles appear without being enumerated in code. Counts are role *membership* — a user with Admin + a custom role is counted under both, so tiles intentionally don't sum to the total. Top 5 by size; the tail folds into one "+N more roles" tile counting unique users. - Pending users now show the invited chip on the enterprise path (it only appeared on the open-source one). - Custom roles get one stable neutral chip instead of an untyped tag whose colour was derived from the role's spelling. - Removed a `<style scoped>` block whose only selector is used nowhere. ## Roles - Member count per role, muted when nobody holds it. Comes from the batched user→roles map — **one request for the org, not one per role** — and is gated to enterprise, where that endpoint exists. Rows render immediately; counts patch in when they land. ## Service Accounts - Created reads as relative age, with a dot on keys minted this week. ## Synthetics Monitors - Status counts were hidden inside a dropdown's option labels ("Passed (12)"). They're now a strip that acts as the facet; the dropdown and its dead options plumbing are gone. - Health row rail, plus the red / muted row wash for failing and paused monitors that Alerts and Pipelines already use. - Last check renders through `OTimeCell` instead of a local time-ago copy. - **Fixes styling that never applied:** the uptime sparkline and its hover tooltip referenced `.spark` / `.stt-*` classes that are defined nowhere in the repo, so the bars had no size and the tooltip no surface. Rebuilt with tokens. - `timezone` is a prop now — a leaf table shouldn't reach into the store. ## No strip on Dashboards, Roles, Service Accounts or Groups Their candidate tiles were structurally constant (System = 1), near-always zero, derivable from a neighbour, or already in the table footer. Dashboards was already compliant (favourites, owner cell, relative dates) and is untouched; Groups' API returns names only, so per-row counts would cost one request per group. `calm-signal.md` records both rules this batch produced: the three-question test for a tile, and rail-vs-wash (a full-row wash only when the state means *act now*, is rare, and the row is the unit of action). ## Testing `lint`, `lint:design:strict`, `lint:styles`, `lint:tokens`, `lint:token-purity`, `type-check` pass. 885 unit tests across IAM and Synthetics pass. Synthetics needs a monitor in each state to eyeball the rail/wash; Roles' member column needs an enterprise build (403s and hides itself otherwise). --------- Co-authored-by: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com> Co-authored-by: ktx-vaidehi <vaidehi.akhani@kiara.tech> |
||
|
|
f8253d4b24 | fix: dashboard table chart width (#13587) | ||
|
|
7f87242ac9 |
fix: dashboard reports drawer duplicates entries and hides reports in custom folders (#13569)
## Summary This PR fixes two bugs in the Dashboard Reports drawer (`ScheduledDashboards.vue` / `ViewDashboard.vue`). **Issue 1 — Reports duplicate on every reopen** Opening the Reports drawer, creating a report, then reopening the drawer for the same dashboard caused the same report to appear as a duplicate row. Reopening again added yet another duplicate, so the count grew by one on every open. This happened because the drawer component never unmounts between opens (it's toggled via `v-model:open`, not `v-if`), and the report list was being appended to on each reopen instead of being rebuilt. **Issue 2 — Reports in a custom folder don't show up** Reports created from a dashboard using the Default folder appeared correctly in the drawer, but reports created using a custom report folder did not — even though creation succeeded. The drawer's list request was filtering by the dashboard's own folder id, but that filter is meant to match the *report's* folder, not the dashboard's. Any report saved to a folder other than the dashboard's own folder was silently excluded. | # | Issue | Root Cause | Fix | |---|-------|------------|-----| | 1 | Reports duplicate in the drawer each time it's reopened | `formatReports()` in `ScheduledDashboards.vue` pushed new rows onto the local `scheduledReports` list without clearing it first; since the drawer stays mounted across opens, old rows persisted and new ones piled on top | Rebuild the list fresh from `props.reports` on every call instead of accumulating | | 2 | Reports saved to a custom folder don't appear in the dashboard's Reports drawer | `openScheduledReports()` in `ViewDashboard.vue` passed the dashboard's folder id as the `folder_id` filter on the reports list API, which filters by the *report's own* folder, not the dashboard's | Removed the folder filter so the drawer lists all reports for the dashboard regardless of which report folder they're saved in | Issue : https://github.com/openobserve/o2-enterprise/issues/2319 --------- Co-authored-by: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com> Co-authored-by: ktx-vaidehi <vaidehi.akhani@kiara.tech> |
||
|
|
de95f7f2d8 |
fix: restore Table default log-detail view (#13368) + heal alert-name inline-edit read (#13585)
## What & why Two **Playwright Regression** nightly failures, reproduced identically on both OSS ([run 30785096054](https://github.com/openobserve/openobserve/actions/runs/30785096054)) and ENT ([run 30785071382](https://github.com/openobserve/o2-enterprise/actions/runs/30785071382)) — same two shared-`tests/` specs, all 3 retries failed each. Both are OSS-owned specs (ENT overlays only its own `tests/*` and builds OSS `web/`), so this single OSS PR fixes both nightlies. ### 1. Logs — real product regression (`logs-regression.spec.js:1158`) `Log detail sidebar opens with Table tab by default (#13368)` → `Error: Table tab should be selected by default`. **Root cause:** [#13368](https://github.com/openobserve/openobserve/pull/13368) (Jul 23) deliberately made the log-detail sidebar open on the **Table** tab. [#13451](https://github.com/openobserve/openobserve/pull/13451) (Jul 30, *"table migration to same components"*) silently flipped `detailTableInitialTab` back to `"json"` in `SearchResult.vue` (both the `ref` init and the per-open reset). The unchanged test correctly caught the reverted behavior. **Fix:** restore `"table"` as the default log-detail tab. Product fix — no test change. ### 2. Alerts — stale test vs redesigned component (`alerts-stream-switching.spec.js:443`) `Bug #11577: Alert preview chart y-axis displays numeric values` → `TimeoutError: locator.inputValue: Timeout 45000ms exceeded`. **Root cause:** the alert name became an **`OInlineEdit`** title (`OFormInlineEdit`, from the [#13547](https://github.com/openobserve/openobserve/pull/13547) Alerts revamp). In display mode the value lives in the `…-value` span; the `…-input` only exists **while editing**. The test read `.inputValue()` on the non-existent input → 45s timeout. **Fix:** read the committed display value via a new `getAlertName()` page-object helper (with an edit-mode `inputValue()` fallback). ## Verification - **Alerts #11577** — ✅ passed locally against a local O2 build (`1 passed`); `getAlertName()` reads the name and the full save/verify/delete flow works. - **Logs #13368** — restores the exact `detailTableInitialTab = "table"` value #13368 set and the unchanged test already validated as green. Confirmed by this run's Logs-Regression job + the dispatched ENT e2e gate. ## Scope - `web/src/plugins/logs/SearchResult.vue` — 2 lines (product) - `tests/ui-testing/playwright-tests/RegressionSet/Alerts/alerts-stream-switching.spec.js` — read display value - `tests/ui-testing/pages/alertsPages/alertsPage.js` — `alertNameValue` locator + `getAlertName()` helper |
||
|
|
fc8e3c1657 | fix: redesign the step editor and the run evidence surfaces (#13582) | ||
|
|
23e816b1e4 |
feat: External Alert Sources incident ingestion (#13578)
## Summary Adds a full "External Alert Sources" feature so OpenObserve Incidents can serve as ingestion/correlation layer for third-party alert tools (Grafana, Alertmanager, generic webhooks): - Token-authenticated inbound webhook endpoint (`o2iat_` tokens) that normalizes Grafana/Alertmanager/generic payloads and correlates them into Incidents alongside internally-generated alerts. - Incident integrations CRUD (create, list, enable/disable, rotate token, delete — default integration protected from deletion) with per-sender observed status. - Settings UI: single always-open alert sources table (merged from an earlier two-panel design), shared-token badge, add/rotate/delete flows. - Incident detail UX: contributing-alert Source/Labels columns, "view raw payload" action (surfaces the exact webhook JSON received), and an external-source badge on the incident header so it's obvious at a glance whether an incident has external contributors. - **Auto-resolve**: incidents now close automatically once every contributing external alert reports `status: resolved` — previously required manual resolution even when the source alert cleared. ## Notable follow-up fixes bundled in - Rebased onto latest `main`; repaired two build breaks surfaced by upstream changes that landed after this branch diverged (`CorrelationSubject` refactor's new `eval_level` param, and a new `SeverityEscalated` correlation outcome) — both textually merged clean but were semantically incomplete for the external-event path. - Removed the `O2_INCIDENTS_EXTERNAL_SOURCES_ENABLED` enterprise config flag (companion removal in o2-enterprise) — external sources are now gated solely by `O2_INCIDENTS_ENABLED`, removing a redundant toggle. ## Test plan - [x] `cargo build --no-default-features --features enterprise` — clean, no warnings introduced - [x] `cargo clippy` on touched crates (`infra`, `openobserve-core`, `openobserve-api-management`, `openobserve-api-http`) — no new warnings - [x] `cargo fmt --check` — clean on all touched files - [x] `cargo test` — all tests in touched modules pass (`alert_incidents`, `incident_integrations`, `external_alerts`, `alerts::*` — 274/274); two unrelated flaky DB-pool-timeout tests (`db::tests::test_delete`, `table::ratelimit::tests::test_add_batch_missing_rule_id`) confirmed pre-existing and pass in isolation - [x] Frontend: `vitest run` on all touched components — 156/156 passing; `vue-tsc --noEmit` clean - [x] Manually verified end-to-end against a running dev server: fired/resolved external alerts, confirmed auto-resolve, delete, and raw-payload retrieval all work against real HTTP calls |
||
|
|
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. |
||
|
|
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. |
||
|
|
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 |
||
|
|
8f60dbcc82 |
fix(metrics): widen rate windows when the scrape interval overstates cadence (#13576)
## Problem Metrics explorer cards for histograms, counters, and summaries showed **"Too few samples for a rate"** even though the streams had data. The rate window is sized from the org's configured `scrape_interval` (e.g. 15s → `[1m]`), but when data actually arrives less often (the OTel demo app exports every 60s), `rate()` almost never finds the two samples it needs — so every rate-based card rendered the sparse message while gauges charted fine. Reproduced against a live instance: `sum(rate(app_cart_add_item_latency_count[1m]))` → empty; the same query with `[4m]` → `0.172/s`. ## Fix **Widened-window retry** (`useMetricsExplorerGrid.ts`, `computeWidenedRateWindows` in `metricDefaults.ts`): when a rate-based card comes back empty but the presence probe confirms samples exist in the window, retry with geometrically wider windows — 4×, then 16× the standard window, capped at half the range — and chart the first rung that answers. The sparse message now only appears when no honest window could produce a chart. **Editor / dashboard handoff** (`MetricsExplorer.vue`): the drill-in normally hands the editor `$__rate_interval`, which resolves from the same overstated scrape interval — opening a blank editor for a metric the card visibly charts. The preview now remembers which rung worked (`widenedRateWindow`, persisted through the card cache like `nanGuardApplied`), and both the card→editor drill-in and the favorites→dashboard conversion hand over that concrete window instead. Cards that never needed widening keep the adaptive `$__rate_interval` behavior. Supporting changes: - `previewKeysOf` enumerates the widened query keys so refresh invalidates them instead of replaying stale retries from the queue cache. - Persisted-cache identity gains a shape version (`v: 2`) so pre-field entries (widened data with no memory of the window) miss once and re-learn live. - A failing widened retry settles for the sparse answer already in hand rather than repainting the card as an error; cancellations still abort. - During a refresh the loading state carries `widenedRateWindow` alongside the old results it keeps on screen, so a mid-refresh drill-in stays correct. ## Verification - Unit tests: retry ladder math (caps, dedup, no-room-to-widen), first- and second-rung success, all-rungs-fail keeps the sparse card, window carried on the preview, handoff passes the concrete window vs `$__rate_interval` — 182 tests across the four spec files pass; ESLint and vue-tsc clean. - Live against a dev backend: previously-sparse `app_cart_*` cards all render (network log shows `[1m]` → empty, presence probe, `[4m]` → data); drill-in opens the editor seeded with `sum(rate(...[4m]))` and a rendered chart, from both the live-query and cache-restored paths. |
||
|
|
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 (
|
||
|
|
bbed40d8f4 |
feat(synthetics): author-owned locators, and step-scoped run evidence (#13563)
## Summary
Three synthetics workstreams on one branch: author-owned locators (Phase
2b), retirement of version-1 monitors (Phase 2c), and making a failed
browser step diagnosable without leaving the Steps tab.
Everything else in the synthetics suite passes (422 tests across the 21
suites the evidence work touches).
## Author-owned locators (Phase 2b)
The recorder proposes a locator bundle; the author owns its order.
Schema, combined-locator build/read, provenance pinned across the
recorder bridge, and the payload contract tested from both sides.
`337911158d` `e63f8f429f` `50b811a865` `2a978b9408` `c8c081c3c5`
`35ef1c36cb`
## Version-1 retirement (Phase 2c)
Stop creating v1 monitors, delete the v1 migration path and its step
fields, drop `steps_version` from the schema.
`b58b03077d` `75984ad222` `68a33e885d` `a2bffae7b7`
## Run detail — evidence, scoped to the step
Implements §5.3 of `docs/synthetics/step-failure-evidence-design.md`
("render the bundle, scoped to the step"), which was specified and never
built: only the kind-grouped run-level Evidence tab shipped.
**Per-step page activity.** The expanded step now shows the
`evidence.ndjson` events attributed to it — severity-ranked, capped at
5, with "View all N →" deep-linking into the Evidence tab pre-filtered
to that step. One shared `OTable` wrapper renders rows for both surfaces
so they cannot drift, and one composable fetches the bundle once for
both.
`a483796d57` `a7cbe0a3e0` `f63973285e` `eb7ec4f3e6` `1ed226febe`
`e3d969bcb2`
**Settle signals were attributed to the wrong step.**
`failure_detail.settle_signals` accumulates across the whole journey. On
live run `3HFCZ3fm` it held 13 signals — one from step 2, six from step
8, two from step 14 — while the failed step 15 owned none of them, and
the panel rendered all 13 under step 15. The signal that mattered
(`stale POST **/_search_stream, 30274ms`) belongs to step 14, which the
run's own error string says, and step 14 displayed nothing.
`last_attempt_steps[i].settle_signals` had carried correct per-step
attribution all along and was read by nothing.
`6ba6f8d633`
**Artifact URLs carried no origin.** `artifactUrl()` returned a bare
path while every other call in that service goes through `http()` with
`baseURL: store.state.API_ENDPOINT`. Artifact URLs go to `<img src>` and
`fetch()` directly, so in dev they resolved against Vite on `:8081`,
which has no `/api` route and no proxy — every screenshot and evidence
bundle 404'd. Verified against a running backend with a real key:
`:8081` → 404, `:5080` → 200. Production, served from the API's own
origin, is unchanged.
`baa7407cb5`
**The locator ladder was invisible.** `candidates_tried` lists only the
rungs the probe stood on, so a step that matched on its primary looked
identical to a step that had no second candidate — both one row, both a
grey `used_as_primary` badge. On live run `3HGDMrlU` the failing assert
had one authored locator and every `assert`/`fill` step in that journey
is built the same way, while `click` steps average 2.4 candidates. The
section now leads with "N of M tried", names a one-rung ladder as having
no fallback, labels outcomes in words instead of enum values, and dims
unreached rungs the way the timeline dims a skipped step.
`c05e1ce133`
## Test plan
- `npx vitest run src/views/synthetics src/components/synthetics/results
src/components/synthetics/StepEvidence.spec.ts
src/composables/synthetics src/composables/useSyntheticEvidence.spec.ts
src/services/synthetics.spec.ts` → 422 passing
- `npm run type-check` → clean
- `npm run lint:design:strict` → 2 pre-existing regressions above (see
Known failing)
- Behaviour on the run-detail changes was verified against live records
from `OpenObserve Sys Query` and `OpenObserve Cloud Happy Path` rather
than fixtures alone
## Not covered
- The "Add a fallback locator" action from the locator-ladder work needs
a step-level deep link; `synthetics/edit/:id` has no step anchor, so the
warning states the finding without an action.
- Browser-level confirmation of the artifact-URL and locator-ladder
changes is outstanding — the local backend's search service was
returning `tcp connect error` during verification, so those two were
confirmed against records and by unit test rather than in a running
page.
|
||
|
|
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> |
||
|
|
1112423ea8 | fix: dashboard table chart width issue and color styling issue (#13567) | ||
|
|
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>
|
||
|
|
e85baca92d |
fix: logs timechart required-fields error and URL refresh failure (#13244)
## Summary Fixes #12897 — two defects on the Logs page **Timechart** tab: 1. The error **"Please select required fields to render the chart"** appeared even though the chart should render. 2. **Refreshing the URL** broke the tab entirely: a *"Select \* query is not supported for visualization"* toast plus a permanent **No Data** state. ## Root cause ### 1. False "required fields" error (regression) `convertPanelData()` throws this error when a chart-type panel has empty `x`/`y` fields. The check was introduced in #10305, then **commented out in #11297 specifically because it broke the logs visualize toggle** (the NOTE explaining that was left in the code), and then **re-enabled in #11586**, which reintroduced the bug. The Timechart drives a **custom-query** panel whose axes are populated *asynchronously* from the result schema (`resetFields()` → `result_schema` network call → repopulate). Empty `x`/`y` is therefore a legitimate transient state for this panel — any render that landed inside that window (or after an extraction early-exit) hit the throw and pushed the error into the errors panel. ### 2. URL refresh broken Two ordering problems on page load: - `handleBeforeMount()` restores `logsVisualizeToggle` from the URL **before** the stream selection is restored, so the visualize toggle watcher fired with an empty stream and built literally `select * from "undefined"` → rejected with the SELECT \* toast (confirmed by instrumenting the running app). - Nothing ever re-ran the visualization after URL restoration completed — `loadVisualizeData()` only loads stream fields — so the tab stayed on **No Data** forever. ## The fix | Change | File | What it does | |---|---|---| | Scope the empty-fields guard to builder-mode panels (`!query.customQuery`) | `web/src/utils/dashboard/convertPanelData.ts` | Builder panels keep the protection (the panel editor's own validation also still blocks empty applies); custom-query panels (logs Timechart, alerts preview) are no longer blocked by their transient empty-fields state | | Bail out of the visualize toggle watcher when no stream is selected yet | `web/src/plugins/logs/Index.vue` | Stops the premature page-load run from building `select * from "undefined"` and toasting | | Run the visualization after URL restoration completes | `web/src/plugins/logs/Index.vue` (`setupLogsTab`) | Mirrors the manual-toggle setup via a shared `prepareVisualizeMode()` (quick-mode auto-enable, layout, `customQuery`, VRL copy), restores the saved chart type/config from `visualization_data` (`restoreVisualizationFromUrlOnLoad()`), then triggers the run. Scoped to the visualize toggle only — Search/build/patterns tabs untouched | | Broaden the stream-field readiness check | `web/src/utils/logs/visualizeStreamFields.ts` (new) | Extracted predicate also reloads fields when quick mode is on but `interestingFieldList` hasn't loaded yet — the state that produced SELECT \* mid-restore | ## Verification - **Unit tests (written failing-first against the unfixed code):** guard skips custom-query panels / still throws for builder panels / PromQL exempt; readiness-predicate cases. 75/75 pass across the two touched suites. - **End-to-end (local backend + dev UI + Playwright):** reproduced both symptoms on the unfixed build, then verified on this branch: run → render; chart-type switch (h-bar) → URL refresh → **chart type + config + query restored and chart rendered, zero errors**; VRL-function variant renders; dashboards builder empty-fields apply is still blocked by its validation; build-tab refresh behavior unchanged. - `eslint` clean on all touched files (the one `no-fallthrough` error in `convertPanelData.ts` pre-exists on `main`, untouched line); `vue-tsc` clean. ## Before / After Both recordings follow the identical flow: select stream → `match_all('error')` → open Timechart → switch chart type to horizontal bar → **refresh the URL**. ### Before (`main`) — refresh shows the SELECT \* toast and permanent No Data  📹 Full recording: [before-refresh-bug.webm](https://raw.githubusercontent.com/DHRUV6029/openobserve/pr-12897-assets/before-refresh-bug.webm) ### After (this branch) — refresh restores chart type, query, and renders  📹 Full recording: [after-fix-full-flow.webm](https://raw.githubusercontent.com/DHRUV6029/openobserve/pr-12897-assets/after-fix-full-flow.webm) <details> <summary>Screenshots — final states and pre-refresh states on both builds</summary> Final state after refresh (unfixed build):  Final state after refresh (this branch):  Timechart renders before refresh (unfixed build):  H-bar renders before refresh (unfixed build):  Timechart on this branch:  H-bar on this branch:  </details> |
||
|
|
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> |