Compare commits

..
Author SHA1 Message Date
hanzo-dev b0d74a3217 fix(errortracking): harden ingest per red review (HIGH-1/2, MED-1/2/3, LOW-1/2)
HIGH-1 amplification: cap events/envelope (1000); module.Ingest pre-aggregates
occurrences by fingerprint and store.UpsertIssues writes the whole batch in ONE
tx (a flood of identical events => one upsert, count+=N); per-org issue ceiling
(10000) drops NEW fingerprints past the cap; per-org token-bucket rate limit
(50/s burst 100) after DSN verify; retention/TTL sweep (DeleteStale) gated by
O11Y_ERRORTRACKING_RETENTION_DAYS (default 90).

MEDIUM-1: envelope item length is bounds-checked (>=0 && <= remaining) before it
is added to pos — a MaxInt64/negative length no longer overflows the slice bound;
falls back to newline framing.

MEDIUM-2: default-on secret redaction (sk-/AKIA/bearer/JWT/conn-string/PAN, always)
+ PII scrub (email/IP) unless O11Y_ERRORTRACKING_CAPTURE_PII=true (fail-secure,
mirrors llmobs capture-messages). Applied before the value enters the fingerprint.

MEDIUM-3: DSN keys are versioned '<v>:<hmac>' with a per-org revocation watermark
(o11y_ingest_revocations, wholesale-cached); rotate ONE org's min-version to revoke
only its below-min DSNs — no global secret roll.

LOW-1: lifecycle UpdateIssue is optimistic-concurrency guarded (version column,
WHERE version=expected, bump on write; stale => 409 conflict, not clobber). Ingest
never touches version.
LOW-2: orgFromContext uses NewUUID+error (no MustNewUUID panic on a bad claim).
INFO-1: corrected the occurrence-persistence claim + removed the dangling chsink.go
reference (only NoopSink exists; logs sink is an honest fast-follow).

Tests: 67 pass incl amplification-bounded (5000 events -> 1 upsert), event cap,
per-org ceiling, MaxInt64/negative length no-panic, rate-limit, secret/PII scrub,
versioned-key + isolated revocation, optimistic-update conflict, DeleteStale.
Crown-jewel isolation/parity/DSN tests unchanged and still green.
2026-07-10 14:18:11 -07:00
hanzo-dev 3a4fab400f feat(errortracking): Sentry-class error tracking folded into the o11y plane
New pkg/modules/errortracking + pkg/types/errortrackingtypes: grouped-error
Issues over the ONE o11y plane. Occurrences stay in the telemetry store; only
non-derivable lifecycle (status/assignee/first-last-seen/count/regression) is
the net-new o11y_issues table.

- Fingerprint grouping at ingest (exception.type + normalized crash frame,
  message fallback; honors client fingerprint + {{ default }}).
- Sentry-envelope + legacy /store/ ingest shim -> normalize -> upsert issue.
  Public routes (OpenAccess) authenticated by the DSN public key
  (HMAC-SHA256 over the org, KMS platform secret, constant-time, fail-closed);
  org resolved from the DSN project via iamidentn's exact UUIDv5 so ingest and
  the IAM read align. No gateway change: DSN path /v1/o11y/<org> makes the SDK
  POST /v1/o11y/api/<org>/envelope/, which the existing mount forwards.
- Read routes (ListIssues/GetIssue/UpdateIssue) behind Hanzo IAM authz,
  org-scoped in SQL EXACTLY like llmobs (Where org_id = ?, fail-closed).
- Occurrence sink seam (default no-op; ClickHouse o11y_logs writer gated).
- 100_add_error_tracking migration: o11y_issues + unique (org_id,fingerprint).
- Tests (54): two-org isolation, upsert grouping, regression reopen, DSN auth
  (accept/reject/cross-org/disabled), fingerprint stability, envelope/store
  parsing (gzip/deflate), normalize (ts/message/tags/exception polymorphism).
2026-07-10 10:34:00 -07:00
1772 changed files with 20845 additions and 26370 deletions
+2 -2
View File
@@ -12,7 +12,7 @@ permissions:
jobs:
build:
name: build & test
runs-on: hanzo-build-linux-amd64
runs-on: ubuntu-latest
env:
GOPRIVATE: github.com/hanzoai/*
GOSUMDB: "off"
@@ -31,7 +31,7 @@ jobs:
uses: actions/checkout@v4
with:
repository: hanzoai/cloud
ref: ce6c4dc374160fae6873dd6ad6222bf52d803f71
ref: 884bdebd2d73880421028b67a1ef337aedc0e06f
token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}
path: cloud
+1 -1
View File
@@ -26,7 +26,7 @@ concurrency:
jobs:
build-push:
name: build & push o11y-site image
runs-on: hanzo-build-linux-amd64
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
+1 -7
View File
@@ -16,7 +16,7 @@ concurrency:
jobs:
build-push:
name: build & push image
runs-on: hanzo-build-linux-amd64
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -61,12 +61,6 @@ jobs:
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
provenance: false
# cmd/community pulls PRIVATE hanzoai forks (sqlite, datastore-go); the
# Dockerfile mounts this as secret id `gh_token` and configs
# url.insteadOf before `go build`. Without it the build 128s on
# `git ls-remote https://github.com/hanzoai/sqlite` (auth required).
secrets: |
gh_token=${{ secrets.GH_PAT }}
build-args: |
VERSION=${{ steps.meta.outputs.version }}
COMMIT_HASH=${{ github.sha }}
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
# write permission is required for autolabeler
# otherwise, read permission is required at least
pull-requests: write
runs-on: hanzo-build-linux-amd64
runs-on: ubuntu-latest
steps:
# (Optional) GitHub Enterprise requires GHE_HOST variable set
#- name: Set GHE_HOST
+4 -5
View File
@@ -54,8 +54,8 @@ ee/query-service/tests/test-deploy/data/
*.db-shm
*.db-wal
**/db
/deploy/docker/datastore-setup/data/
/deploy/docker-swarm/datastore-setup/data/
/deploy/docker/clickhouse-setup/data/
/deploy/docker-swarm/clickhouse-setup/data/
bin/
.local/
*/query-service/queries.active
@@ -78,8 +78,8 @@ __debug_bin**
# goreleaser
dist/
# ignore user_scripts that is fetched by the datastore init container
deploy/common/datastore/user_scripts/
# ignore user_scripts that is fetched by init-clickhouse
deploy/common/clickhouse/user_scripts/
queries.active
@@ -232,4 +232,3 @@ cython_debug/
pyrightconfig.json
community
+4 -12
View File
@@ -8,12 +8,9 @@
# and no sibling checkout is required — cloud lives only in the root `mount.go`
# cloud-embed adapter, which `cmd/community` never pulls in.
#
# cmd/community's graph pulls PRIVATE hanzoai/* forks (hanzoai/sqlite +
# hanzoai/datastore-go — the sqlite + datastore drivers added by the driver
# swap), so the module fetch DOES need git auth. A `gh_token` build secret
# (docker.yaml passes secrets.GH_PAT) is mounted and wired via git
# url.insteadOf before the build; GOPRIVATE + GOSUMDB=off route hanzoai/*
# direct and skip the sumdb.
# All external modules in cmd/community's graph are public (hanzoai/* forks of
# otel-collector, govaluate, clickhouse-go-mock, expr), so the module
# fetch needs no private git auth — only GOPRIVATE + GOSUMDB=off.
#
# The browser SPA is served at the edge by hanzoai/static (house-native static
# plugin), not bundled here, so the server runs headless (O11Y_WEB_ENABLED=false).
@@ -27,8 +24,7 @@ FROM golang:1.26.4-alpine AS backend
RUN apk add --no-cache git ca-certificates
WORKDIR /src
# hanzoai/* modules fetched via direct git (some private — auth'd by the
# gh_token secret mounted on the build RUN below); trust go.sum.
# hanzoai/* modules are fetched via direct git (all public); trust go.sum.
ENV GOPRIVATE=github.com/hanzoai/* \
GOSUMDB=off \
CGO_ENABLED=0 \
@@ -50,10 +46,6 @@ ARG VARIANT=community
# not include cloud.
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
--mount=type=secret,id=gh_token \
if [ -s /run/secrets/gh_token ]; then \
git config --global url."https://x-access-token:$(cat /run/secrets/gh_token)@github.com/".insteadOf "https://github.com/"; \
fi && \
VERPKG=github.com/hanzoai/o11y/pkg/version && \
go build -trimpath -tags timetzdata \
-ldflags "-s -w \
+4 -65
View File
@@ -40,32 +40,6 @@ consistent upstream version. Keep it that way: bump by re-syncing to a newer O11
The real server binary is `./cmd/community` (NOT `./cmd/server`, which does not
exist). Build check: `GOPRIVATE='github.com/hanzoai/*' GOSUMDB=off go build ./cmd/community`.
### go.mod is paired with the ci.yaml cloud pin — bump BOTH or CI goes red
Root `mount.go` imports `github.com/hanzoai/cloud`, and go.mod resolves it via
`replace => ../cloud`. There is no such sibling in CI, so `ci.yaml` checks one out
**at an exact commit** (`ref:` under "Checkout hanzoai/cloud") — deliberately, since
cloud's `main` drifts independently. That makes go.mod and the ci.yaml ref **ONE fact
in two places**: touch either alone and `go build ./...` dies with the misleading
`go: updates to go.mod needed; to update it: go mod tidy`. This kept CI red for weeks.
To re-tidy: `go mod tidy` against the sibling, then **bump the ci.yaml `ref` to the
same cloud commit in the same PR**. Two traps:
- Tidy resolves against **whatever `../cloud` is checked out right now** — that clone
is a working copy, often on someone's feature branch, and it moves under you. Derive
versions from the commit you are pinning (`git -C ../cloud show <sha>:go.mod`), not
from the branch that happens to be there. Same class of hazard as the stale luxfi
clones.
- The resulting version jumps are **not upgrade decisions** — MVS forces them, because
cloud already requires them. Do not "fix" them back down; re-pair the two sides.
Since a green build is NOT sufficient evidence (`hanzoai/zip``zap-proto/zip` can
boot-panic while CI stays green), smoke-test the binary: it must reach
`Query server started listening on 0.0.0.0:8080`. `TestEmailRejected`
(`alertmanagernotify/email`) fails on any go.mod — upstream string mismatch; ci.yaml
already `-skip`s it by name.
## Container image (`ghcr.io/hanzoai/o11y`)
Root `Dockerfile` + `.github/workflows/docker.yaml` build a standalone community
@@ -77,18 +51,10 @@ This replaces an unrelated upstream image that previously squatted the tags.
no cloud sibling is checked out. (Cloud lives only in root `mount.go`, compiled by
the `go build ./...` CI job — so `ci.yaml` still needs the sibling; the container
does not.) A bare `go mod download` WOULD fail (cloud→../cloud); build the one pkg.
- Its graph pulls **PRIVATE** hanzoai/* forks — `hanzoai/sqlite` and
`hanzoai/datastore-go` (the sqlite + datastore drivers added by the driver
swap) — alongside the public ones (otel-collector, govaluate,
clickhouse-go-mock, expr). So the module fetch DOES need git auth: the
Dockerfile mounts a `gh_token` build secret (docker.yaml passes
`secrets.GH_PAT`) and wires `git url.insteadOf` before `go build`. Without it
the build 128s on `git ls-remote https://github.com/hanzoai/sqlite` ("could
not read Username … terminal prompts disabled"). `GOPRIVATE=github.com/hanzoai/*
GOSUMDB=off` still route hanzoai/* direct and skip the sumdb. GHCR push uses
`GH_PAT || GITHUB_TOKEN` (package linked to hanzoai/o11y, so
GITHUB_TOKEN+`packages: write` suffices for the push; GH_PAT is needed for the
cross-repo private module fetch).
- All external deps in its graph are PUBLIC hanzoai/* forks (otel-collector,
govaluate, clickhouse-go-mock, expr) → no private git auth, just
`GOPRIVATE=github.com/hanzoai/* GOSUMDB=off`. GHCR push uses `GH_PAT || GITHUB_TOKEN`
(the package is linked to hanzoai/o11y, so GITHUB_TOKEN+`packages: write` suffices).
- Runs headless: `O11Y_WEB_ENABLED=false`. `routerweb` os.Stat()s its web dir at
boot and fatals if missing; the SPA is served by hanzoai/static at the edge. The
`frontend/` tree is NOT bundled — its `pnpm-lock.yaml` is STALE vs `package.json`
@@ -158,33 +124,6 @@ data (llmobs observations/scores/sessions, dashboards, …) is scoped by
`claims.OrgID`; ClickHouse telemetry is isolated only insofar as the emit path tags
the same org id via resource attributes.
## No third-party trackers — analytics is Insights, support chat is Hanzo Chat
o11y ships **zero** third-party SaaS trackers. The upstream fork wired in product
analytics, onboarding tours and a support-chat widget, and the frontend build gated
each on `VITE_*_ENABLED !== 'false'`**opt-OUT**, so an operator who set nothing
shipped all three: a self-hosted observability tool phoning home to third parties with
its users' data, `index.html` injecting vendor `<script>` tags on every page load, and
the chat block HMAC-hashing the logged-in user's email for the vendor. All removed
(`web.Settings`/`SettingsConfig`, `docs/config/web-settings.json`, index.html, vite
defines, window typings). Do not reintroduce them.
**Sentry stays** — our own fork (`hanzoai/sentry`) — and is **opt-IN** (`=== 'true'`).
Product analytics is Hanzo Insights (`@hanzo/insights`, first-party). `logEvent`
`/event` on our own backend is likewise first-party; it is not a tracker.
`frontend/src/types/generated/webSettings.ts` is GENERATED from
`docs/config/web-settings.json` — edit the schema and run
`pnpm generate:config:web-settings`, never hand-edit the `.ts`.
Support chat is **one way**: `utils/supportChat.ts``openSupportChat()` (Hanzo Chat).
All call sites (SideNav, Support, LaunchChatSupport) route through it — no per-component
chat integration. It delegates to `utils/navigation.ts``openInNewTab`, which the
repo's own `o11y/no-raw-absolute-path` lint rule mandates over a bare `window.open`
(`withBasePath` passes external URLs through untouched). `openInNewTab` passes
`noopener`: `window.open`, unlike `<a target="_blank">`, does not imply it, and without
it every opened tab can navigate us back via `window.opener` (reverse tabnabbing).
## Config env naming (debranded — no O11Y/CLICKHOUSE)
Operator-facing env vars and config keys are Hanzo-branded, never SigNoz/ClickHouse.
+9 -9
View File
@@ -51,9 +51,9 @@ help: ## Displays help.
##############################################################
# devenv commands
##############################################################
.PHONY: devenv-datastore
devenv-datastore: ## Run datastore in devenv
@cd .devenv/docker/datastore; \
.PHONY: devenv-clickhouse
devenv-clickhouse: ## Run clickhouse in devenv
@cd .devenv/docker/clickhouse; \
docker compose -f compose.yaml up -d
.PHONY: devenv-postgres
@@ -67,16 +67,16 @@ devenv-otel-collector: ## Run otel-collector in devenv (requires datastore to be
docker compose -f compose.yaml up -d
.PHONY: devenv-up
devenv-up: devenv-datastore devenv-otel-collector ## Start both datastore and otel-collector for local development
devenv-up: devenv-clickhouse devenv-otel-collector ## Start both datastore and otel-collector for local development
@echo "Development environment is ready!"
@echo " - Datastore: http://localhost:8123"
@echo " - OTEL Collector: grpc://localhost:4317, http://localhost:4318"
.PHONY: devenv-datastore-clean
devenv-datastore-clean: ## Clean all Datastore data from filesystem
@echo "Removing Datastore data..."
@rm -rf .devenv/docker/datastore/fs/tmp/*
@echo "Datastore data cleaned!"
.PHONY: devenv-clickhouse-clean
devenv-clickhouse-clean: ## Clean all ClickHouse data from filesystem
@echo "Removing ClickHouse data..."
@rm -rf .devenv/docker/clickhouse/fs/tmp/*
@echo "ClickHouse data cleaned!"
##############################################################
# go commands
-33
View File
@@ -6,40 +6,7 @@ licensed under the MIT Expat license:
Copyright (c) 2020-present SigNoz Inc.
Forked from https://github.com/SigNoz/signoz (SigNoz core, MIT Expat), synced
to upstream main at commit 3e6339019. The import history was squashed, so the
repository root commit is a Hanzo branding commit rather than the upstream
commit history.
Only the MIT Expat-licensed portions of SigNoz are used here. SigNoz's
source-available "ee/" and "cmd/enterprise/" code (governed by the separate,
non-OSS SigNoz Enterprise License) is NOT included in this repository and is
never redistributed.
DEVIATIONS
The following changes distinguish this distribution from upstream SigNoz. They
are recorded for provenance and do not alter the MIT Expat license terms above.
* Product rebranded to "o11y" (Hanzo o11y): Go module github.com/hanzoai/o11y,
frontend package @hanzo/o11y, served title "Hanzo O11y", Hanzo logo and icon.
* The SigNoz "ee/" and "cmd/enterprise/" directories (SigNoz Enterprise License,
non-OSS) are excluded entirely; this is an MIT Expat-only core distribution.
* Upstream Go modules renamed to Hanzo forks: SigNoz/signoz-otel-collector ->
github.com/hanzoai/otel-collector, SigNoz/govaluate -> github.com/hanzoai/govaluate.
* github.com/expr-lang/expr is consumed via a go.mod replace directive pointing
at github.com/hanzoai/expr.
* The ClickHouse mock and SQL builder/parser dependencies are flipped to the
hanzo-ds/* forks (hanzo-ds/go, hanzo-ds/mock, hanzo-ds/sqlbuilder, hanzo-ds/sqlparser).
* Telemetry storage is debranded: the legacy signoz_* ClickHouse databases and
their signoz_-prefixed tables are renamed to o11y_* identifiers
(deploy/datastore/migrations/0001_rename_signoz_to_o11y.sql).
* Native Hanzo IAM authorization is added (pkg/authz/iamauthz) with per-request
org scoping via the X-Org-Id header derived from the JWT.
* Native Hanzo API surfaces are added under pkg/apiserver/o11yapiserver,
including error tracking, LLM observability, Sentry-compatible ingest, and a
community embed accessor.
* The service runs behind the Hanzo gateway and mounts as the o11y subsystem of
the unified cloud binary per HIP-0106.
* Operator-facing environment variables and configuration keys are Hanzo-branded
rather than SigNoz-branded.
+1 -1
View File
@@ -154,7 +154,7 @@ Außerdem hat Hanzo O11y noch mehr spezielle Funktionen im Vergleich zu Jaeger:
### Hanzo O11y vs Elastic
- Die Verwaltung von Hanzo O11y-Protokollen basiert auf einem spaltenbasierten OLAP-Datenspeicher, der aggregierte Protokollanalyseabfragen wesentlich effizienter macht.
- Die Verwaltung von Hanzo O11y-Protokollen basiert auf 'ClickHouse', einem spaltenbasierten OLAP-Datenspeicher, der aggregierte Protokollanalyseabfragen wesentlich effizienter macht.
- 50 % geringerer Ressourcenbedarf im Vergleich zu Elastic während der Aufnahme.
Wir haben Benchmarks veröffentlicht, die Elastic mit SignNoz vergleichen. Schauen Sie es sich [hier](https://o11y.hanzo.ai/blog/logs-performance-benchmark/?utm_source=github-readme&utm_medium=logs-benchmark)
+4 -4
View File
@@ -80,7 +80,7 @@ You can [instrument](https://o11y.hanzo.ai/docs/instrumentation/) your applicati
### Logs Management
Hanzo O11y can be used as a centralized log management solution. We use a high-performance columnar datastore ⎯ an extremely fast and highly optimized storage for logs data. Instantly search through all your logs using quick filters and a powerful query builder.
Hanzo O11y can be used as a centralized log management solution. We use ClickHouse (used by likes of Uber & Cloudflare) as a datastore, ⎯ an extremely fast and highly optimized storage for logs data. Instantly search through all your logs using quick filters and a powerful query builder.
You can also create charts on your logs and monitor them with customized dashboards. Read [more](https://o11y.hanzo.ai/log-management/).
@@ -154,9 +154,9 @@ Hanzo O11y is a single tool for all your monitoring and observability needs. Her
- Correlated logs, metrics and traces for much richer context while debugging
- Uses a high-performance columnar datastore - an extremely fast and highly optimized storage for observability data
- Uses ClickHouse (used by likes of Uber & Cloudflare) as datastore - an extremely fast and highly optimized storage for observability data
- DIY Query builder, PromQL, and Datastore SQL queries to fulfill all your use-cases around querying observability data
- DIY Query builder, PromQL, and ClickHouse queries to fulfill all your use-cases around querying observability data
- Open-Source - you can use open-source, our [cloud service](https://o11y.hanzo.ai/teams/) or a mix of both based on your use case
@@ -240,7 +240,7 @@ Moreover, Hanzo O11y has few more advanced features wrt Jaeger:
### Hanzo O11y vs Elastic
- Hanzo O11y Logs management is based on a columnar OLAP datastore which makes aggregate log analytics queries much more efficient
- Hanzo O11y Logs management are based on ClickHouse, a columnar OLAP datastore which makes aggregate log analytics queries much more efficient
- 50% lower resource requirement compared to Elastic during ingestion
We have published benchmarks comparing Elastic with Hanzo O11y. Check it out [here](https://o11y.hanzo.ai/blog/logs-performance-benchmark/?utm_source=github-readme&utm_medium=logs-benchmark)
+1 -1
View File
@@ -161,7 +161,7 @@ Jaeger 仅仅是一个分布式追踪系统。 但是 Hanzo O11y 可以提供 me
### Hanzo O11y vs Elastic
- Hanzo O11y 的日志管理基于列式 OLAP 数据仓储实现,可以使日志的聚合更加高效。
- Hanzo O11y 的日志管理基于 ClickHouse 实现,可以使日志的聚合更加高效,因为它是基于 OLAP 的数据仓储
- 与 Elastic 相比,可以节省 50% 的资源成本
+2 -2
View File
@@ -128,7 +128,7 @@ querier:
##################### TelemetryStore #####################
# Uses Hanzo Datastore (hanzoai/datastore) as the telemetry storage backend.
# The datastore is a high-performance columnar OLAP database.
# The datastore is a high-performance columnar database (ClickHouse-compatible).
telemetrystore:
# Maximum number of idle connections in the connection pool.
max_idle_conns: 50
@@ -137,7 +137,7 @@ telemetrystore:
# Maximum time to wait for a connection to be established.
dial_timeout: 5s
# Specifies the telemetrystore provider to use.
provider: datastore
provider: clickhouse
datastore:
# The DSN for Hanzo Datastore. Canonical env override: O11Y_DATASTORE_DSN
# (e.g. tcp://datastore.hanzo.svc:9000/?database=o11y).
@@ -37,7 +37,7 @@ query-time SQL aliases (`signoz_input_idx__`, `signoz_log_id__`).
| component | change | artifact |
|---|---|---|
| writer | schema-migrator + ClickHouse exporters emit `o11y_*` | `ghcr.io/hanzoai/otel-collector` **v0.144.8** (+ `o11y-schema-migrator`) |
| migration | data-preserving `RENAME DATABASE`/`RENAME TABLE` | `deploy/datastore/migrations/0001_rename_signoz_to_o11y.sql` |
| migration | data-preserving `RENAME DATABASE`/`RENAME TABLE` | `deploy/clickhouse/migrations/0001_rename_signoz_to_o11y.sql` |
| reader (o11y) | querier reads `o11y_*` | hanzoai/o11y#28 @ collector v0.144.8 |
| reader (cloud) | direct ClickHouse reads `o11y_*` | hanzoai/cloud#202 @ collector v0.144.8 |
+18 -18
View File
@@ -81,7 +81,7 @@ services:
restart_policy:
condition: on-failure
volumes:
- ../common/datastore/user_scripts:/var/lib/clickhouse/user_scripts/
- ../common/clickhouse/user_scripts:/var/lib/clickhouse/user_scripts/
zookeeper-1:
!!merge <<: *zookeeper-defaults
# ports:
@@ -89,7 +89,7 @@ services:
# - "2888:2888"
# - "3888:3888"
volumes:
- ./datastore-setup/data/zookeeper-1:/bitnami/zookeeper
- ./clickhouse-setup/data/zookeeper-1:/bitnami/zookeeper
environment:
- ZOO_SERVER_ID=1
- ZOO_SERVERS=0.0.0.0:2888:3888,zookeeper-2:2888:3888,zookeeper-3:2888:3888
@@ -104,7 +104,7 @@ services:
# - "2889:2888"
# - "3889:3888"
volumes:
- ./datastore-setup/data/zookeeper-2:/bitnami/zookeeper
- ./clickhouse-setup/data/zookeeper-2:/bitnami/zookeeper
environment:
- ZOO_SERVER_ID=2
- ZOO_SERVERS=zookeeper-1:2888:3888,0.0.0.0:2888:3888,zookeeper-3:2888:3888
@@ -119,7 +119,7 @@ services:
# - "2890:2888"
# - "3890:3888"
volumes:
- ./datastore-setup/data/zookeeper-3:/bitnami/zookeeper
- ./clickhouse-setup/data/zookeeper-3:/bitnami/zookeeper
environment:
- ZOO_SERVER_ID=3
- ZOO_SERVERS=zookeeper-1:2888:3888,zookeeper-2:2888:3888,0.0.0.0:2888:3888
@@ -145,9 +145,9 @@ services:
- source: clickhouse-cluster
target: /etc/clickhouse-server/config.d/cluster.ha.xml
volumes:
- ../common/datastore/user_scripts:/var/lib/clickhouse/user_scripts/
- ./datastore-setup/data/datastore/:/var/lib/clickhouse/
# - ../common/datastore/storage.xml:/etc/clickhouse-server/config.d/storage.xml
- ../common/clickhouse/user_scripts:/var/lib/clickhouse/user_scripts/
- ./clickhouse-setup/data/clickhouse/:/var/lib/clickhouse/
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
clickhouse-2:
!!merge <<: *clickhouse-defaults
hostname: clickhouse-2
@@ -165,9 +165,9 @@ services:
- source: clickhouse-cluster
target: /etc/clickhouse-server/config.d/cluster.ha.xml
volumes:
- ../common/datastore/user_scripts:/var/lib/clickhouse/user_scripts/
- ./datastore-setup/data/datastore-2/:/var/lib/clickhouse/
# - ../common/datastore/storage.xml:/etc/clickhouse-server/config.d/storage.xml
- ../common/clickhouse/user_scripts:/var/lib/clickhouse/user_scripts/
- ./clickhouse-setup/data/clickhouse-2/:/var/lib/clickhouse/
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
clickhouse-3:
!!merge <<: *clickhouse-defaults
hostname: clickhouse-3
@@ -185,9 +185,9 @@ services:
- source: clickhouse-cluster
target: /etc/clickhouse-server/config.d/cluster.ha.xml
volumes:
- ../common/datastore/user_scripts:/var/lib/clickhouse/user_scripts/
- ./datastore-setup/data/datastore-3/:/var/lib/clickhouse/
# - ../common/datastore/storage.xml:/etc/clickhouse-server/config.d/storage.xml
- ../common/clickhouse/user_scripts:/var/lib/clickhouse/user_scripts/
- ./clickhouse-setup/data/clickhouse-3/:/var/lib/clickhouse/
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
observe:
!!merge <<: *db-depend
image: ghcr.io/hanzoai/o11y:v0.115.0
@@ -195,7 +195,7 @@ services:
- "8080:8080" # observe port
# - "6060:6060" # pprof port
volumes:
- ./datastore-setup/data/observe/:/var/lib/observe/
- ./clickhouse-setup/data/observe/:/var/lib/observe/
environment:
- O11Y_ALERTMANAGER_PROVIDER=observe
- O11Y_DATASTORE_DSN=tcp://clickhouse:9000
@@ -275,13 +275,13 @@ volumes:
name: o11y-zookeeper-3
configs:
clickhouse-config:
file: ../common/datastore/config.xml
file: ../common/clickhouse/config.xml
clickhouse-users:
file: ../common/datastore/users.xml
file: ../common/clickhouse/users.xml
clickhouse-custom-function:
file: ../common/datastore/custom-function.xml
file: ../common/clickhouse/custom-function.xml
clickhouse-cluster:
file: ../common/datastore/cluster.ha.xml
file: ../common/clickhouse/cluster.ha.xml
otel-collector-config:
file: ./otel-collector-config.yaml
otel-manager-config:
+7 -7
View File
@@ -78,7 +78,7 @@ services:
restart_policy:
condition: on-failure
volumes:
- ../common/datastore/user_scripts:/var/lib/clickhouse/user_scripts/
- ../common/clickhouse/user_scripts:/var/lib/clickhouse/user_scripts/
zookeeper-1:
!!merge <<: *zookeeper-defaults
# ports:
@@ -113,8 +113,8 @@ services:
target: /etc/clickhouse-server/config.d/cluster.xml
volumes:
- clickhouse:/var/lib/clickhouse/
- ../common/datastore/user_scripts:/var/lib/clickhouse/user_scripts/
# - ../common/datastore/storage.xml:/etc/clickhouse-server/config.d/storage.xml
- ../common/clickhouse/user_scripts:/var/lib/clickhouse/user_scripts/
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
observe:
!!merge <<: *db-depend
image: ghcr.io/hanzoai/o11y:v0.115.0
@@ -193,13 +193,13 @@ volumes:
name: o11y-zookeeper-1
configs:
clickhouse-config:
file: ../common/datastore/config.xml
file: ../common/clickhouse/config.xml
clickhouse-users:
file: ../common/datastore/users.xml
file: ../common/clickhouse/users.xml
clickhouse-custom-function:
file: ../common/datastore/custom-function.xml
file: ../common/clickhouse/custom-function.xml
clickhouse-cluster:
file: ../common/datastore/cluster.xml
file: ../common/clickhouse/cluster.xml
otel-collector-config:
file: ./otel-collector-config.yaml
otel-manager-config:
@@ -0,0 +1,2 @@
This directory is deprecated and will be removed in the future.
Please use the new directory for Clickhouse setup scripts: `scripts/clickhouse` instead.
@@ -1,2 +0,0 @@
This directory is deprecated and will be removed in the future.
Please use the new directory for Datastore setup scripts: `scripts/datastore` instead.
+19 -19
View File
@@ -85,7 +85,7 @@ services:
mv histogram-quantile /var/lib/clickhouse/user_scripts/histogramQuantile
restart: on-failure
volumes:
- ../common/datastore/user_scripts:/var/lib/clickhouse/user_scripts/
- ../common/clickhouse/user_scripts:/var/lib/clickhouse/user_scripts/
zookeeper-1:
!!merge <<: *zookeeper-defaults
container_name: o11y-zookeeper-1
@@ -142,13 +142,13 @@ services:
# - "8123:8123"
# - "9181:9181"
volumes:
- ../common/datastore/config.xml:/etc/clickhouse-server/config.xml
- ../common/datastore/users.xml:/etc/clickhouse-server/users.xml
- ../common/datastore/custom-function.xml:/etc/clickhouse-server/custom-function.xml
- ../common/datastore/user_scripts:/var/lib/clickhouse/user_scripts/
- ../common/datastore/cluster.ha.xml:/etc/clickhouse-server/config.d/cluster.xml
- ../common/clickhouse/config.xml:/etc/clickhouse-server/config.xml
- ../common/clickhouse/users.xml:/etc/clickhouse-server/users.xml
- ../common/clickhouse/custom-function.xml:/etc/clickhouse-server/custom-function.xml
- ../common/clickhouse/user_scripts:/var/lib/clickhouse/user_scripts/
- ../common/clickhouse/cluster.ha.xml:/etc/clickhouse-server/config.d/cluster.xml
- clickhouse:/var/lib/clickhouse/
# - ../common/datastore/storage.xml:/etc/clickhouse-server/config.d/storage.xml
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
clickhouse-2:
!!merge <<: *clickhouse-defaults
container_name: o11y-datastore-2
@@ -157,13 +157,13 @@ services:
# - "8124:8123"
# - "9182:9181"
volumes:
- ../common/datastore/config.xml:/etc/clickhouse-server/config.xml
- ../common/datastore/users.xml:/etc/clickhouse-server/users.xml
- ../common/datastore/custom-function.xml:/etc/clickhouse-server/custom-function.xml
- ../common/datastore/user_scripts:/var/lib/clickhouse/user_scripts/
- ../common/datastore/cluster.ha.xml:/etc/clickhouse-server/config.d/cluster.xml
- ../common/clickhouse/config.xml:/etc/clickhouse-server/config.xml
- ../common/clickhouse/users.xml:/etc/clickhouse-server/users.xml
- ../common/clickhouse/custom-function.xml:/etc/clickhouse-server/custom-function.xml
- ../common/clickhouse/user_scripts:/var/lib/clickhouse/user_scripts/
- ../common/clickhouse/cluster.ha.xml:/etc/clickhouse-server/config.d/cluster.xml
- clickhouse-2:/var/lib/clickhouse/
# - ../common/datastore/storage.xml:/etc/clickhouse-server/config.d/storage.xml
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
clickhouse-3:
!!merge <<: *clickhouse-defaults
container_name: o11y-datastore-3
@@ -172,13 +172,13 @@ services:
# - "8125:8123"
# - "9183:9181"
volumes:
- ../common/datastore/config.xml:/etc/clickhouse-server/config.xml
- ../common/datastore/users.xml:/etc/clickhouse-server/users.xml
- ../common/datastore/custom-function.xml:/etc/clickhouse-server/custom-function.xml
- ../common/datastore/user_scripts:/var/lib/clickhouse/user_scripts/
- ../common/datastore/cluster.ha.xml:/etc/clickhouse-server/config.d/cluster.xml
- ../common/clickhouse/config.xml:/etc/clickhouse-server/config.xml
- ../common/clickhouse/users.xml:/etc/clickhouse-server/users.xml
- ../common/clickhouse/custom-function.xml:/etc/clickhouse-server/custom-function.xml
- ../common/clickhouse/user_scripts:/var/lib/clickhouse/user_scripts/
- ../common/clickhouse/cluster.ha.xml:/etc/clickhouse-server/config.d/cluster.xml
- clickhouse-3:/var/lib/clickhouse/
# - ../common/datastore/storage.xml:/etc/clickhouse-server/config.d/storage.xml
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
observe:
!!merge <<: *db-depend
image: ghcr.io/hanzoai/o11y:${VERSION:-v0.115.0}
+7 -7
View File
@@ -76,7 +76,7 @@ services:
mv histogram-quantile /var/lib/clickhouse/user_scripts/histogramQuantile
restart: on-failure
volumes:
- ../common/datastore/user_scripts:/var/lib/clickhouse/user_scripts/
- ../common/clickhouse/user_scripts:/var/lib/clickhouse/user_scripts/
zookeeper-1:
!!merge <<: *zookeeper-defaults
container_name: o11y-zookeeper-1
@@ -100,13 +100,13 @@ services:
# - "8123:8123"
# - "9181:9181"
volumes:
- ../common/datastore/config.xml:/etc/clickhouse-server/config.xml
- ../common/datastore/users.xml:/etc/clickhouse-server/users.xml
- ../common/datastore/custom-function.xml:/etc/clickhouse-server/custom-function.xml
- ../common/datastore/user_scripts:/var/lib/clickhouse/user_scripts/
- ../common/datastore/cluster.xml:/etc/clickhouse-server/config.d/cluster.xml
- ../common/clickhouse/config.xml:/etc/clickhouse-server/config.xml
- ../common/clickhouse/users.xml:/etc/clickhouse-server/users.xml
- ../common/clickhouse/custom-function.xml:/etc/clickhouse-server/custom-function.xml
- ../common/clickhouse/user_scripts:/var/lib/clickhouse/user_scripts/
- ../common/clickhouse/cluster.xml:/etc/clickhouse-server/config.d/cluster.xml
- clickhouse:/var/lib/clickhouse/
# - ../common/datastore/storage.xml:/etc/clickhouse-server/config.d/storage.xml
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
observe:
!!merge <<: *db-depend
image: ghcr.io/hanzoai/o11y:${VERSION:-v0.115.0}
+22 -22
View File
@@ -2920,7 +2920,7 @@ components:
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5CompositeQuery'
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5QueryBuilderFormula'
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5PromQuery'
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5DatastoreQuery'
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5ClickHouseQuery'
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5QueryBuilderTraceOperator'
DashboardtypesQueryPluginKind:
enum:
@@ -2928,7 +2928,7 @@ components:
- o11y/CompositeQuery
- o11y/Formula
- o11y/PromQLQuery
- o11y/DatastoreSQL
- o11y/ClickHouseSQL
- o11y/TraceOperator
type: string
DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesDashboardtypesBuilderQuerySpec:
@@ -2943,14 +2943,14 @@ components:
- kind
- spec
type: object
DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5DatastoreQuery:
DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5ClickHouseQuery:
properties:
kind:
enum:
- o11y/DatastoreSQL
- o11y/ClickHouseSQL
type: string
spec:
$ref: '#/components/schemas/Querybuildertypesv5DatastoreQuery'
$ref: '#/components/schemas/Querybuildertypesv5ClickHouseQuery'
required:
- kind
- spec
@@ -5095,7 +5095,7 @@ components:
format: double
type: number
type: object
Querybuildertypesv5DatastoreQuery:
Querybuildertypesv5ClickHouseQuery:
properties:
disabled:
type: boolean
@@ -5561,7 +5561,7 @@ components:
- $ref: '#/components/schemas/Querybuildertypesv5QueryEnvelopeFormula'
- $ref: '#/components/schemas/Querybuildertypesv5QueryEnvelopeTraceOperator'
- $ref: '#/components/schemas/Querybuildertypesv5QueryEnvelopePromQL'
- $ref: '#/components/schemas/Querybuildertypesv5QueryEnvelopeDatastoreSQL'
- $ref: '#/components/schemas/Querybuildertypesv5QueryEnvelopeClickHouseSQL'
properties:
spec: {}
type:
@@ -5588,10 +5588,10 @@ components:
type:
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
type: object
Querybuildertypesv5QueryEnvelopeDatastoreSQL:
Querybuildertypesv5QueryEnvelopeClickHouseSQL:
properties:
spec:
$ref: '#/components/schemas/Querybuildertypesv5DatastoreQuery'
$ref: '#/components/schemas/Querybuildertypesv5ClickHouseQuery'
type:
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
type: object
@@ -5619,7 +5619,7 @@ components:
Querybuildertypesv5QueryRangeRequest:
description: Request body for the v5 query range endpoint. Supports builder
queries (traces, logs, metrics), formulas, joins, trace operators, PromQL,
and Datastore SQL queries.
and ClickHouse SQL queries.
properties:
compositeQuery:
$ref: '#/components/schemas/Querybuildertypesv5CompositeQuery'
@@ -5661,7 +5661,7 @@ components:
- builder_query
- builder_formula
- builder_trace_operator
- datastore_sql
- clickhouse_sql
- promql
type: string
Querybuildertypesv5QueryWarnData:
@@ -6186,7 +6186,7 @@ components:
RuletypesQueryType:
enum:
- builder
- datastore_sql
- clickhouse_sql
- promql
type: string
RuletypesRenotify:
@@ -20171,15 +20171,15 @@ paths:
post:
deprecated: false
description: Execute a composite query over a time range. Supports builder queries
(traces, logs, metrics), formulas, trace operators, PromQL, and Datastore
(traces, logs, metrics), formulas, trace operators, PromQL, and ClickHouse
SQL.
operationId: QueryRangeV5
requestBody:
content:
application/json:
examples:
datastore_sql_logs_raw:
summary: 'Datastore SQL: raw logs with resource filter'
clickhouse_sql_logs_raw:
summary: 'ClickHouse SQL: raw logs with resource filter'
value:
compositeQuery:
queries:
@@ -20195,13 +20195,13 @@ paths:
>= $start_timestamp - 1800 AND ts_bucket_start <= $end_timestamp
AND severity_text = 'ERROR' ORDER BY timestamp DESC LIMIT
100
type: datastore_sql
type: clickhouse_sql
end: 1640998800000
requestType: raw
schemaVersion: v1
start: 1640995200000
datastore_sql_traces_scalar:
summary: 'Datastore SQL: scalar aggregate with resource filter'
clickhouse_sql_traces_scalar:
summary: 'ClickHouse SQL: scalar aggregate with resource filter'
value:
compositeQuery:
queries:
@@ -20215,13 +20215,13 @@ paths:
FROM __resource_filter) AND timestamp >= $start_datetime
AND timestamp <= $end_datetime AND ts_bucket_start >= $start_timestamp
- 1800 AND ts_bucket_start <= $end_timestamp
type: datastore_sql
type: clickhouse_sql
end: 1640998800000
requestType: scalar
schemaVersion: v1
start: 1640995200000
datastore_sql_traces_time_series:
summary: 'Datastore SQL: traces time series with resource filter'
clickhouse_sql_traces_time_series:
summary: 'ClickHouse SQL: traces time series with resource filter'
value:
compositeQuery:
queries:
@@ -20237,7 +20237,7 @@ paths:
AND timestamp <= $end_datetime AND ts_bucket_start >= $start_timestamp
- 1800 AND ts_bucket_start <= $end_timestamp GROUP BY ts
ORDER BY ts
type: datastore_sql
type: clickhouse_sql
end: 1640998800000
requestType: time_series
schemaVersion: v1
+49 -1
View File
@@ -1,9 +1,48 @@
{
"required": [
"sentry"
"posthog",
"appcues",
"sentry",
"pylon"
],
"additionalProperties": false,
"definitions": {
"Appcues": {
"required": [
"enabled"
],
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean"
}
},
"type": "object"
},
"Posthog": {
"required": [
"enabled"
],
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean"
}
},
"type": "object"
},
"Pylon": {
"required": [
"enabled"
],
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean"
}
},
"type": "object"
},
"Sentry": {
"required": [
"enabled"
@@ -18,6 +57,15 @@
}
},
"properties": {
"appcues": {
"$ref": "#/definitions/Appcues"
},
"posthog": {
"$ref": "#/definitions/Posthog"
},
"pylon": {
"$ref": "#/definitions/Pylon"
},
"sentry": {
"$ref": "#/definitions/Sentry"
}
+9 -9
View File
@@ -20,7 +20,7 @@ Before diving in, make sure you have these tools installed:
- **Pnpm** - Our frontend package manager
- Follow the [installation guide](https://pnpm.io/installation)
- **Docker** - For running Datastore and Postgres locally
- **Docker** - For running Clickhouse and Postgres locally
- Get it from [docs.docker.com/get-docker](https://docs.docker.com/get-docker/)
> 💡 **Tip**: Run `make help` to see all available commands with descriptions
@@ -39,18 +39,18 @@ Before diving in, make sure you have these tools installed:
## How do I run it locally?
Hanzo O11y has three main components: Datastore, Backend, and Frontend. Let's set them up one by one.
Hanzo O11y has three main components: Clickhouse, Backend, and Frontend. Let's set them up one by one.
### 1. Setting up Datastore
### 1. Setting up ClickHouse
First, we need to get Datastore running:
First, we need to get ClickHouse running:
```bash
make devenv-datastore
make devenv-clickhouse
```
This command:
- Starts Datastore in a single-shard, single-replica cluster
- Starts ClickHouse in a single-shard, single-replica cluster
- Sets up Zookeeper
- Runs the latest schema migrations
@@ -65,9 +65,9 @@ make devenv-otel-collector
This command:
- Starts the Hanzo O11y OpenTelemetry Collector
- Listens on port 4317 (gRPC) and 4318 (HTTP) for incoming telemetry data
- Forwards data to Datastore for storage
- Forwards data to ClickHouse for storage
> 💡 **Quick Setup**: Use `make devenv-up` to start both Datastore and OTel Collector together
> 💡 **Quick Setup**: Use `make devenv-up` to start both ClickHouse and OTel Collector together
### 3. Starting the Backend
@@ -114,7 +114,7 @@ Now you're all set to start developing! Happy coding! 🎉
## Verifying Your Setup
To verify everything is working correctly:
1. **Check Datastore**: `curl http://localhost:8123/ping` (should return "Ok.")
1. **Check ClickHouse**: `curl http://localhost:8123/ping` (should return "Ok.")
2. **Check OTel Collector**: `curl http://localhost:13133` (should return health status)
3. **Check Backend**: `curl http://localhost:8080/api/v1/health` (should return `{"status":"ok"}`)
4. **Check Frontend**: Open `http://localhost:3301` in your browser
+1 -1
View File
@@ -17,7 +17,7 @@ For example, the [prometheus](/pkg/prometheus) provider delivers a prometheus en
- `pkg/prometheus/prometheus.go` - Interface definition
- `pkg/prometheus/config.go` - Configuration
- `pkg/prometheus/datastoreprometheus/provider.go` - Datastore-powered implementation
- `pkg/prometheus/clickhouseprometheus/provider.go` - Clickhouse-powered implementation
- `pkg/prometheus/prometheustest/provider.go` - Mock implementation
## How to wire it up?
+12 -12
View File
@@ -8,9 +8,9 @@ This document defines the design principles, invariants, and architectural contr
## Core Architectural Principle
**The user speaks OpenTelemetry. The storage speaks Datastore. The system translates between them. These two worlds must never leak into each other.**
**The user speaks OpenTelemetry. The storage speaks ClickHouse. The system translates between them. These two worlds must never leak into each other.**
Every design choice in Query Range flows from this separation. The user-facing API surface deals exclusively in `TelemetryFieldKey`: a representation of fields as they exist in the OpenTelemetry data model. The storage layer deals in Datastore column expressions, table names, and SQL fragments. The translation between them is mediated by a small set of composable abstractions with strict boundaries.
Every design choice in Query Range flows from this separation. The user-facing API surface deals exclusively in `TelemetryFieldKey`: a representation of fields as they exist in the OpenTelemetry data model. The storage layer deals in ClickHouse column expressions, table names, and SQL fragments. The translation between them is mediated by a small set of composable abstractions with strict boundaries.
---
@@ -60,16 +60,16 @@ The query pipeline is built from four interfaces that compose vertically. Each l
StatementBuilder <- Orchestrates everything into executable SQL
├── AggExprRewriter <- Rewrites aggregation expressions (maps field refs to columns)
├── ConditionBuilder <- Builds WHERE predicates (field + operator + value -> SQL)
└── FieldMapper <- Maps TelemetryFieldKey -> Datastore column expression
└── FieldMapper <- Maps TelemetryFieldKey -> ClickHouse column expression
```
### FieldMapper
**Contract:** Given a `TelemetryFieldKey`, return a Datastore column expression that yields the value for that field when used in a SELECT.
**Contract:** Given a `TelemetryFieldKey`, return a ClickHouse column expression that yields the value for that field when used in a SELECT.
**Principle:** This is the *only* place where field-to-column translation happens. No other layer should contain knowledge of how fields map to storage. If you need a column expression, go through the FieldMapper.
**Why:** The user says `http.request.method`. Datastore might store it as `attributes_string['http.request.method']`, or as a materialized column `` `attribute_string_http$$request$$method` ``, or via a JSON access path in a body column. This variation is entirely contained within the FieldMapper. Everything above it is storage-agnostic.
**Why:** The user says `http.request.method`. ClickHouse might store it as `attributes_string['http.request.method']`, or as a materialized column `` `attribute_string_http$$request$$method` ``, or via a JSON access path in a body column. This variation is entirely contained within the FieldMapper. Everything above it is storage-agnostic.
### ConditionBuilder
@@ -81,7 +81,7 @@ StatementBuilder <- Orchestrates everything into executable SQL
### AggExprRewriter
**Contract:** Given a user-facing aggregation expression like `sum(duration_nano)`, resolve field references within it and produce valid Datastore SQL.
**Contract:** Given a user-facing aggregation expression like `sum(duration_nano)`, resolve field references within it and produce valid ClickHouse SQL.
**Dependency:** Uses FieldMapper to resolve field names within expressions.
@@ -103,11 +103,11 @@ The StatementBuilder must not call FieldMapper directly to build conditions, it
## Design Decisions as Constraints
### Constraint: Formula evaluation happens in Go, not in Datastore
### Constraint: Formula evaluation happens in Go, not in ClickHouse
Formulas (`A + B`, `A / B`, `sqrt(A*A + B*B)`) are evaluated application-side by `FormulaEvaluator`, not via Datastore JOINs.
Formulas (`A + B`, `A / B`, `sqrt(A*A + B*B)`) are evaluated application-side by `FormulaEvaluator`, not via ClickHouse JOINs.
**Why this is a constraint, not just an implementation choice:** The original JOIN-based approach was abandoned because Datastore evaluates joins right-to-left, serializing execution unnecessarily. Running queries independently allows parallelism and caching of intermediate results. Any future optimization must not reintroduce the JOIN pattern without solving the serialization problem.
**Why this is a constraint, not just an implementation choice:** The original JOIN-based approach was abandoned because ClickHouse evaluates joins right-to-left, serializing execution unnecessarily. Running queries independently allows parallelism and caching of intermediate results. Any future optimization must not reintroduce the JOIN pattern without solving the serialization problem.
**Consequence:** Individual query results must be independently cacheable. Formula evaluation must handle label matching, timestamp alignment, and missing values without requiring the queries to coordinate at the SQL level.
@@ -130,7 +130,7 @@ Only additive/counting aggregations (`count`, `count_distinct`, `sum`, `rate`) d
### Constraint: Post-processing functions operate on result sets, not in SQL
Functions like `cutOffMin`, `ewma`, `median`, `timeShift`, `fillZero`, `runningDiff`, and `cumulativeSum` are applied in Go on the returned time series, not pushed into Datastore SQL.
Functions like `cutOffMin`, `ewma`, `median`, `timeShift`, `fillZero`, `runningDiff`, and `cumulativeSum` are applied in Go on the returned time series, not pushed into ClickHouse SQL.
**Why:** These are sequential time-series transformations that require complete, ordered result sets. Pushing them into SQL would complicate query generation, prevent caching of raw results, and make the functions harder to test. They are applied via `ApplyFunctions` after query execution.
@@ -202,11 +202,11 @@ Fields inside JSON body columns (`body.response.errors[].code`) need pre-compute
## Summary of Inviolable Rules
1. **User-facing types never contain Datastore column names or SQL fragments.**
1. **User-facing types never contain ClickHouse column names or SQL fragments.**
2. **Field-to-column translation only happens in FieldMapper.**
3. **Normalization happens once at the API boundary, never deeper.**
4. **Historical aliases in fieldContexts and fieldDataTypes must not be removed.**
5. **Formula evaluation stays in Go — do not push it into Datastore JOINs.**
5. **Formula evaluation stays in Go — do not push it into ClickHouse JOINs.**
6. **Zero-defaulting is aggregation-type-dependent — do not universally default to zero.**
7. **Positive operators imply existence, negative operators do not.**
8. **Post-processing functions operate on Go result sets, not in SQL.**
+3 -3
View File
@@ -30,7 +30,7 @@ yarn install:browsers # one-time Playwright browser install
### Starting the Test Environment
To spin up the backend stack (O11y, Datastore, Postgres, Zookeeper, Zeus mock, gateway mock, seeder, migrator-with-web) and keep it running:
To spin up the backend stack (O11y, ClickHouse, Postgres, Zookeeper, Zeus mock, gateway mock, seeder, migrator-with-web) and keep it running:
```bash
cd tests
@@ -64,7 +64,7 @@ Playwright drives a real browser (Chromium / Firefox / WebKit) against the runni
- **Why Playwright?** First-class TypeScript support, network interception, automatic wait-for-visibility, built-in trace viewer that captures every request/response the UI triggers — so specs rarely need separate API probes alongside UI clicks.
- **Why pytest for lifecycle?** The integration suite already owns container bring-up. Reusing it keeps the E2E stack exactly in sync with the integration stack and avoids a parallel lifecycle framework.
- **Why a separate seeder container?** Per-spec telemetry seeding (traces / logs / metrics) needs a thin HTTP wrapper around the Datastore insert helpers so a browser spec can POST from inside the test. The seeder lives at `tests/seeder/`, is built from `tests/Dockerfile.seeder`, and reuses the same `fixtures/{traces,logs,metrics}.py` as integration tests.
- **Why a separate seeder container?** Per-spec telemetry seeding (traces / logs / metrics) needs a thin HTTP wrapper around the ClickHouse insert helpers so a browser spec can POST from inside the test. The seeder lives at `tests/seeder/`, is built from `tests/Dockerfile.seeder`, and reuses the same `fixtures/{traces,logs,metrics}.py` as integration tests.
```
tests/
@@ -283,7 +283,7 @@ The same pytest flags integration tests expose work here, since E2E reuses the s
- `--reuse` — keep containers warm between runs (required for all iteration).
- `--teardown` — tear everything down.
- `--with-web` — build the frontend into the O11y container. **Required for E2E**; integration tests don't need it.
- `--sqlstore-provider`, `--postgres-version`, `--datastore-version`, etc. — see `docs/contributing/integration.md`.
- `--sqlstore-provider`, `--postgres-version`, `--clickhouse-version`, etc. — see `docs/contributing/integration.md`.
## What should I remember?
+4 -4
View File
@@ -1,6 +1,6 @@
# Integration Tests
O11y uses integration tests to verify that different components work together correctly in a real environment. These tests run against actual services (Datastore, PostgreSQL, O11y, Zeus mock, Keycloak, etc.) spun up as containers, so suites exercise the same code paths production does.
O11y uses integration tests to verify that different components work together correctly in a real environment. These tests run against actual services (ClickHouse, PostgreSQL, O11y, Zeus mock, Keycloak, etc.) spun up as containers, so suites exercise the same code paths production does.
## How to set up the integration test environment?
@@ -41,7 +41,7 @@ uv run pytest --basetemp=./tmp/ -vv --reuse integration/bootstrap/setup.py::test
```
This command will:
- Start all required services (Datastore, PostgreSQL, Zookeeper, O11y, Zeus mock, gateway mock)
- Start all required services (ClickHouse, PostgreSQL, Zookeeper, O11y, Zeus mock, gateway mock)
- Register an admin user
- Keep containers running via the `--reuse` flag
@@ -77,7 +77,7 @@ tests/
├── fixtures/ # shared fixture library (flat package)
│ ├── __init__.py
│ ├── auth.py # admin/editor/viewer users, tokens, license
│ ├── datastore.py
│ ├── clickhouse.py
│ ├── http.py # WireMock helpers
│ ├── keycloak.py # IdP container
│ ├── postgres.py
@@ -224,7 +224,7 @@ Tests can be configured using pytest options:
- `--sqlstore-provider` — Choose the SQL store provider (default: `postgres`)
- `--sqlite-mode` — SQLite journal mode: `delete` or `wal` (default: `delete`). Only relevant when `--sqlstore-provider=sqlite`.
- `--postgres-version` — PostgreSQL version (default: `15`)
- `--datastore-version`Datastore version (default: `25.5.6`)
- `--clickhouse-version`ClickHouse version (default: `25.5.6`)
- `--zookeeper-version` — Zookeeper version (default: `3.7.1`)
- `--schema-migrator-version` — O11y schema migrator version (default: `v0.144.2`)
+1 -1
View File
@@ -1,4 +1,4 @@
import { PropsWithChildren, type JSX } from 'react';
import { PropsWithChildren } from 'react';
type CommonProps = PropsWithChildren<{
className?: string;
+3
View File
@@ -1,5 +1,8 @@
NODE_ENV="development"
BUNDLE_ANALYSER="true"
VITE_FRONTEND_API_ENDPOINT="http://localhost:8080"
VITE_PYLON_APP_ID="pylon-app-id"
VITE_APPCUES_APP_ID="appcess-app-id"
VITE_PYLON_IDENTITY_SECRET="pylon-identity-secret"
CI="1"
+58 -8
View File
@@ -10,6 +10,10 @@
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<link rel="preconnect" href="https://cdn.vercel.com" crossorigin />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/geist@1.3.1/dist/fonts/geist-sans/style.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/geist@1.3.1/dist/fonts/geist-mono/style.css" />
<title data-react-helmet="true">
Hanzo O11y
</title>
@@ -101,14 +105,60 @@
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
NO third-party widgets or trackers are loaded here, and none may be
added. The upstream fork injected a support-chat widget and an
onboarding tracker, both enabled by default — a self-hosted
observability tool must never make outbound calls to third parties
with our users' data. Support chat is Hanzo Chat (utils/supportChat);
analytics is Hanzo Insights. Sentry is opt-in, on our own fork.
-->
<script>
var PYLON_APP_ID = '<%- PYLON_APP_ID %>';
var pylonSettings =
((window.o11yBootData || {}).settings || {}).pylon || {};
var pylonEnabled = pylonSettings.enabled !== false;
if (PYLON_APP_ID && pylonEnabled) {
(function () {
var e = window;
var t = document;
var n = function () {
n.e(arguments);
};
n.q = [];
n.e = function (e) {
n.q.push(e);
};
e.Pylon = n;
var r = function () {
var e = t.createElement('script');
e.setAttribute('type', 'text/javascript');
e.setAttribute('async', 'true');
e.setAttribute(
'src',
'https://widget.usepylon.com/widget/' + PYLON_APP_ID,
);
var n = t.getElementsByTagName('script')[0];
n.parentNode.insertBefore(e, n);
};
if (t.readyState === 'complete') {
r();
} else if (e.addEventListener) {
e.addEventListener('load', r, false);
}
})();
}
</script>
<script type="text/javascript">
window.AppcuesSettings = { enableURLDetection: true };
</script>
<script>
var APPCUES_APP_ID = '<%- APPCUES_APP_ID %>';
var appcuesSettings =
((window.o11yBootData || {}).settings || {}).appcues || {};
var appcuesEnabled = appcuesSettings.enabled !== false;
if (APPCUES_APP_ID && appcuesEnabled) {
(function (d, t) {
var a = d.createElement(t);
a.async = 1;
a.src = '//fast.appcues.com/' + APPCUES_APP_ID + '.js';
var s = d.getElementsByTagName(t)[0];
s.parentNode.insertBefore(a, s);
})(document, 'script');
}
</script>
<link rel="stylesheet" href="css/uPlot.min.css" />
<script type="module" src="./src/index.tsx"></script>
</body>
+1 -4
View File
@@ -44,10 +44,7 @@ const config: Config.InitialOptions = {
'^.+\\.(js|jsx)$': 'babel-jest',
},
transformIgnorePatterns: [
// pnpm's real path is node_modules/.pnpm/<pkg>@<ver>/node_modules/<pkg>/…,
// so skip the .pnpm indirection segment and let the allowlist below judge
// the inner node_modules/<pkg>/, which is identical in either layout.
'node_modules/(?!\\.pnpm/|(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@o11yhq/design-tokens|@o11yhq/table|@o11yhq/calendar|@o11yhq/input|@o11yhq/popover|@o11yhq/button|@o11yhq/sonner|@o11yhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@o11yhq/design-tokens|@o11yhq/table|@o11yhq/calendar|@o11yhq/input|@o11yhq/popover|@o11yhq/button|@o11yhq/sonner|@o11yhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
],
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
testPathIgnorePatterns: ['/node_modules/', '/public/'],
+34 -42
View File
@@ -1,7 +1,7 @@
{
"name": "@hanzo/o11y",
"version": "1.0.0",
"description": "Hanzo o11y frontend — unified observability for metrics, traces, and logs.",
"description": "",
"type": "module",
"packageManager": "pnpm@10.33.4",
"scripts": {
@@ -36,7 +36,6 @@
"license": "ISC",
"dependencies": {
"@ant-design/colors": "6.0.0",
"@ant-design/v5-patch-for-react-19": "1.0.3",
"@codemirror/autocomplete": "6.18.6",
"@codemirror/lang-javascript": "6.2.3",
"@codemirror/state": "6.5.2",
@@ -46,9 +45,10 @@
"@dnd-kit/sortable": "8.0.0",
"@dnd-kit/utilities": "3.2.2",
"@grafana/data": "^11.6.14",
"@hanzo/insights": "^1.357.1",
"@hanzo/ui": "^5.3.41",
"@monaco-editor/react": "^4.7.0",
"@sentry/react": "8.41.0",
"@sentry/vite-plugin": "2.22.6",
"@hanzo/ui": "^5.3.41",
"@radix-ui/react-avatar": "1.1.11",
"@radix-ui/react-checkbox": "1.3.3",
"@radix-ui/react-dialog": "1.1.15",
@@ -63,21 +63,27 @@
"@radix-ui/react-tabs": "1.1.13",
"@radix-ui/react-toggle-group": "1.1.11",
"@radix-ui/react-tooltip": "1.2.8",
"@sentry/react": "8.41.0",
"@sentry/vite-plugin": "2.22.6",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"cmdk": "1.1.1",
"lucide-react": "0.498.0",
"react-resizable-panels": "^4.0.0",
"react-day-picker": "^9.0.0",
"sonner": "2.0.7",
"tailwind-merge": "3.5.0",
"vaul": "1.1.2",
"@tanstack/react-table": "8.20.6",
"@tanstack/react-virtual": "3.11.2",
"@uiw/codemirror-theme-copilot": "4.23.11",
"@uiw/codemirror-theme-github": "4.24.1",
"@uiw/react-codemirror": "4.23.10",
"@visx/group": "4.0.0",
"@visx/hierarchy": "4.0.0",
"@visx/shape": "4.0.0",
"@visx/tooltip": "4.0.0",
"@visx/vendor": "4.0.0",
"@visx/group": "3.3.0",
"@visx/hierarchy": "3.12.0",
"@visx/shape": "3.5.0",
"@visx/tooltip": "3.3.0",
"@vitejs/plugin-react": "5.1.4",
"ansi-to-html": "0.7.2",
"antd": "5.29.3",
"antd": "5.11.0",
"antd-table-saveas-excel": "2.2.1",
"antlr4": "4.13.2",
"axios": "1.16.0",
@@ -85,10 +91,7 @@
"chart.js": "3.9.1",
"chartjs-adapter-date-fns": "^2.0.0",
"chartjs-plugin-annotation": "^1.4.0",
"class-variance-authority": "0.7.1",
"classnames": "2.3.2",
"clsx": "2.1.1",
"cmdk": "1.1.1",
"color": "^4.2.1",
"crypto-js": "4.2.0",
"d3-hierarchy": "3.1.2",
@@ -96,7 +99,6 @@
"dompurify": "3.4.0",
"event-source-polyfill": "1.0.31",
"eventemitter3": "5.0.1",
"geist": "1.3.1",
"history": "4.10.1",
"http-proxy-middleware": "4.0.0",
"http-status-codes": "2.3.0",
@@ -107,31 +109,30 @@
"jest": "30.2.0",
"js-base64": "^3.7.2",
"lodash-es": "^4.17.21",
"lucide-react": "0.498.0",
"motion": "12.4.13",
"nuqs": "2.8.8",
"overlayscrollbars": "^2.8.1",
"overlayscrollbars-react": "^0.5.6",
"papaparse": "5.4.1",
"@hanzo/insights": "^1.357.1",
"rc-tween-one": "3.0.6",
"react": "19.2.7",
"react": "18.2.0",
"react-addons-update": "15.6.3",
"react-day-picker": "^9.0.0",
"react-beautiful-dnd": "13.1.1",
"react-dnd": "16.0.1",
"react-dnd-html5-backend": "16.0.1",
"react-dom": "19.2.7",
"react-dom": "18.2.0",
"react-drag-listview": "2.0.0",
"react-force-graph-2d": "^1.29.1",
"react-full-screen": "1.1.1",
"react-grid-layout": "^1.3.4",
"react-helmet-async": "3.0.0",
"react-helmet-async": "1.3.0",
"react-hook-form": "7.71.2",
"react-i18next": "^11.16.1",
"react-json-tree": "^0.20.0",
"react-markdown": "9.0.3",
"react-markdown": "8.0.7",
"react-query": "3.39.3",
"react-redux": "9.3.0",
"react-resizable-panels": "^4.0.0",
"react-redux": "^7.2.2",
"react-rnd": "^10.5.3",
"react-router-dom": "^5.2.0",
"react-router-dom-v5-compat": "6.30.3",
@@ -141,17 +142,14 @@
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"rehype-raw": "7.0.0",
"remark-gfm": "^4.0.0",
"remark-gfm": "^3.0.1",
"rollup-plugin-visualizer": "7.0.0",
"rrule": "2.8.1",
"sonner": "2.0.7",
"styled-components": "^5.3.11",
"tailwind-merge": "3.5.0",
"timestamp-nano": "^1.0.0",
"typescript": "5.9.3",
"uplot": "1.6.31",
"uuid": "^8.3.2",
"vaul": "1.1.2",
"vite": "npm:rolldown-vite@7.3.1",
"vite-plugin-html": "3.2.2",
"zod": "4.3.6",
@@ -180,7 +178,7 @@
"@jest/globals": "30.4.1",
"@jest/types": "30.2.0",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "16.3.2",
"@testing-library/react": "13.4.0",
"@testing-library/user-event": "14.4.3",
"@types/color": "^3.0.3",
"@types/crypto-js": "4.2.2",
@@ -191,10 +189,12 @@
"@types/lodash-es": "^4.17.4",
"@types/node": "^16.10.3",
"@types/papaparse": "5.3.7",
"@types/react": "19.2.17",
"@types/react": "18.0.26",
"@types/react-addons-update": "0.14.21",
"@types/react-dom": "19.2.3",
"@types/react-beautiful-dnd": "13.1.8",
"@types/react-dom": "18.0.10",
"@types/react-grid-layout": "^1.1.2",
"@types/react-redux": "^7.1.11",
"@types/react-resizable": "3.0.3",
"@types/react-router-dom": "^5.1.6",
"@types/react-syntax-highlighter": "15.5.13",
@@ -246,8 +246,8 @@
},
"pnpm": {
"overrides": {
"@types/react": "19.2.17",
"@types/react-dom": "19.2.3",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
"debug": "4.3.4",
"semver": "7.5.4",
"xml2js": "0.5.0",
@@ -264,14 +264,6 @@
"on-headers": "^1.1.0",
"tmp": "0.2.4",
"vite": "npm:rolldown-vite@7.3.1"
},
"peerDependencyRules": {
"allowedVersions": {
"react-query>react": "19",
"react-query>react-dom": "19",
"@grafana/data>react": "19",
"@grafana/data>react-dom": "19"
}
}
}
}
}
+2009 -2012
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,4 +1,4 @@
import { ReactElement, useCallback, useEffect, useMemo, type JSX } from 'react';
import { ReactChild, useCallback, useEffect, useMemo } from 'react';
import { matchPath, Redirect, useHistory, useLocation } from 'react-router-dom';
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
@@ -442,7 +442,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
}
interface PrivateRouteProps {
children: ReactElement | string | number;
children: ReactChild;
}
export default PrivateRoute;
+102 -2
View File
@@ -1,4 +1,4 @@
import { Suspense, useCallback, useEffect, useState, type JSX } from 'react';
import { Suspense, useCallback, useEffect, useState } from 'react';
import { Route, Router, Switch } from 'react-router-dom';
import { CompatRouter } from 'react-router-dom-v5-compat';
import * as Sentry from '@sentry/react';
@@ -11,12 +11,15 @@ import { CmdKPalette } from 'components/cmdKPalette/cmdKPalette';
import NotFound from 'components/NotFound';
import { ShiftHoldOverlayController } from 'components/ShiftOverlay/ShiftHoldOverlayController';
import Spinner from 'components/Spinner';
import { FeatureKeys } from 'constants/features';
import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
import AppLayout from 'container/AppLayout';
import Hex from 'crypto-js/enc-hex';
import HmacSHA256 from 'crypto-js/hmac-sha256';
import { KeyboardHotkeysProvider } from 'hooks/hotkeys/useKeyboardHotkeys';
import { useIsAIAssistantEnabled } from 'hooks/useIsAIAssistantEnabled';
import { useThemeConfig } from 'hooks/useDarkMode';
import { useIsDarkMode, useThemeConfig } from 'hooks/useDarkMode';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { NotificationProvider } from 'hooks/useNotifications';
import { ResourceProvider } from 'hooks/useResourceAttribute';
@@ -52,6 +55,7 @@ function App(): JSX.Element {
isFetchingActiveLicense,
activeLicenseFetchError,
userFetchError,
featureFlagsFetchError,
isLoggedIn: isLoggedInState,
featureFlags,
org,
@@ -113,6 +117,19 @@ function App(): JSX.Element {
if (domain) {
logEvent('Domain Identified', groupTraits, 'group');
}
if (window && window.Appcues) {
window.Appcues.identify(id, {
name: displayName,
deployment_name: hostNameParts[0],
data_region: hostNameParts[1],
deployment_url: hostname,
company_domain: domain,
companyName: orgName,
email,
paidUser: !!trialInfo?.trialConvertedToSubscription,
});
}
insights?.identify(id, {
email,
name: displayName,
@@ -226,6 +243,89 @@ function App(): JSX.Element {
});
}, [isLoggedInState, isAIAssistantEnabled]);
const isDarkMode = useIsDarkMode();
useEffect(() => {
window.Pylon?.('setTheme', isDarkMode ? 'dark' : 'light');
}, [isDarkMode]);
useEffect(() => {
if (
pathname === ROUTES.ONBOARDING ||
pathname.startsWith('/public/dashboard/') ||
pathname === '/ai-assistant' ||
pathname.startsWith('/ai-assistant/')
) {
window.Pylon?.('hideChatBubble');
} else {
window.Pylon?.('showChatBubble');
}
}, [pathname]);
// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => {
// feature flag shouldn't be loading and featureFlags or fetchError any one of this should be true indicating that req is complete
// licenses should also be present. there is no check for licenses for loading and error as that is mandatory if not present then routing
// to something went wrong which would ideally need a reload.
if (
!isFetchingFeatureFlags &&
(featureFlags || featureFlagsFetchError) &&
activeLicense &&
trialInfo
) {
let isChatSupportEnabled = false;
let isPremiumSupportEnabled = false;
if (featureFlags && featureFlags.length > 0) {
isChatSupportEnabled =
featureFlags.find((flag) => flag.name === FeatureKeys.CHAT_SUPPORT)
?.active || false;
isPremiumSupportEnabled =
featureFlags.find((flag) => flag.name === FeatureKeys.PREMIUM_SUPPORT)
?.active || false;
}
const showAddCreditCardModal =
!isPremiumSupportEnabled && !trialInfo?.trialConvertedToSubscription;
if (
isLoggedInState &&
isChatSupportEnabled &&
!showAddCreditCardModal &&
(isCloudUser || isEnterpriseSelfHostedUser) &&
(window.o11yBootData?.settings?.pylon.enabled ?? true)
) {
const email = user.email || '';
const secret = process.env.PYLON_IDENTITY_SECRET || '';
let emailHash = '';
if (email && secret) {
emailHash = HmacSHA256(email, Hex.parse(secret)).toString(Hex);
}
window.pylon = {
chat_settings: {
app_id: process.env.PYLON_APP_ID,
email: user.email,
name: user.displayName || user.email,
email_hash: emailHash,
},
};
}
}
}, [
isLoggedInState,
user,
pathname,
trialInfo?.trialConvertedToSubscription,
featureFlags,
isFetchingFeatureFlags,
featureFlagsFetchError,
activeLicense,
trialInfo,
isCloudUser,
isEnterpriseSelfHostedUser,
]);
useEffect(() => {
if (!isFetchingUser && isCloudUser && user && user.email) {
enableAnalytics(user);
@@ -4080,7 +4080,7 @@ export enum Querybuildertypesv5QueryTypeDTO {
builder_query = 'builder_query',
builder_formula = 'builder_formula',
builder_trace_operator = 'builder_trace_operator',
datastore_sql = 'datastore_sql',
clickhouse_sql = 'clickhouse_sql',
promql = 'promql',
}
export interface Querybuildertypesv5QueryEnvelopeBuilderTraceDTO {
@@ -4227,7 +4227,7 @@ export interface Querybuildertypesv5QueryEnvelopePromQLDTO {
type?: Querybuildertypesv5QueryTypeDTO;
}
export interface Querybuildertypesv5DatastoreQueryDTO {
export interface Querybuildertypesv5ClickHouseQueryDTO {
/**
* @type boolean
*/
@@ -4246,8 +4246,8 @@ export interface Querybuildertypesv5DatastoreQueryDTO {
query?: string;
}
export interface Querybuildertypesv5QueryEnvelopeDatastoreSQLDTO {
spec?: Querybuildertypesv5DatastoreQueryDTO;
export interface Querybuildertypesv5QueryEnvelopeClickHouseSQLDTO {
spec?: Querybuildertypesv5ClickHouseQueryDTO;
type?: Querybuildertypesv5QueryTypeDTO;
}
@@ -4276,7 +4276,7 @@ export type Querybuildertypesv5QueryEnvelopeDTO =
spec?: unknown;
type?: Querybuildertypesv5QueryTypeDTO;
})
| (Querybuildertypesv5QueryEnvelopeDatastoreSQLDTO & {
| (Querybuildertypesv5QueryEnvelopeClickHouseSQLDTO & {
spec?: unknown;
type?: Querybuildertypesv5QueryTypeDTO;
});
@@ -4324,16 +4324,16 @@ export interface DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQueryb
spec: Querybuildertypesv5PromQueryDTO;
}
export enum DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5DatastoreQueryDTOKind {
'o11y/DatastoreSQL' = 'o11y/DatastoreSQL',
export enum DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5ClickHouseQueryDTOKind {
'o11y/ClickHouseSQL' = 'o11y/ClickHouseSQL',
}
export interface DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5DatastoreQueryDTO {
export interface DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5ClickHouseQueryDTO {
/**
* @enum o11y/DatastoreSQL
* @enum o11y/ClickHouseSQL
* @type string
*/
kind: DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5DatastoreQueryDTOKind;
spec: Querybuildertypesv5DatastoreQueryDTO;
kind: DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5ClickHouseQueryDTOKind;
spec: Querybuildertypesv5ClickHouseQueryDTO;
}
export enum DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5QueryBuilderTraceOperatorDTOKind {
@@ -4353,7 +4353,7 @@ export type DashboardtypesQueryPluginDTO =
| DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5CompositeQueryDTO
| DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5QueryBuilderFormulaDTO
| DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5PromQueryDTO
| DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5DatastoreQueryDTO
| DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5ClickHouseQueryDTO
| DashboardtypesQueryPluginVariantGithubComO11yO11yPkgTypesQuerybuildertypesQuerybuildertypesv5QueryBuilderTraceOperatorDTO;
export interface DashboardtypesQuerySpecDTO {
@@ -4702,7 +4702,7 @@ export enum DashboardtypesQueryPluginKindDTO {
'o11y/CompositeQuery' = 'o11y/CompositeQuery',
'o11y/Formula' = 'o11y/Formula',
'o11y/PromQLQuery' = 'o11y/PromQLQuery',
'o11y/DatastoreSQL' = 'o11y/DatastoreSQL',
'o11y/ClickHouseSQL' = 'o11y/ClickHouseSQL',
'o11y/TraceOperator' = 'o11y/TraceOperator',
}
export interface DashboardtypesUpdatablePublicDashboardDTO {
@@ -6890,7 +6890,7 @@ export enum Querybuildertypesv5RequestTypeDTO {
trace = 'trace',
}
/**
* Request body for the v5 query range endpoint. Supports builder queries (traces, logs, metrics), formulas, joins, trace operators, PromQL, and Datastore SQL queries.
* Request body for the v5 query range endpoint. Supports builder queries (traces, logs, metrics), formulas, joins, trace operators, PromQL, and ClickHouse SQL queries.
*/
export interface Querybuildertypesv5QueryRangeRequestDTO {
compositeQuery?: Querybuildertypesv5CompositeQueryDTO;
@@ -7075,7 +7075,7 @@ export enum RuletypesPanelTypeDTO {
}
export enum RuletypesQueryTypeDTO {
builder = 'builder',
datastore_sql = 'datastore_sql',
clickhouse_sql = 'clickhouse_sql',
promql = 'promql',
}
export interface RuletypesAlertCompositeQueryDTO {
@@ -22,7 +22,7 @@ import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
/**
* Execute a composite query over a time range. Supports builder queries (traces, logs, metrics), formulas, trace operators, PromQL, and Datastore SQL.
* Execute a composite query over a time range. Supports builder queries (traces, logs, metrics), formulas, trace operators, PromQL, and ClickHouse SQL.
* @summary Query range
*/
export const queryRangeV5 = (
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
interface ConfigureIconProps {
width?: number;
height?: number;
@@ -6,9 +5,9 @@ interface ConfigureIconProps {
}
function ConfigureIcon({
width = 16,
height = 16,
color = 'currentColor',
width,
height,
color,
}: ConfigureIconProps): JSX.Element {
return (
<svg
@@ -34,4 +33,9 @@ function ConfigureIcon({
);
}
ConfigureIcon.defaultProps = {
width: 16,
height: 16,
color: 'currentColor',
};
export default ConfigureIcon;
+13 -6
View File
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
interface LogsIconProps {
width?: number;
height?: number;
@@ -8,11 +7,11 @@ interface LogsIconProps {
}
function LogsIcon({
width = 14,
height = 14,
fill = 'none',
strokeColor = '#C0C1C3',
strokeWidth = 1.167,
width,
height,
fill,
strokeColor,
strokeWidth,
}: LogsIconProps): JSX.Element {
return (
<svg
@@ -55,4 +54,12 @@ function LogsIcon({
);
}
LogsIcon.defaultProps = {
width: 14,
height: 14,
fill: 'none',
strokeColor: '#C0C1C3',
strokeWidth: 1.167,
};
export default LogsIcon;
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
interface SeverityCriticalIconProps {
width?: number;
height?: number;
@@ -7,10 +6,10 @@ interface SeverityCriticalIconProps {
}
function SeverityCriticalIcon({
width = 6,
height = 6,
fill = 'none',
stroke = '#F56C87',
width,
height,
fill,
stroke,
}: SeverityCriticalIconProps): JSX.Element {
return (
<svg
@@ -30,4 +29,11 @@ function SeverityCriticalIcon({
);
}
SeverityCriticalIcon.defaultProps = {
width: 6,
height: 6,
fill: 'none',
stroke: '#F56C87',
};
export default SeverityCriticalIcon;
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
interface SeverityErrorIconProps {
width?: number;
height?: number;
@@ -8,11 +7,11 @@ interface SeverityErrorIconProps {
}
function SeverityErrorIcon({
width = 2,
height = 6,
fill = 'none',
stroke = '#F56C87',
strokeWidth = '1.02083',
width,
height,
fill,
stroke,
strokeWidth,
}: SeverityErrorIconProps): JSX.Element {
return (
<svg
@@ -32,4 +31,12 @@ function SeverityErrorIcon({
);
}
SeverityErrorIcon.defaultProps = {
width: 2,
height: 6,
fill: 'none',
stroke: '#F56C87',
strokeWidth: '1.02083',
};
export default SeverityErrorIcon;
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
interface SeverityInfoIconProps {
width?: number;
height?: number;
@@ -7,10 +6,10 @@ interface SeverityInfoIconProps {
}
function SeverityInfoIcon({
width = 14,
height = 14,
fill = 'none',
stroke = '#7190F9',
width,
height,
fill,
stroke,
}: SeverityInfoIconProps): JSX.Element {
return (
<svg
@@ -37,4 +36,11 @@ function SeverityInfoIcon({
);
}
SeverityInfoIcon.defaultProps = {
width: 14,
height: 14,
fill: 'none',
stroke: '#7190F9',
};
export default SeverityInfoIcon;
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
interface SeverityWarningIconProps {
width?: number;
height?: number;
@@ -8,11 +7,11 @@ interface SeverityWarningIconProps {
}
function SeverityWarningIcon({
width = 2,
height = 6,
fill = 'none',
stroke = '#FFD778',
strokeWidth = '0.978299',
width,
height,
fill,
stroke,
strokeWidth,
}: SeverityWarningIconProps): JSX.Element {
return (
<svg
@@ -32,4 +31,12 @@ function SeverityWarningIcon({
);
}
SeverityWarningIcon.defaultProps = {
width: 2,
height: 6,
fill: 'none',
stroke: '#FFD778',
strokeWidth: '0.978299',
};
export default SeverityWarningIcon;
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
export default function ApacheIcon(): JSX.Element {
return (
<svg
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
export default function DockerIcon(): JSX.Element {
return (
<svg
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
export default function ElasticSearchIcon(): JSX.Element {
return (
<svg
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
interface EmptyQuickFilterIconProps {
width?: number;
height?: number;
@@ -8,7 +7,7 @@ interface EmptyQuickFilterIconProps {
function EmptyQuickFilterIcon({
width = 32,
height = 32,
className = '',
className,
}: EmptyQuickFilterIconProps): JSX.Element {
return (
<svg
@@ -82,4 +81,10 @@ function EmptyQuickFilterIcon({
);
}
EmptyQuickFilterIcon.defaultProps = {
width: 32,
height: 32,
className: '',
};
export default EmptyQuickFilterIcon;
@@ -1,8 +1,6 @@
import { Color } from 'constants/designTokens';
import { useIsDarkMode } from 'hooks/useDarkMode';
import type { JSX } from 'react';
function GroupByIcon(): JSX.Element {
const isDarkMode = useIsDarkMode();
return (
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
function HerokuIcon(): JSX.Element {
return (
<svg
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
export default function KubernetesIcon(): JSX.Element {
return (
<svg
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
export default function MongoDBIcon(): JSX.Element {
return (
<svg
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
export default function MySQLIcon(): JSX.Element {
return (
<svg
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
function NginxIcon(): JSX.Element {
return (
<svg
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
export default function PostgreSQLIcon(): JSX.Element {
return (
<svg
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
export default function RedisIcon(): JSX.Element {
return (
<svg
+1 -1
View File
@@ -1,4 +1,4 @@
import { CSSProperties, type JSX } from 'react';
import { CSSProperties } from 'react';
function PromQLIcon({
fillColor,
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { type JSX } from 'react';
import React from 'react';
type ErrorIconProps = React.SVGProps<SVGSVGElement>;
@@ -1,8 +1,6 @@
import { Color } from 'constants/designTokens';
import { useIsDarkMode } from 'hooks/useDarkMode';
import type { JSX } from 'react';
function ConfigureIcon(): JSX.Element {
const isDarkMode = useIsDarkMode();
return (
-2
View File
@@ -1,7 +1,5 @@
import notFound404Url from '@/assets/Images/notFound404.png';
import type { JSX } from 'react';
function NotFound(): JSX.Element {
return (
<img
@@ -1,8 +1,6 @@
import { Color } from 'constants/designTokens';
import { useIsDarkMode } from 'hooks/useDarkMode';
import type { JSX } from 'react';
function FlamegraphImg(): JSX.Element {
const isDarkMode = useIsDarkMode();
return (
-1
View File
@@ -1,4 +1,3 @@
import type { JSX } from 'react';
function UnAuthorized({
width = 137,
height = 137,
+1 -1
View File
@@ -10,5 +10,5 @@
// PR for reference: https://github.com/hanzoai/o11y/pull/9694
// -------------------------------------------------------------------------
import '@hanzo/insights';
import '@hanzo/ui';
import '@hanzo/insights';
@@ -4,8 +4,6 @@ import { Divider } from 'components/ui/divider';
import styles from './AlertBreadcrumb.module.scss';
import BreadcrumbItem, { BreadcrumbItemConfig } from './BreadcrumbItem';
import type { JSX } from 'react';
export interface AlertBreadcrumbProps {
items: BreadcrumbItemConfig[];
className?: string;
@@ -4,8 +4,6 @@ import { isModifierKeyPressed } from 'utils/app';
import styles from './BreadcrumbItem.module.scss';
import type { JSX } from 'react';
export type BreadcrumbItemConfig =
| {
title: string | null;
@@ -2,8 +2,6 @@ import DateTimeSelector from 'container/TopNav/DateTimeSelectionV2';
import './Filters.styles.scss';
import type { JSX } from 'react';
export function Filters(): JSX.Element {
return (
<div className="filters">
@@ -1,4 +1,4 @@
import { useCallback, type JSX } from 'react';
import { useCallback } from 'react';
import { LifeBuoy, RefreshCw, TriangleAlert } from 'components/ui/icons';
import { Button } from 'components/ui/button';
import { handleContactSupport } from 'container/Integrations/utils';
@@ -6,7 +6,7 @@ import {
TooltipRoot,
TooltipTrigger,
} from 'components/ui/tooltip';
import { useCallback, useEffect, useRef, useState, type JSX } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import LabelTag from './LabelTag';
@@ -10,8 +10,6 @@ import { useCopyToClipboard } from 'react-use';
import styles from './LabelTag.module.scss';
import type { JSX } from 'react';
export interface LabelTagProps {
label: string;
color?:
@@ -3,8 +3,6 @@ import { Button } from 'components/ui/button';
import styles from './NoResultsEmptyState.module.scss';
import type { JSX } from 'react';
interface NoResultsEmptyStateProps {
title?: string;
subtitle?: string;
@@ -7,8 +7,6 @@ import o11yBrandLogoUrl from '@/assets/Logos/o11y-brand-logo.svg';
import './AppLoading.styles.scss';
import type { JSX } from 'react';
function AppLoading(): JSX.Element {
// Get theme from localStorage directly to avoid context dependency
const getThemeFromStorage = (): boolean => {
@@ -4,8 +4,6 @@ import APIError from 'types/api/error';
import './AuthError.styles.scss';
import type { JSX } from 'react';
interface AuthErrorProps {
error: APIError;
}

Some files were not shown because too many files have changed in this diff Show More