Supersedes #13738, whose commit is included here as the first of four.
Adds PromQL support to count and time-sliced SLOs, lets metrics streams
choose SQL or PromQL, and fixes three shipped bugs found along the way.
## Shipped bug: every SQL time-slice SLO recorded wrong history
`fetch_rows`' `SliQueryPlan::Single` arm normalized every hit with
`with_good_column: true`, but that arm serves **two** shapes and only
one projects `zo_slo_good`:
| shape | projects | result |
|---|---|---|
| count single-query | `zo_slo_good` + `zo_slo_value` | correct |
| time slice | `zo_slo_value` only | `good` defaulted to `0.0` |
So every SQL time-slice slice was classified from `0.0` instead of its
aggregate. With a positive threshold, `<`/`<=` (e.g. `p95 < 300ms`)
recorded every slice **good** — the SLO read ~100%, the budget never
burned, and no alert on it could fire. `>`/`>=` recorded every slice
**bad** and fired continuously. Shipped in v0.92.0 (`4772df1723`);
`to_row` had no time-slice test, which is how it got through.
Fixed by deriving the flag from the SLI type in a new
`single_query_rows`. Deliberately **not** fixed inside `to_row` by
falling back to `total` — that would read a count bucket whose `SUM`
came back NULL as *fully good*, inventing uptime. A test forbids it.
**Stored history is not rewritten.** It ages out within one window; an
operator can force a rebuild sooner by making a real edit (a no-op
re-save does nothing — `update` bumps only on `definition_changed`). No
auto-migration: bumping every time-slice SLO at boot would freeze every
attached alert org-wide until backfill cleared the coverage floor, and
double each org's row budget for 97 days — a self-inflicted monitoring
gap to repair a reporting error.
## PromQL support
**Time slice** carried a `query_language` field the planner ignored, so
a PromQL time-slice SLO saved cleanly and measured nothing, forever.
- `SliQueryPlan::PromQlValue` — one expression, not a good/total pair,
planned at slice ends from the same helper the count arm uses.
- The expression is passed through **verbatim**. Grouping comes from
series labels; wrapping it in `sum by (...)` the way the SQL arm injects
GROUP BY would destroy the aggregate (the sum of four pods' p95 is not a
p95).
- `promql_value_rows` **rejects** two series colliding on one
`(slice_start, group_key)` via `RejectReason::AmbiguousSeries` instead
of summing. Summing is right for counters (what `promql_rows` does) and
meaningless here. Detection is unconditional, so the ungrouped case — an
unaggregated `histogram_quantile` returning one series per instance — is
covered. Rejects are plumbed through `fetch_rows` into
`PassResult.rejected` on both the live and backfill paths.
**Count** already had a working `CountSource::PromQl` backend that no UI
could reach.
## Metrics streams choose SQL or PromQL
`language_suits_stream` refused SQL whenever `stream_type == "metrics"`,
and the form derived the language from the stream type — so metrics had
no SQL option. A metrics stream is an ordinary stream with a `value`
column, and `SELECT histogram(_timestamp, '5 minute'), avg(value) FROM
cpu_usage` is a perfectly good SLI. SQL is now valid for any stream
type; PromQL stays metrics-only. The form asks via a toggle (defaulting
to PromQL) instead of deriving.
## Validation
- `scope` refused for PromQL — a SQL `WHERE` fragment with nowhere to go
in a PromQL plan.
- Empty `query` refused for **both** languages (the SQL aggregate was
previously never validated — same silent-freeze shape).
- Expressions parsed with `promql-parser` at save, in `core` (`config`
has no parser).
- **Parse-only by design.** A grouping check requiring the root to be an
aggregation whose `by(...)` matches `group_by` wrongly rejects
`histogram_quantile(0.95, sum by (le, region)(...))` (function-call
root; `le` is a necessary label superset) and binary-op roots like `sum
by (region)(increase(a[5m])) - sum by (region)(increase(b[5m]))`. A
"warn instead" variant needs a warnings channel the save API doesn't
have.
## Also fixed
- **Time-slice comparator 422.** `SliConfig::TimeSlice.comparator` has
no serde default, so a dropdown the user never opened was omitted and
the save died with a bare 422 — blocking creation of *any* time-slice
SLO from the UI. Seeded to `<` only when blank, so a stored comparator
survives an edit.
- **SLO/alert/service-stream regressions** (the commit from #13738):
fractional-threshold serde round-trip, `SloAlertCondition` UI, alerts
table, service streams.
## Verification
- 487 config · 206 core · 45 infra · 418 frontend — all passing; clippy
`-D warnings`, `cargo fmt`, prettier, eslint, `vue-tsc` all clean.
- Driven through a real browser against a running server (isolated
instance, own data dir). All four SLI/language combinations create and
store correctly: count·PromQL, count·**SQL over metrics**,
time-slice·PromQL, time-slice·**SQL over metrics**. Also verified: the
toggle swaps field shapes both ways, scope shows for SQL and hides for
PromQL, PromQL-only hints appear only in PromQL mode, and editing a
stored SQL-over-metrics SLO reopens as SQL with its predicate intact.
- Validation rejections confirmed live with actionable messages:
PromQL-with-scope, unparseable PromQL, and an empty SQL aggregate.
Known limitation, pre-existing: `threshold` likewise has no serde
default and is not seeded, so leaving it blank still 422s. That is
genuine user error rather than an untouched-control trap; adding form
validation was out of scope.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
The translation job ran on `push: main` and opened a separate
`chore/update-translations` PR. That PR had no owner — it was never
assigned to or reviewed by a human — and it left a window where main
carried English strings with no translations.
Move the trigger to `pull_request` and commit the generated locale files
back onto the PR's own head branch, so the author who added the English
strings also owns their translations and both land in one merge.
Notes on the mechanics:
- The commit is pushed with TRANSLATIONS_APPROVE_TOKEN, not
GITHUB_TOKEN. GitHub does not trigger workflows for GITHUB_TOKEN pushes,
so the new head SHA would carry no status checks and required checks
could never pass — the PR would be wedged. Falls back to GITHUB_TOKEN if
unset.
- A guard step skips the run when the head commit is already the
generated translation commit, so the PAT push cannot ping-pong.
- Fork PRs are skipped: their branch lives in the contributor's repo and
no token here can push to it.
- concurrency flips to cancel-in-progress: true. The `false` was
main-specific (merges landed faster than a run finished); on a PR branch
nothing is committed until a run completes and the surviving run
re-derives the pending set from .translation_state.json, so a cancel
costs spend, never progress.
- The push retries with a rebase in case the author pushes concurrently.
Cost does not regress: re-translation across branches is prevented by
the committed per-key hashes in .translation_state.json, not by the
trigger, so each string is still translated once — on whichever branch
first introduces it.
Restrict npm-check-updates to in-major upgrades (--target minor --peer).
Unrestricted major bumps pushed typescript from 5.9.x to 7.0.x, which
drops the ./lib/tsc export that vue-tsc still resolves, failing the type
check with ERR_PACKAGE_PATH_NOT_EXPORTED. Majors held back are now
listed in the PR body so they stay visible for a deliberate manual
upgrade.
Rename the branch to chore/npm-update-<date> and the PR title to
chore(deps): update web npm dependencies, matching the repo convention
used by update-translations.yml instead of the automated/ prefix.
Also fix along the way:
- npm ci before checking for updates, so reported current versions are
real
- gate has_updates on ncu output instead of npm outdated, so the job no
longer runs a full build cycle only to find nothing to commit
- write scratch files to RUNNER_TEMP so an untracked outdated.json
cannot make the commit step think dependencies changed
- scope the change check to web/package.json and web/package-lock.json
- split the commit message into separate -m flags to drop YAML
indentation
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>
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en-US.json`).
🤖 Generated with automated translation workflow
Co-authored-by: github-merge-queue[bot] <118344674+github-merge-queue[bot]@users.noreply.github.com>
Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en-US.json`).
🤖 Generated with automated translation workflow
Co-authored-by: github-merge-queue[bot] <118344674+github-merge-queue[bot]@users.noreply.github.com>
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en-US.json`).
🤖 Generated with automated translation workflow
Co-authored-by: github-merge-queue[bot] <118344674+github-merge-queue[bot]@users.noreply.github.com>
Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
chore: update package.json scripts for improved type-checking
fix: adjust frontmatter regex to handle BOM characters
refactor: streamline error handling in dashboard data conversion
style: clean up comments and formatting in various files
## Summary
Replaces the AWS Translate i18n pipeline with a DeepSeek (LLM) backend,
standardizes locale files to BCP-47 naming with an `en-US` base, adds
new languages, and backfills **every locale to 100%**.
## What changed
### Translation engine (DeepSeek)
- Swap AWS Translate → DeepSeek via its OpenAI-compatible API
(`scripts/translations/`). Auth via the `DEEPSEEK_API_KEY` secret;
per-request 60s timeout.
- **Batched** translation (50/call) with **per-string validation**:
interpolation placeholders (`{count}`, `%s`, `@:key`), vue-i18n
**pluralization pipes** (`{n} Day \| {n} Days`), and literal escapes
(`{'@'}`, `{'{'}`, `{'\|'}`) are preserved or the string is rejected and
retried.
- Robust recovery: index-aligned batches, split-on-mismatch to isolate a
single bad string, temperature-perturbed retries, and rejection of
empty/whitespace output (never blanks a label). Removed the old
`TRANSLATION_MAX_KEYS` safety cap.
### Locale naming + base language
- All 15 locale files renamed to **BCP-47 hyphen** form (`en-US.json`,
`zh-CN.json`, `zh-TW.json`, `fr-FR.json`, …) — matching
`navigator.language` / `Intl` / HTML `lang`.
- **English base switched `en-gb` → `en-us`** (app code,
`fallbackLocale`, BCP-47 map, selector, defaults). British-Pound
currency and date-formatting `en-GB` usages left untouched. Existing
`en-gb` cookies fall back cleanly to English — no migration needed.
### New languages
- **Russian (`ru`), Polish (`pl`), Vietnamese (`vi`)** — fully generated
and wired into `localeFileMap`, `APP_LOCALE_TO_BCP47`, and the language
selector.
- **Traditional Chinese (`zh-TW`)** now maintained by the translator
alongside Simplified (`zh-CN`).
- RTL languages (`ar`, `fa`) intentionally **deferred** until the app
has `dir="rtl"` support.
### Full backfill (one-time)
- Every pre-existing locale filled from ~5.9k up to the full **8,269
keys** (~26.9k strings). `.translation_state.json` rebuilt complete, so
future runs are incremental.
### Nav i18n bug fix
- The **Dashboards** and **Data** nav group labels were hardcoded
English (bypassing i18n) and stayed English in every locale. Now
resolved via `t()` (`menu.dashboard`, new `menu.data`), localized and
re-resolving on language change.
## Verification
- Full web unit suite: **39,035 passed / 414 skipped / 0 failed** (1,167
files).
- `type-check` clean (0 TS errors).
- Locale registry sync spec green; all 15 files = 8,269 leaves.
- Translation engine verified live against `deepseek-v4-flash`.
## Notes
- Since all locales are now 100%, the post-merge `update-translations`
workflow will find nothing pending (no follow-up translation PR).
- `ar`/`fa` to be added with RTL support in a follow-up.
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>
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>
Follow-up tooling to
[#12946](https://github.com/openobserve/openobserve/pull/12946). Pairs
with the [ENT MCP-fixes
PR](https://github.com/openobserve/o2-enterprise/pull/2072) to complete
the loop from "engine works on one issue" → "batch of 100 fires in
parallel and lands verdicts in a reviewable table."
## Summary
- **`scripts/test-assist-ci-batch.sh`** — POSIX bash script that
dispatches the test-assist engine (in o2-enterprise) for a list of OSS
issues. Reads issue numbers from stdin or positional args. Supports
`--dry-run`, `--write-mode`, `--throttle`. Uses `gh workflow run` under
the maintainer's local `gh auth`. Each dispatch is fire-and-forget; the
engine's own concurrency group (`source_repo+issue`) enforces
one-run-per-issue-at-a-time.
- **`qa-reports/test-assist-ci-backlog-sweep.md`** — Roll-up table where
verdicts land after each batch. Documents the tier structure of the
priority-100 list, the verdict-to-action map (FIXED / STILL_PRESENT /
INCONCLUSIVE / SKIP), and the tier queries used to build the list. Table
starts empty; batches append rows.
## Usage
```bash
# One-shot: dispatch for a single issue
./scripts/test-assist-ci-batch.sh 12452
# Batch: dispatch for many issues in parallel (from stdin)
gh issue list --repo openobserve/openobserve --label "☢️ Bug" --label "Retested-Failed" --state open --limit 30 \
--json number --jq '.[].number' \
| ./scripts/test-assist-ci-batch.sh
# Dry-run to see what would fire (no dispatches)
./scripts/test-assist-ci-batch.sh --dry-run 12452 11700 12647
# Explicitly enable label+comment writes (only after soak calibration)
./scripts/test-assist-ci-batch.sh --write-mode < /tmp/batch.txt
```
## Interaction with the `Test-Assist-CI-Verified` label
The ENT engine ([PR
#2072](https://github.com/openobserve/o2-enterprise/pull/2072)) applies
the `Test-Assist-CI-Verified` label to each processed issue as factual
metadata (independent of `write_mode`). After a batch:
- [Filter checked
issues](https://github.com/openobserve/openobserve/issues?q=is%3Aissue+label%3A%22Test-Assist-CI-Verified%22)
→ the ones we've processed
- [Filter unchecked
issues](https://github.com/openobserve/openobserve/issues?q=is%3Aissue+is%3Aopen+label%3A%22%E2%98%A2%EF%B8%8F+Bug%22+-label%3A%22Test-Assist-CI-Verified%22)
→ the untouched backlog
## Test plan
- [ ] `bash -n scripts/test-assist-ci-batch.sh` — script parses
(verified locally)
- [ ] `./scripts/test-assist-ci-batch.sh --dry-run 12452` — prints "DRY:
would dispatch #12452"
- [ ] After ENT PR #2072 merges: fire a single-issue dispatch via the
script → confirm run appears in o2-enterprise Actions
- [ ] Fire the 100-issue batch → confirm verdicts populate the sweep
markdown
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
added workflow to udpate the language conversion
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Bhargav <BJP232004@GMAIL.COM>