hazo_umetrics 0.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGE_LOG.md +119 -0
- package/README.md +54 -12
- package/SETUP_CHECKLIST.md +8 -4
- package/config/hazo_umetrics_config.ini.sample +15 -0
- package/dist/api/index.d.ts +82 -11
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +442 -69
- package/dist/index.client.d.ts +10 -8
- package/dist/index.client.d.ts.map +1 -1
- package/dist/index.client.js +180 -20
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +3 -0
- package/dist/server/actions.d.ts +66 -0
- package/dist/server/actions.d.ts.map +1 -0
- package/dist/server/actions.js +234 -0
- package/dist/server/activity.d.ts +37 -0
- package/dist/server/activity.d.ts.map +1 -0
- package/dist/server/activity.js +118 -0
- package/dist/server/behavior.d.ts +64 -0
- package/dist/server/behavior.d.ts.map +1 -0
- package/dist/server/behavior.js +346 -0
- package/dist/server/flags.d.ts +2 -1
- package/dist/server/flags.d.ts.map +1 -1
- package/dist/server/flags.js +17 -12
- package/dist/server/ga4.d.ts +1 -1
- package/dist/server/ga4.d.ts.map +1 -1
- package/dist/server/stats.d.ts +10 -8
- package/dist/server/stats.d.ts.map +1 -1
- package/dist/server/stats.js +25 -18
- package/dist/server/types.d.ts +31 -3
- package/dist/server/types.d.ts.map +1 -1
- package/dist/ui/actions_panel.d.ts +6 -0
- package/dist/ui/actions_panel.d.ts.map +1 -0
- package/dist/ui/actions_panel.js +218 -0
- package/dist/ui/analytics_panel.d.ts +6 -0
- package/dist/ui/analytics_panel.d.ts.map +1 -0
- package/dist/ui/analytics_panel.js +138 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +2 -0
- package/dist/ui/metrics_panel.d.ts +2 -2
- package/dist/ui/metrics_panel.d.ts.map +1 -1
- package/dist/ui/metrics_panel.js +50 -105
- package/migrations/db_setup_postgres.sql +48 -20
- package/migrations/db_setup_sqlite.sql +48 -20
- package/package.json +15 -7
package/CHANGE_LOG.md
CHANGED
|
@@ -1,5 +1,124 @@
|
|
|
1
1
|
# hazo_umetrics — Change Log
|
|
2
2
|
|
|
3
|
+
## 1.3.0 — Analytics dashboard + behavioral fixes
|
|
4
|
+
|
|
5
|
+
**New features + bug fixes (no breaking changes):**
|
|
6
|
+
|
|
7
|
+
### New: `topActions` behavior aggregation
|
|
8
|
+
- `topActions` function in `src/server/behavior.ts` — ranks all `action.*` events by frequency, no `user_id` required. Answers "what is the most common action?" without needing session segmentation.
|
|
9
|
+
- Exposed as `metric=top_actions` in `GET /api/hazo_umetrics/analytics/behavior`.
|
|
10
|
+
- New "Top actions (all users)" panel in `AnalyticsPanel` (first panel in behavioral grid).
|
|
11
|
+
|
|
12
|
+
### New: `UserIdentifier` client component (test-app)
|
|
13
|
+
- Fetches `/api/hazo_auth/me` on mount and calls `identify(user_id)` automatically so logged-in users are tracked with `user_id` in all subsequent events. Required for first-session, post-relogin, and returning-user panels to populate.
|
|
14
|
+
|
|
15
|
+
### Bug fix: `tabDwell` elapsed time
|
|
16
|
+
- `tabDwell` was reading `row.value` (hardcoded `1`) instead of `dims.value` (the real accumulated foreground ms from the client-side visibility timer). Fixed to use `dims.value` when present. `Time per tab` panel now shows actual seconds, not event count.
|
|
17
|
+
|
|
18
|
+
### Bug fix: `AnalyticsPanel` bar chart invisible bars
|
|
19
|
+
- `SimpleBarChart` column divs lacked `h-full`, so `height: X%` resolved to 0px (percentage of auto-height container). Removed `items-end` from parent flex container (default `items-stretch`) and added `h-full` to each column div. Bars now render at correct proportional height.
|
|
20
|
+
|
|
21
|
+
### Enhanced seed (test-app)
|
|
22
|
+
- `buildEventRows()` rewritten with 4 realistic user personas (alice/bob/carol/dave) carrying `user_id` + `session_id` in all dimensions. Generates `action.*`, `ui.dwell.*`, and `ui.error.*` events spread across 14 days. Seeds `hazo_umetrics_action_def` catalog on every run so Action Catalog shows pre-classified actions with correct hierarchy and key-event flags.
|
|
23
|
+
|
|
24
|
+
## 1.2.0 — Behavioral analytics + catalog UX
|
|
25
|
+
|
|
26
|
+
**New features (additive, no breaking changes):**
|
|
27
|
+
|
|
28
|
+
### Phase 1 — Capture foundation
|
|
29
|
+
- `identify(userId)` now stores user ID in module var + `localStorage['hazo_umetrics_uid']`; `getUserId()` restores on page reload. `postEvent` attaches `user_id` to every event's dimensions automatically.
|
|
30
|
+
- `HazoMetricsProvider` gains optional `userId` prop — calls `identify` when set (SSR/known-user apps).
|
|
31
|
+
- Global `error` + `unhandledrejection` listeners post `ui.error.<slug>` events with `type`, `message` (truncated 200 chars), `path`.
|
|
32
|
+
- Tab-dwell tracking: `ui.dwell.<path_slug>` posted on path change with `value=ms`; `pagehide`/`visibilitychange` flushes remaining dwell.
|
|
33
|
+
- `trackError(err, ctx?)` exported from context for manual error capture.
|
|
34
|
+
|
|
35
|
+
### Phase 2 — Catalog UX
|
|
36
|
+
- New `hazo_umetrics_action_link` table (SQLite + Postgres) — undirected many-to-many related-action pairs. PK `(action_key, related_key, app_id)`; canonical order (alpha sort) prevents duplicate rows.
|
|
37
|
+
- `getLinks`, `addLink`, `removeLink` in `src/server/actions.ts`. Self-link guard (`action_key === related_key` → null/400). Idempotent add.
|
|
38
|
+
- `createLinkHandlers` in `hazo_umetrics/api`: `GET`, `POST`, `DELETE` for `/actions/links`. Auth-gated (`metrics.view` / `metrics.manage`).
|
|
39
|
+
- `actions_panel.tsx` updated: free-text parent_key field replaced with `ActionKeyPicker` (searchable single-select with clear button); new `RelatedActionsPicker` multi-select with removable pills. Both source options from the flat action catalog.
|
|
40
|
+
|
|
41
|
+
### Phase 3 — Analytics query layer
|
|
42
|
+
- New `src/server/behavior.ts` — 7 behavioral aggregation functions using the established JS-aggregation pattern (windowed fetch, in-memory grouping, backend-agnostic):
|
|
43
|
+
- `firstSessionActions` — actions in each user's first session
|
|
44
|
+
- `postReloginActions` — actions in each user's second session
|
|
45
|
+
- `actionsBeforeGoal(goalKey, n)` — N actions preceding a goal event
|
|
46
|
+
- `topErrors` — `ui.error.*` frequency ranking
|
|
47
|
+
- `tabDwell` — summed dwell ms by path
|
|
48
|
+
- `frequentActionsReturning` — actions by users with >1 session
|
|
49
|
+
- `eventsOverTime` — day buckets with optional per-user breakdown
|
|
50
|
+
- All functions accept `catalogMap`, `includeArchived`, `keyEventsOnly` filters — archived actions excluded by default; key events given first-class treatment.
|
|
51
|
+
- `createBehaviorHandlers` in `hazo_umetrics/api`: `GET /analytics/behavior?metric=<name>` and `GET /analytics/events-over-time`.
|
|
52
|
+
|
|
53
|
+
### Phase 4 — Analytics dashboard
|
|
54
|
+
- New `AnalyticsPanel` component (`hazo_umetrics/ui`) — date-range filter, user multi-select, show-archived toggle, key-events-only toggle, goal-key input.
|
|
55
|
+
- Events-over-time bar chart (inline `SimpleBarChart`, no extra peer dep) with per-user stacked segments when user filter active.
|
|
56
|
+
- Six behavioral question cards: first-session, post-relogin, before-goal, errors, tab-dwell, frequent-returning.
|
|
57
|
+
- Test-app: `/analytics` page, `Analytics` sidebar nav link, three new API routes (`/actions/links`, `/analytics/behavior`, `/analytics/events-over-time`).
|
|
58
|
+
- Autotest scenarios for client capture (identify) and link API.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 1.1.0 — Action tracking + catalog
|
|
63
|
+
|
|
64
|
+
**New features (additive, no breaking changes):**
|
|
65
|
+
|
|
66
|
+
### Action catalog
|
|
67
|
+
- New `hazo_umetrics_action_def` table (SQLite + Postgres) — governance layer for user action tracking. Composite PK `(action_key, app_id)`, self-referencing `parent_key` for hierarchy, `is_key_event` flag, `status` (`unclassified | active | archived`).
|
|
68
|
+
- `src/server/actions.ts` — catalog service: `ensureActionDef`, `listActions`, `getActionTree`, `updateActionDef`. In-process seen-set cache prevents repeated DB writes per event.
|
|
69
|
+
- `ActionDef`, `ActionNode`, `ActionDefPatch` types added to `src/server/types.ts` and re-exported from the package root.
|
|
70
|
+
|
|
71
|
+
### Broadened event ingestion
|
|
72
|
+
- `createEventHandlers.recordEvent` now accepts `metric_key` starting with `action.*` in addition to `ui.*`. On first ingest of a new key, auto-creates an `unclassified` catalog row via `ensureActionDef`.
|
|
73
|
+
- Arbitrary keys still rejected (guard unchanged for anything else).
|
|
74
|
+
|
|
75
|
+
### Catalog API
|
|
76
|
+
- `createActionHandlers(options)` — new handler factory in `hazo_umetrics/api`:
|
|
77
|
+
- `listActions` — `GET`, requires `metrics.view`.
|
|
78
|
+
- `getActionTree` — `GET`, requires `metrics.view`. Returns catalog nested by `parent_key`.
|
|
79
|
+
- `updateAction` — `PATCH`, requires `metrics.manage`. Patches mutable fields; emits `action.catalog.update` audit entry.
|
|
80
|
+
|
|
81
|
+
### Client
|
|
82
|
+
- `useMetrics()` context now includes `trackAction(key, opts?)` — thin wrapper that posts `action.<key>` via the existing `postEvent`. Optional `opts.url` and `opts.dims` forwarded as dimensions.
|
|
83
|
+
|
|
84
|
+
### Admin UI
|
|
85
|
+
- `ActionsPanel` component exported from `hazo_umetrics/ui` — catalog tree view with inline editing (label, description, parent reparent, URL, key-event toggle, status).
|
|
86
|
+
|
|
87
|
+
### Test-app
|
|
88
|
+
- New `/actions` page with demo `trackAction` buttons and live `ActionsPanel`.
|
|
89
|
+
- New API routes: `GET/PATCH /api/hazo_umetrics/actions`, `GET /api/hazo_umetrics/actions/tree`.
|
|
90
|
+
- 4 browser autotest scenarios in `hazo_umetrics_actions` scenario group.
|
|
91
|
+
- Sidebar: Actions nav item added.
|
|
92
|
+
|
|
93
|
+
### Tests
|
|
94
|
+
- 22 new Jest tests in `__tests__/actions.test.ts`. Total: 83 (all green).
|
|
95
|
+
|
|
96
|
+
## 1.0.0 — Breaking rename: scope_id→app_id, app_id→scope_id
|
|
97
|
+
|
|
98
|
+
**Breaking changes:**
|
|
99
|
+
- `scope_id` (required site-isolation key) renamed to `app_id` in all tables, TypeScript interfaces, function signatures, and API query parameters.
|
|
100
|
+
- Old optional `app_id` column on `hazo_umetrics_stat` renamed to `scope_id` (now means: optional tenant subdivision within a site).
|
|
101
|
+
- New optional `scope_id TEXT NOT NULL DEFAULT ''` column added to all 7 tables.
|
|
102
|
+
- `StatCollectorDef.collect(scope_id)` → `collect(app_id)`.
|
|
103
|
+
- `MaintenanceJob` handler ctx: `scope_id` → `app_id`.
|
|
104
|
+
- `MetricsPanel` prop: `scopeId` → `appId`.
|
|
105
|
+
- `Ga4Provisioner.provision(scopeId, ...)` → `provision(appId, ...)`.
|
|
106
|
+
- API query params: `?scope_id=` → `?app_id=` on all stat/experiment/GA4/query endpoints.
|
|
107
|
+
|
|
108
|
+
**No in-place migration provided** — this package is pre-release. Re-apply fresh schema via `db_setup_postgres.sql` or `db_setup_sqlite.sql`.
|
|
109
|
+
|
|
110
|
+
## 0.1.2 — 2026-06-14
|
|
111
|
+
|
|
112
|
+
**Changed:**
|
|
113
|
+
- `MetricsPanel`/`StatTrend`: `LineChart` now imported from `hazo_dataviz/client` instead of `hazo_ui`, following the hazo_ui 4.0.0 chart migration (LineChart/MultiLineChart moved to hazo_dataviz).
|
|
114
|
+
- Added `hazo_dataviz ^0.3.0` as a (required) peer dependency.
|
|
115
|
+
- `hazo_ui` peer range bumped to `^4.0.0`.
|
|
116
|
+
- README: install/peer-dep list, entry-point table, and Tailwind `@source` block updated for the `hazo_dataviz` chart dependency.
|
|
117
|
+
|
|
118
|
+
**Test-app:**
|
|
119
|
+
- Autotest `hazo_umetrics_stats` scenario: the `GET /stats/series` case now self-authenticates (logs in as the seeded `viewer@hazo.local` before fetching) instead of silently depending on a pre-existing manual login — it was failing with `UNAUTHORIZED` on a fresh run. Added a shared `loginAs()` helper.
|
|
120
|
+
- Added two stats cases unlocked by `loginAs()`: `GET /stats` authenticated viewer (happy path, `ok:true` + `data.stat`) and `GET /stats` as `none@hazo.local` (the `permission_ok=false → 403 FORBIDDEN` branch, previously uncovered).
|
|
121
|
+
|
|
3
122
|
## 0.1.1 — 2026-06-12
|
|
4
123
|
|
|
5
124
|
**Fixed:**
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Gives every hazo Next.js app one consistent, auth-gated way to understand user b
|
|
|
8
8
|
- **Feature flags** — deterministic weighted-variant bucketing, signed consent cookie, sticky assignment.
|
|
9
9
|
- **A/B experiments** — same engine as flags + significance (deferred until traffic justifies it).
|
|
10
10
|
|
|
11
|
-
All surfaces mount inside `hazo_admin` via the existing `kind:'metrics'` section; every read/write is `
|
|
11
|
+
All surfaces mount inside `hazo_admin` via the existing `kind:'metrics'` section; every read/write is `app_id`-gated.
|
|
12
12
|
|
|
13
13
|
## Status
|
|
14
14
|
|
|
@@ -23,9 +23,13 @@ npm install hazo_umetrics
|
|
|
23
23
|
**Peer dependencies (install the ones you use):**
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
npm install hazo_core hazo_connect hazo_auth hazo_secure hazo_audit hazo_api hazo_ui
|
|
26
|
+
npm install hazo_core hazo_connect hazo_auth hazo_secure hazo_audit hazo_api hazo_ui hazo_dataviz
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
> The `hazo_umetrics/ui` panel renders its trend chart with `hazo_dataviz` (the chart
|
|
30
|
+
> primitives moved out of `hazo_ui` in the hazo_ui 4.0.0 migration), so install
|
|
31
|
+
> `hazo_dataviz` when you use `MetricsPanel`/`StatTrend`.
|
|
32
|
+
|
|
29
33
|
Required env vars for cookie signing (A/B assignment):
|
|
30
34
|
```
|
|
31
35
|
HAZO_UMETRICS_COOKIE_KEY_CURRENT=v1
|
|
@@ -37,9 +41,9 @@ HAZO_UMETRICS_COOKIE_KEY_v1=<base64-encoded 32-byte AES-256 key>
|
|
|
37
41
|
| Import path | Contents |
|
|
38
42
|
|---|---|
|
|
39
43
|
| `hazo_umetrics` | Server: recordStat, getLatestStat, getStatSeries, resolveVariant, weightedBucket, GA4/experiment stubs |
|
|
40
|
-
| `hazo_umetrics/api` | Route factories: createStatHandlers, createExperimentHandlers, createGa4ConnectHandlers, createQueryHandlers |
|
|
41
|
-
| `hazo_umetrics/client` | Client: HazoMetricsProvider, useMetrics, getVariant, isEnabled, identify, recordStat |
|
|
42
|
-
| `hazo_umetrics/ui` | UI: MetricsPanel, StatCards, StatTrend
|
|
44
|
+
| `hazo_umetrics/api` | Route factories: createStatHandlers, createExperimentHandlers, createGa4ConnectHandlers, createQueryHandlers, createEventHandlers (anon ingestion), createBehaviorHandlers, createActionHandlers, createLinkHandlers, createActivityHandlers |
|
|
45
|
+
| `hazo_umetrics/client` | Client: HazoMetricsProvider, useMetrics, getVariant, isEnabled, identify, trackAction, trackError, recordStat |
|
|
46
|
+
| `hazo_umetrics/ui` | UI: MetricsPanel, StatCards, StatTrend, AnalyticsPanel, ActionsPanel |
|
|
43
47
|
|
|
44
48
|
## Stat store
|
|
45
49
|
|
|
@@ -47,24 +51,24 @@ HAZO_UMETRICS_COOKIE_KEY_v1=<base64-encoded 32-byte AES-256 key>
|
|
|
47
51
|
import { recordStat, getLatestStat, getStatSeries, registerStatCollector, runStatSnapshot } from 'hazo_umetrics';
|
|
48
52
|
|
|
49
53
|
// Append a reading (always inserts, never upserts)
|
|
50
|
-
await recordStat(adapter, {
|
|
54
|
+
await recordStat(adapter, { app_id: 'my-app', metric_key: 'active_users', value: 42 });
|
|
51
55
|
|
|
52
56
|
// Latest value
|
|
53
|
-
const stat = await getLatestStat(adapter, {
|
|
57
|
+
const stat = await getLatestStat(adapter, { app_id: 'my-app', metric_key: 'active_users' });
|
|
54
58
|
|
|
55
59
|
// Series (ascending by captured_at)
|
|
56
|
-
const series = await getStatSeries(adapter, {
|
|
60
|
+
const series = await getStatSeries(adapter, { app_id: 'my-app', metric_key: 'active_users', limit: 30 });
|
|
57
61
|
|
|
58
62
|
// Register a collector (runs in runStatSnapshot)
|
|
59
63
|
registerStatCollector({
|
|
60
64
|
key: 'active_users',
|
|
61
65
|
label: 'Active users',
|
|
62
66
|
format: 'count',
|
|
63
|
-
collect: async (
|
|
67
|
+
collect: async (app_id) => countActiveUsers(app_id),
|
|
64
68
|
});
|
|
65
69
|
|
|
66
70
|
// Run all collectors (call on a schedule via hazo_jobs in M1)
|
|
67
|
-
const results = await runStatSnapshot(adapter, {
|
|
71
|
+
const results = await runStatSnapshot(adapter, { app_id: 'my-app' });
|
|
68
72
|
```
|
|
69
73
|
|
|
70
74
|
## Feature flags
|
|
@@ -75,15 +79,52 @@ import { resolveVariant, getVariant, isEnabled } from 'hazo_umetrics';
|
|
|
75
79
|
// Evaluate a flag with logged-in user (sticky assignment)
|
|
76
80
|
const { variant } = await resolveVariant(adapter, {
|
|
77
81
|
experimentKey: 'new_dashboard',
|
|
78
|
-
|
|
82
|
+
app_id: 'my-app',
|
|
79
83
|
subjectId: user.id,
|
|
80
84
|
consent: true,
|
|
81
85
|
});
|
|
82
86
|
|
|
83
87
|
// Boolean flag shorthand
|
|
84
|
-
const enabled = await isEnabled(adapter, { experimentKey: 'new_feature',
|
|
88
|
+
const enabled = await isEnabled(adapter, { experimentKey: 'new_feature', app_id, subjectId });
|
|
85
89
|
```
|
|
86
90
|
|
|
91
|
+
## Analytics dashboard
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
import { AnalyticsPanel } from 'hazo_umetrics/ui';
|
|
95
|
+
|
|
96
|
+
// Mount in a server or client page — fetches all behavioral data from /api/hazo_umetrics/analytics/*
|
|
97
|
+
<AnalyticsPanel appId="my-app" />
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Seven behavioral panels answer:
|
|
101
|
+
- **Top actions (all users)** — most frequent `action.*` events, no user_id required
|
|
102
|
+
- **First-session actions** — what new users do first (requires `user_id` in events)
|
|
103
|
+
- **Post re-login actions** — what users do on their second session
|
|
104
|
+
- **Before "share"** — the N actions preceding a goal event (configurable goal key)
|
|
105
|
+
- **Common errors** — ranked `ui.error.*` frequency
|
|
106
|
+
- **Time per tab** — real foreground dwell time per path (from `ui.dwell.*` events)
|
|
107
|
+
- **Returning-user actions** — most common actions for users with >1 session
|
|
108
|
+
|
|
109
|
+
For `user_id` to be captured, call `identify(userId)` after login or mount `UserIdentifier` (see test-app `src/components/user_identifier.tsx`).
|
|
110
|
+
|
|
111
|
+
## Action tracking (client)
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import { HazoMetricsProvider, useMetrics } from 'hazo_umetrics/client';
|
|
115
|
+
|
|
116
|
+
// Wrap your app
|
|
117
|
+
<HazoMetricsProvider appId="my-app" userId={currentUser?.id}>
|
|
118
|
+
{children}
|
|
119
|
+
</HazoMetricsProvider>
|
|
120
|
+
|
|
121
|
+
// In any component
|
|
122
|
+
const { trackAction, trackError } = useMetrics();
|
|
123
|
+
trackAction('timer.complete', { url: '/timer' });
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Auto-tracked on mount: page views (`ui.pageview.*`), clicks (`ui.click.*`), errors (`ui.error.*`), dwell time (`ui.dwell.*`).
|
|
127
|
+
|
|
87
128
|
## API route factories
|
|
88
129
|
|
|
89
130
|
```ts
|
|
@@ -115,6 +156,7 @@ Copy `config/hazo_umetrics_config.ini.sample` to `hazo_umetrics_config.ini` and
|
|
|
115
156
|
|
|
116
157
|
@source "../node_modules/hazo_umetrics/dist";
|
|
117
158
|
@source "../node_modules/hazo_ui/dist";
|
|
159
|
+
@source "../node_modules/hazo_dataviz/dist"; /* StatTrend's LineChart lives here */
|
|
118
160
|
|
|
119
161
|
/* Tailwind v4 — map tokens to utilities */
|
|
120
162
|
@theme inline {
|
package/SETUP_CHECKLIST.md
CHANGED
|
@@ -11,9 +11,12 @@ npm install hazo_umetrics
|
|
|
11
11
|
Install peer dependencies you need:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npm install hazo_core hazo_connect hazo_auth hazo_secure hazo_audit hazo_api hazo_ui
|
|
14
|
+
npm install hazo_core hazo_connect hazo_auth hazo_secure hazo_audit hazo_api hazo_ui hazo_dataviz
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
> `hazo_dataviz` is required when you render `MetricsPanel`/`StatTrend` — its `LineChart`
|
|
18
|
+
> draws the trend chart (moved out of `hazo_ui` in the 4.0.0 chart migration).
|
|
19
|
+
|
|
17
20
|
## 2. Run DB migrations
|
|
18
21
|
|
|
19
22
|
**SQLite:**
|
|
@@ -63,7 +66,7 @@ import { HazoMetricsProvider } from 'hazo_umetrics/client';
|
|
|
63
66
|
|
|
64
67
|
export default function RootLayout({ children }) {
|
|
65
68
|
return (
|
|
66
|
-
<HazoMetricsProvider
|
|
69
|
+
<HazoMetricsProvider appId={process.env.NEXT_PUBLIC_APP_ID}>
|
|
67
70
|
{children}
|
|
68
71
|
</HazoMetricsProvider>
|
|
69
72
|
);
|
|
@@ -81,6 +84,7 @@ export default function RootLayout({ children }) {
|
|
|
81
84
|
|
|
82
85
|
@source "../node_modules/hazo_umetrics/dist";
|
|
83
86
|
@source "../node_modules/hazo_ui/dist";
|
|
87
|
+
@source "../node_modules/hazo_dataviz/dist";
|
|
84
88
|
|
|
85
89
|
/* Tailwind v4 — map HSL tokens to color utilities */
|
|
86
90
|
@theme inline {
|
|
@@ -112,7 +116,7 @@ export default function RootLayout({ children }) {
|
|
|
112
116
|
|
|
113
117
|
```ts
|
|
114
118
|
import { recordStat, getLatestStat } from 'hazo_umetrics';
|
|
115
|
-
await recordStat(adapter, {
|
|
116
|
-
const stat = await getLatestStat(adapter, {
|
|
119
|
+
await recordStat(adapter, { app_id: 'my-app', metric_key: 'setup_test', value: 1 });
|
|
120
|
+
const stat = await getLatestStat(adapter, { app_id: 'my-app', metric_key: 'setup_test' });
|
|
117
121
|
console.assert(stat?.value === 1, 'hazo_umetrics setup OK');
|
|
118
122
|
```
|
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
; Copy to hazo_umetrics_config.ini and fill in values for your environment.
|
|
3
3
|
; Env-var overrides: HAZO_UMETRICS_<SECTION>_<KEY> (uppercase, underscores).
|
|
4
4
|
|
|
5
|
+
[umetrics]
|
|
6
|
+
; The site identifier for this application instance. Used as the primary
|
|
7
|
+
; isolation key (app_id) on all hazo_umetrics tables.
|
|
8
|
+
app_id = my_app
|
|
9
|
+
|
|
10
|
+
[database]
|
|
11
|
+
; Set type = postgrest to use a PostgREST HTTP adapter (recommended for production).
|
|
12
|
+
; Set type = sqlite for local development without a Postgres instance.
|
|
13
|
+
; type = postgrest
|
|
14
|
+
; base_url = http://your-postgrest-host:4444
|
|
15
|
+
; api_key comes from POSTGREST_API_KEY env var — do not store it here.
|
|
16
|
+
; type = sqlite
|
|
17
|
+
; driver = better-sqlite3
|
|
18
|
+
; database_path = ./umetrics_test.sqlite
|
|
19
|
+
|
|
5
20
|
[env]
|
|
6
21
|
; Uncomment to hard-code the environment name (overrides HAZO_ENV / NODE_ENV).
|
|
7
22
|
; env = development
|
package/dist/api/index.d.ts
CHANGED
|
@@ -18,61 +18,132 @@ export interface ApiFactoryOptions {
|
|
|
18
18
|
onAudit?: (entry: {
|
|
19
19
|
action: string;
|
|
20
20
|
actor_id: string;
|
|
21
|
-
|
|
21
|
+
app_id: string;
|
|
22
22
|
details?: unknown;
|
|
23
23
|
}) => Promise<void>;
|
|
24
24
|
}
|
|
25
25
|
export declare function createStatHandlers(options: ApiFactoryOptions): {
|
|
26
26
|
/**
|
|
27
|
-
* GET /...?
|
|
27
|
+
* GET /...?app_id=<id>&metric_key=<key>
|
|
28
28
|
* Returns the latest reading for the metric, or null if none.
|
|
29
29
|
*/
|
|
30
30
|
getStat(req: Request): Promise<Response>;
|
|
31
31
|
/**
|
|
32
|
-
* GET /...?
|
|
32
|
+
* GET /...?app_id=<id>&metric_key=<key>[&since=<iso>][&limit=<n>]
|
|
33
33
|
* Returns time-series readings.
|
|
34
34
|
*/
|
|
35
35
|
getStatSeries(req: Request): Promise<Response>;
|
|
36
36
|
/**
|
|
37
37
|
* POST /...
|
|
38
|
-
* Body: {
|
|
38
|
+
* Body: { app_id, metric_key, value, scope_id?, unit?, dimensions? }
|
|
39
39
|
* Appends a new stat reading.
|
|
40
40
|
*/
|
|
41
41
|
recordStat(req: Request): Promise<Response>;
|
|
42
42
|
};
|
|
43
43
|
export declare function createExperimentHandlers(options: ApiFactoryOptions): {
|
|
44
44
|
/**
|
|
45
|
-
* GET /...?
|
|
46
|
-
* Lists all experiments for
|
|
45
|
+
* GET /...?app_id=<id>
|
|
46
|
+
* Lists all experiments for an app.
|
|
47
47
|
*/
|
|
48
48
|
listExperiments(req: Request): Promise<Response>;
|
|
49
49
|
/**
|
|
50
50
|
* POST /...
|
|
51
|
-
* Body: { experimentKey,
|
|
51
|
+
* Body: { experimentKey, app_id, subjectId?, consent? }
|
|
52
52
|
* Resolves the variant for a subject.
|
|
53
53
|
*/
|
|
54
54
|
evalVariant(req: Request): Promise<Response>;
|
|
55
55
|
/**
|
|
56
56
|
* POST /...
|
|
57
|
-
* Body: { key,
|
|
57
|
+
* Body: { key, app_id }
|
|
58
58
|
* Transitions an experiment to 'running'. Requires metrics.manage.
|
|
59
59
|
*/
|
|
60
60
|
startExperiment(req: Request): Promise<Response>;
|
|
61
61
|
/**
|
|
62
62
|
* POST /...
|
|
63
|
-
* Body: { key,
|
|
63
|
+
* Body: { key, app_id }
|
|
64
64
|
* Transitions an experiment to 'stopped'. Requires metrics.manage.
|
|
65
65
|
*/
|
|
66
66
|
stopExperiment(req: Request): Promise<Response>;
|
|
67
67
|
};
|
|
68
68
|
export declare function createGa4ConnectHandlers(options: ApiFactoryOptions): {
|
|
69
|
-
/** STUB (M1): Initiate GA4 OAuth connection for
|
|
69
|
+
/** STUB (M1): Initiate GA4 OAuth connection for an app. */
|
|
70
70
|
connect(req: Request): Promise<Response>;
|
|
71
|
-
/** STUB (M1): Provision a GA4 property for
|
|
71
|
+
/** STUB (M1): Provision a GA4 property for an app. */
|
|
72
72
|
provision(req: Request): Promise<Response>;
|
|
73
73
|
};
|
|
74
74
|
export declare function createQueryHandlers(options: ApiFactoryOptions): {
|
|
75
75
|
/** STUB (M1): Execute a GA4 or stat-store query. */
|
|
76
76
|
query(req: Request): Promise<Response>;
|
|
77
77
|
};
|
|
78
|
+
export declare function createEventHandlers(options: Pick<ApiFactoryOptions, 'getAdapter'>): {
|
|
79
|
+
/**
|
|
80
|
+
* POST /...
|
|
81
|
+
* Body: { app_id, metric_key, value?, dimensions? }
|
|
82
|
+
* Records a UI or action event stat without auth. metric_key must start with
|
|
83
|
+
* 'ui.' or 'action.'. Intentional auth carve-out: instrumentation must work
|
|
84
|
+
* without login.
|
|
85
|
+
*
|
|
86
|
+
* For action.* keys: auto-creates an 'unclassified' catalog row on first sight
|
|
87
|
+
* (seen-set cached in-process). Accepts optional `url` dimension.
|
|
88
|
+
*/
|
|
89
|
+
recordEvent(req: Request): Promise<Response>;
|
|
90
|
+
};
|
|
91
|
+
export declare function createActionHandlers(options: ApiFactoryOptions): {
|
|
92
|
+
/**
|
|
93
|
+
* GET /...?app_id=<id>
|
|
94
|
+
* Returns all action defs as a flat list. Requires metrics.view.
|
|
95
|
+
*/
|
|
96
|
+
listActions(req: Request): Promise<Response>;
|
|
97
|
+
/**
|
|
98
|
+
* GET /...?app_id=<id>
|
|
99
|
+
* Returns action defs nested by parent_key. Requires metrics.view.
|
|
100
|
+
*/
|
|
101
|
+
getActionTree(req: Request): Promise<Response>;
|
|
102
|
+
/**
|
|
103
|
+
* PATCH /...
|
|
104
|
+
* Body: { app_id, action_key, patch: { label?, description?, parent_key?, url?, is_key_event?, status? } }
|
|
105
|
+
* Updates mutable fields of an action def. Requires metrics.manage.
|
|
106
|
+
* Emits an audit entry 'action.catalog.update'.
|
|
107
|
+
*/
|
|
108
|
+
updateAction(req: Request): Promise<Response>;
|
|
109
|
+
};
|
|
110
|
+
export declare function createActivityHandlers(options: ApiFactoryOptions): {
|
|
111
|
+
/**
|
|
112
|
+
* GET /...?app_id=<id>[&since=<iso>]
|
|
113
|
+
* Returns { summary, trend } aggregated from ui.* events in the stat store.
|
|
114
|
+
*/
|
|
115
|
+
getActivity(req: Request): Promise<Response>;
|
|
116
|
+
};
|
|
117
|
+
export declare function createLinkHandlers(options: ApiFactoryOptions): {
|
|
118
|
+
/**
|
|
119
|
+
* GET /...?app_id=<id>[&action_key=<key>]
|
|
120
|
+
* Returns related-action links. Requires metrics.view.
|
|
121
|
+
*/
|
|
122
|
+
getLinks(req: Request): Promise<Response>;
|
|
123
|
+
/**
|
|
124
|
+
* POST /...
|
|
125
|
+
* Body: { app_id, action_key, related_key }
|
|
126
|
+
* Adds a link (idempotent). Requires metrics.manage.
|
|
127
|
+
*/
|
|
128
|
+
addLink(req: Request): Promise<Response>;
|
|
129
|
+
/**
|
|
130
|
+
* DELETE /...
|
|
131
|
+
* Body: { app_id, action_key, related_key }
|
|
132
|
+
* Removes a link. Requires metrics.manage.
|
|
133
|
+
*/
|
|
134
|
+
removeLink(req: Request): Promise<Response>;
|
|
135
|
+
};
|
|
136
|
+
export declare function createBehaviorHandlers(options: ApiFactoryOptions): {
|
|
137
|
+
/**
|
|
138
|
+
* GET /...?app_id=<id>&metric=<name>[&since=<iso>][&goal=<key>][&n=<int>][&include_archived=1][&key_events_only=1][&user_ids=u1,u2]
|
|
139
|
+
* Dispatches to behavioral aggregation functions. Requires metrics.view.
|
|
140
|
+
* metric: first_session | post_relogin | before_goal | errors | tab_dwell | frequent_returning
|
|
141
|
+
*/
|
|
142
|
+
getBehavior(req: Request): Promise<Response>;
|
|
143
|
+
/**
|
|
144
|
+
* GET /...?app_id=<id>[&since=<iso>][&user_ids=u1,u2][&key_events_only=1][&include_archived=1]
|
|
145
|
+
* Returns events bucketed by day. Requires metrics.view.
|
|
146
|
+
*/
|
|
147
|
+
getEventsOverTime(req: Request): Promise<Response>;
|
|
148
|
+
};
|
|
78
149
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/api/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"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"}
|