OSelect's trigger lacked role="combobox", so a focused select wasn't
recognized as an input by the shortcut guard — pressing a single-letter
key (e.g. "s" on logs) fired the page-level shortcut instead of typing
into the select. ODialog's auto-focus also skipped selects entirely and
never re-focused after a body swap (e.g. Create/Update toggle), leaving
focus on a button where keystrokes leaked past the modal.
- OSelect: mark the trigger role="combobox" and add type-to-search so a
focused, closed trigger opens the dropdown and seeds the filter.
- ODialog: auto-focus falls back to a combobox trigger when there's no
text field, re-focuses the first field after body content swaps, and now
swallows any bare printable keydown that doesn't land in an input-like
element so page shortcuts can never fire while the dialog is open,
regardless of what has focus.
design at: cloud/org%20deletion/org-deletion-design.md
## Summary
Self-service cloud org deletion with a **recoverable grace period**,
followed by complete, retryable, auditable hard cleanup.
**Lifecycle:** `active → pending_deletion → deleting → gone`
- **Delete** soft-deletes into `pending_deletion` — data intact, org
hidden from the switcher + all API access blocked, `deleted_at`
recorded. With `grace=0` it deletes immediately (legacy path).
- A **promotion scheduler** flips expired pending orgs to `deleting`
after the grace window; the background **cleanup worker** then tears
down all data across object store, file_list, DB, OpenFGA, and billing —
per-step tracked and retried (`MAX_ATTEMPTS`).
- **Resurrect** (`_meta`/root only) flips `pending_deletion` back to
`active` — no data was ever touched.
## Key changes
- New `org_cleanup_tasks` ledger table +
`organizations.status`/`deleted_at` columns (2 migrations);
`DB_SCHEMA_VERSION` 47→48.
- `OrgStatus::PendingDeletion` cache + coordinator events + watch;
`is_blocked` middleware hides & blocks pending/deleting orgs.
- Grace window from enterprise `O2_ORG_DELETION_GRACE_PERIOD_DAYS`
(default 30), read from OSS behind `#[cfg]`.
- Cleanup worker + promotion scheduler run on **compactor** nodes;
dist-lock + atomic CAS make execution exactly-once cluster-wide. Task
rows purged immediately on hard-delete completion and on resurrect (no
retention sweep).
- Audit-trail entry to `_meta` on every status transition
(fire-and-forget).
- Resurrect endpoint (dual-impl, cloud-gated) + list surfaces
`deleted_at`/grace days.
- UI: pending-deletion badge + countdown + resurrect action,
deletion-progress dialog, Danger Zone restyle with type-to-confirm.
## RBAC
In-handler role checks (no OFGA middleware): **delete** = root OR org
Admin; **resurrect** = root only via `_meta`.
## Companion PR
Enterprise side: `openobserve/o2-enterprise` #2128 (config + ofga +
cloud billing).
## Testing
Unit suites pass; full flow live-verified end-to-end (soft-delete →
resurrect with data intact, grace=0 immediate delete, audit entries,
RBAC 403s).
> Requires ≥1 compactor node running for deletions to progress (always
true for single-binary / `ZO_NODE_ROLE=all`).
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en.json`).
🤖 Generated with automated translation workflow
Co-authored-by: oasisk <35160958+oasisk@users.noreply.github.com>
Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
## What
Adds an **"Enable usage reporting" call-to-action** to the Cloud Billing
→ Usage tab for organizations that have self-usage reporting turned off,
a **"waiting for usage data" illustrated state** for orgs that have it
on but haven't reported any usage yet, and flips the per-org default for
the setting to **OFF** so the CTA is the entry point.
Previously, when `usage_stream_enabled` was off the Usage tab showed
only the billing-cycle cards and nothing where the daily chart would be.
And when it was on but the org's `usage` stream didn't exist yet, the
panel showed a raw `Search stream not found: usage` error. Now:
- **Reporting OFF** → an illustrated empty state (`OEmptyState`,
`wave-bars`) invites the user to enable reporting. Clicking **Enable**
opens a `ConfirmDialog` explaining the per-org, reversible effect before
the merge-and-POST enable flow runs.
- **Reporting ON, no data yet** → the raw "stream not found" error is
suppressed (on this panel only) and replaced with an illustrated
**"Waiting for usage data"** state. When usage lands, the search
succeeds, the error clears, and the daily chart shows through
automatically.
- **Reporting ON, has data** → unchanged: the daily line chart
(Ingestion + Search + Pipelines) renders.
## Behavior change (please review)
`default_usage_stream_enabled()` now returns **`false` in every build**
(previously cloud builds defaulted to ON when
`ZO_USAGE_REPORT_TO_OWN_ORG` was set). `ZO_USAGE_REPORT_TO_OWN_ORG`
remains the deploy-wide master switch but no longer flips the per-org
default. This is the intentional pairing with the CTA — an "enable"
prompt only makes sense when reporting is off by default. Explicit
stored values (opt-in or opt-out) still win in every build.
## How the waiting state is scoped
The "stream not found" suppression is set via `error_config` **only on
the usage panel's schema** (`usageDailyPanelSchema.ts`) —
`custom_error_handeling: true` with no `custom_error_message` silences
PanelSchemaRenderer's own error blocks. `usage.vue` catches the panel's
`@error`, detects the stream-not-found case specifically (unrelated
query errors still surface), and overlays the graphic. No other chart is
affected.
## Testing
- `web`: 417/417 billing specs pass, incl. new coverage for the enable
CTA, merged-settings POST, the schema silencing the renderer, and the
waiting overlay showing only for stream-not-found (not other errors).
- `cargo check -p openobserve` (non-release): clean.
## Note
These commits were cherry-picked onto a fresh branch from `main` — the
original branch (`chore/usage-ui-cloud-changes`) had already merged its
base work via #13112 and carried divergent, pre-squash history that
would have produced a conflicting diff.
## Summary
Fixes#13062.
On deployments served over plain HTTP (`isSecureContext = false`) the
Clipboard API is disabled, so `writeClipboard` falls back to
`document.execCommand("copy")`. The hidden textarea was appended to
`document.body` — outside reka-ui's `FocusScope` subtree — so the
drawer's focus trap immediately reclaimed focus, cleared the textarea
selection, and `execCommand` ran on an empty selection. Chrome returns
`true` for an empty-selection copy, so no error was thrown and the
"Content Copied Successfully!" toast appeared even though nothing was
copied.
## Changes (`web/src/utils/clipboard.ts`)
**1. Append inside the dialog, not `document.body`**
```ts
const activeEl = document.activeElement as HTMLElement | null;
const container: HTMLElement =
activeEl?.closest<HTMLElement>('[role="dialog"]') ?? document.body;
container.appendChild(textarea);
```
The temporary textarea is now inserted inside the nearest
`[role="dialog"]` ancestor (the drawer's `DialogContent`). The
`FocusScope` trap does not fire for focus changes that stay within its
own subtree, so the selection survives until `execCommand` runs.
**2. Gate the copy on focus still being held**
```ts
const focused = document.activeElement === textarea;
const copied = focused && document.execCommand("copy");
if (!copied) throw new Error("execCommand copy failed");
```
If focus was reclaimed (edge case) the copy is a known no-op; we throw
so `copyToClipboard` shows an error toast instead of a false-success.
**3. Fall through from `navigator.clipboard` on permission error**
The `navigator.clipboard.writeText()` call is now wrapped in its own
`try/catch` so a `NotAllowedError` (permission denied) falls through to
the `execCommand` path rather than propagating as an unhandled
rejection.
## Test plan
- [ ] HTTP deployment (non-secure context): open Source Details drawer →
click "Copy to clipboard" → paste confirms content is copied; no
false-success toast on failure
- [ ] HTTPS / localhost: `navigator.clipboard` path still used;
behaviour unchanged
- [ ] Deny clipboard permission in browser settings on HTTPS: falls
through to `execCommand` path gracefully
Co-authored-by: Abhay Padamani <141122205+ktx-abhay@users.noreply.github.com>
Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
## Summary
Fixes the "pin dashboard to Home" feature going stale when the pinned
dashboard is **moved** or **deleted**.
- **o2-enterprise#2139** (P1) — pin a dashboard, delete it, click the
pin → **404**.
- **o2-enterprise#2140** (P2) — pin a dashboard in Folder B, move it to
Folder A, click the pin → navigates to the **stale folder**.
### Root cause
The pin is stored as an org setting `home_dashboard` holding a static
snapshot `{ dashboardId, folderId, label }`, written once at pin-time
and never reconciled with the dashboard's live state. Both issues are
instances of the same class: a stored reference that drifts out of sync
on move/delete/rename.
## Approach — defense in depth
**L1 — authoritative server-side reconciliation (root fix).**
`move_dashboard` / `delete_dashboard` (OSS
`src/service/dashboards/mod.rs`) now keep the `home_dashboard` org
setting in sync at the mutation source:
- **move** → update the stored `folderId` to the destination folder.
- **delete** → clear the setting.
This is authoritative across every client and API caller (incl. **bulk
delete**, which loops `delete_dashboard`). Reconciliation is
**best-effort** — it runs *after* the primary op succeeds and
logs-and-swallows any error, so it can never fail the move/delete. No
`folder_id` is added to the GET response (no folder-membership info
leak).
**L2 — read-side re-sync (cross-system convergence).** The FE re-reads
the now-authoritative setting when the pinned tab is opened and when the
Dashboards page mounts, so a snapshot that went stale on **another
system** self-corrects before navigation.
**L3 — graceful degradation.** A deleted pin clears + toasts instead of
routing to a 404 (both the Home pinned tab and the Dashboards shortcut
button). Deliberate unpin (× button) stays silent.
## Deferred (follow-ups, out of scope)
- **Super-cluster cross-region reconciliation** — the replication queue
consumer (`src/super_cluster_queue/dashboards.rs`) applies moves/deletes
via table ops directly (bypassing the service layer where L1 lives), and
`system_settings` is not super-cluster-replicated. In a multi-region
deployment the non-originating region's pin isn't reconciled on **move**
(delete still converges via L3). Follow-up: add the same reconcile calls
to the queue consumer.
- **403-on-move (RBAC)** — moving a pin into a folder the current user
can't access. L3 handles 404 only for now; whether to clear the org-wide
setting vs. hide per-user is a separate design.
## Testing
- **Backend:** 6 new unit tests for the reconcile decision logic
(match/no-match, folder-changed, malformed value). `cargo test -p
openobserve home_pin` ✅
- **Frontend:** extended `HomeView.spec.ts` — re-sync on tab activation,
error toast on unavailable, silent deliberate unpin. `HomeView` +
`PinnedDashboardTab` specs: **40 passing** ✅
- **rcheck:** `cargo fmt --all --check` + `cargo clippy -- -D warnings`
clean ✅
- The `openHomeDashboard` shortcut guard is a small guard mirroring the
already-tested `PinnedDashboardTab` unavailable path; it's covered by
manual repro rather than a brittle full-mount of the 1400-line
`Dashboards.vue`.
### Manual reproduction
- **#2140:** pin in B → move to A → pin opens in **A** (correct
URL/breadcrumb); setting shows `folderId: "A"`.
- **#2139:** pin → delete → click pin/shortcut → no 404, toast,
pin/tab/shortcut gone; setting removed.
- **Cross-system:** move on client B while client A sits on Home/AI tab
→ A opens the pinned tab → resolves to the new folder.
## Layer compliance
All changes are OSS and unconditional (no enterprise gating, no
migrations, no new HTTP handlers) — `home_dashboard` is a generic system
setting readable by both builds. Reconciliation lives in the service
layer and reuses `src/service/db/system_settings.rs`.
## What
Adds a **daily usage view** to Cloud Billing → Usage for orgs with usage
reporting enabled, layered on top of the existing billing-cycle cards.
Cloud-only.
### Frontend (cloud billing components)
- **Merged into the existing Usage tab** (no separate tab): the billable
usage cards are always shown; when self-usage reporting is on, a
dashboards **date-range picker** (in the toolbar next to the GB/MB
toggle) drives **both** the cards and a daily chart.
- **Daily line chart** — a single query via `PanelSchemaRenderer` over
the org's own `usage` stream, broken down by event into **Ingestion /
Search / Pipeline / Remote Pipeline** lines (connect-nulls on).
- **Six billable usage tiles** on one row with **automatic unit
scaling** (GB→TB→PB / MB→GB→…) so large numbers stay readable; AI
Credits shown as a count.
- Cards re-fetch over the calendar range (derived `<N>days`, hardened
against bad values).
### Backend (all cloud-gated)
- `usage_stream_enabled` **defaults to `ZO_USAGE_REPORT_TO_OWN_ORG`** in
cloud builds, so orgs report to their own `usage` stream by default; a
per-org opt-out persists an explicit `false`. OSS / self-hosted default
stays `false`.
- **Reserved self-reporting streams** (`usage`, `stats`, `triggers`,
`errors`, `data_retention_usage`) are blocked from user **create /
ingest / delete** — cloud-only; the internal self-reporting writer
(`IngestionRequest::Usage`) is exempt.
- `get_org_usage`: a **missing `usage` / `data_retention_usage` stream
is tolerated** (treated as zero) instead of failing the whole request
with a 400.
## Notes
- No enterprise-repo or DB-migration changes; backend logic stays in OSS
behind `#[cfg(feature = "cloud")]`, per the OSS/Enterprise layer map.
- `Cargo.toml` / `Cargo.lock` are intentionally **not** included (local
`downloadO2.sh` dev-setup).
## Testing
- `cargo check` (OSS) and `cargo check --features cloud` — pass; `cargo
fmt` clean.
- Frontend: **411 billing unit tests** pass; `vue-tsc` typecheck clean;
production build succeeds.
1. Variable dropdown closes automatically after selecting "Select All"
in multi-select mode.
2. not able to unselect all.
3. for multi select dropdown, requires to click 2 times to close
dropdown list.
- #13102
## Logging: dev filtering + production strip
### Setup
1. Add to `web/.env` (git-ignored):
```ini
VITE_DEBUG_GROUPS=dashboards,auth:* # comma-separated, * wildcard
supported
```
2. Run `npm run dev` (editing `.env` auto-restarts Vite — just reload
the page).
3. Log with the feature group as the first argument:
```ts
console.debug('dashboards', 'panel refreshed', panelId);
```
4. In DevTools, enable the **Verbose** level to see `console.debug`.
### Default behavior (`VITE_DEBUG_GROUPS` unset)
- **Dev:** all `console.log/debug/info` are hidden — logging is opt-in
per feature group.
- **Production build:** `console.log/debug/info` are always stripped
from the bundle, regardless of env.
- `console.error` / `console.warn` are never filtered or stripped.
Design At: designs/ui/feature-group-console-logging.md
## What changed
- **Removed** the native `scrollIntoView()` call from `SpanBlock.vue`'s
`onMounted` hook, which was fighting the user's manual scroll in a
virtualized list.
- **Added** `cancelScroll()` to `TraceTree.vue` — clears pending
timeouts and aborts the TanStack virtualizer retry loop by resetting
`currentScrollToIndex`.
- **Wired cancellations** in `TraceDetails.vue` — calls
`traceTreeRef.value?.cancelScroll?.()` when the sidebar closes, trace
details reset, or a new trace is loaded.
- **Added `onBeforeUnmount`** cleanup in `TraceTree.vue` so a queued
scroll can't fire against a torn-down virtualizer.
- **Added regression tests** in `SpanBlock.spec.ts`,
`TraceTree.spec.ts`, and `TraceDetails.spec.ts` covering all
scroll-cancellation paths.
## Why
When `TraceDetails.vue` is opened with a `span_id` query parameter,
`SpanBlock.vue` called `element.scrollIntoView()` on mount. Because rows
are virtualized via `@tanstack/vue-virtual`, a `SpanBlock` remounts
every time it scrolls into the viewport — so each remount snapped the
viewport back to the pre-selected span, fighting the user's manual
scroll. The issue only manifested when a `span_id` was pre-selected at
page load.
## What changed
- **`web/src/plugins/traces/SearchBar.vue`**: Fixed the Run Query button
by changing `variant="ghost"` to `variant="primary"`, restoring its
primary action styling (matches Logs SearchBar behavior).
- **`web/src/components/TenstackTable.vue`**: Fixed skeleton loader
background color to match the logs version. Added
`bg-(--o2-log-table-row-bg)` and `border-(--o2-log-table-row-border)`
tokens to skeleton rows. Replaced inline shimmer gradients with a
reusable `.o2-skel-pill` CSS class that uses OTable skeleton token
variables (`--color-skeleton-base`, `--color-skeleton-highlight`).
## Why
The Traces Run Query button had lost its primary styling and appeared
visually broken — it was rendering as a ghost button instead of the
prominent primary action button. The generic `TenstackTable.vue`
skeleton loaders had a different background color than the logs-specific
`TenstackTable.vue`, causing visual inconsistency.
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
## What
Lets an org pin a single dashboard as a tab on the **Home** page for the
whole organization. The chosen dashboard is stored server-side (per org)
so it persists across devices/browsers, and every member of the org sees
the same home tab.
## How
- **New `useHomeDashboard` composable** — single-value, server-backed
store using the existing `settings/v2` KV API (`setting_key:
"home_dashboard"`, org scope). Optimistic writes with revert-on-failure
(403 → permission toast), a falsy-org guard, and label auto-sync that
persists.
- **Loads in the existing `getOrganizationSettings()` hook** (boot + org
switch), so it is naturally **org-scoped** — switching orgs shows that
org's home dashboard, with no cross-org leak.
- **HomeView** renders the single home tab (read-only
`PinnedDashboardTab` embedding `RenderDashboardCharts` with its own time
picker); the tab × removes it.
- **Set / remove** from the dashboard list ⋮ overflow menu and the
ViewDashboard header button, with an at-a-glance indicator on the home
dashboard.
## Scope
- **Frontend only — no backend changes.** Reuses the existing
`system_settings` table + `settings/v2` handler + its RBAC gate.
- Single value (max 1); pinning a new dashboard replaces the previous
one.
- Permissions are enforced by the settings API (a 403 reverts the
optimistic change + toasts) — no client-side RBAC logic.
## Testing
- **74 unit tests**: composable (optimistic/revert/403/falsy-org
guard/label persist), HomeView (single tab render, ×
pointerdown-activation regression), PinnedDashboardTab (time/panel
contracts, µs time shape, tab-id provide).
- **Verified live against a local server**: set → server persists →
**fresh browser context with no localStorage** renders the tab → replace
swaps the single value → remove clears it. **Org isolation confirmed**
(`default` vs `demo` resolve independent values).
- 0 type errors in touched files.
Single squashed commit; 10 files, +1154/-11.
---------
Co-authored-by: ktx-abhay <abhay.padamani@kiara.tech>
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en.json`).
🤖 Generated with automated translation workflow
Co-authored-by: github-merge-queue[bot] <118344674+github-merge-queue[bot]@users.noreply.github.com>
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en.json`).
🤖 Generated with automated translation workflow
Co-authored-by: github-merge-queue[bot] <118344674+github-merge-queue[bot]@users.noreply.github.com>