hazo_umetrics 1.8.0 → 1.9.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 CHANGED
@@ -1,5 +1,22 @@
1
1
  # hazo_umetrics — Change Log
2
2
 
3
+ ## 1.9.0 — Search queries: per-term last-searched timestamps, second-precision "over time"
4
+
5
+ **New features (minor — no code-level breaking changes):**
6
+
7
+ ### `searchQueries()` — finer-grained `over_time`
8
+
9
+ - `over_time` buckets are now **second-precision timestamps** (`YYYY-MM-DD HH:MM:SS`) instead of day-only (`YYYY-MM-DD`), capped to the **most recent 100** buckets in chronological order.
10
+ - Added a `tsBucket()` helper in `src/server/behavior.ts`; the existing `dayBucket()` (used by other aggregations) is unchanged.
11
+ - Per-term `lastSearchedAt` was already computed and returned — no change there.
12
+ - **Behavior note for consumers:** the `EventDayBucket.day` field returned by `searchQueries` now carries a full timestamp string rather than a date. Shape is unchanged, so consumers upgrade without code changes, but any UI that rendered `day` as date-only will now display the full timestamp.
13
+
14
+ ### `SearchQueriesPanel` — visible timestamps
15
+
16
+ - **Top Search Terms** and **Zero-Result Terms** rows now show a right-aligned **last-searched** timestamp (from `lastSearchedAt`).
17
+ - **Searches Over Time** now renders the full second-precision timestamp per bucket (was date-only).
18
+ - Added an `fmtTs()` formatter; no prop or public-type changes, so consumers upgrade with no code changes.
19
+
3
20
  ## 1.8.0 — Journey: anonymous-session bucket, session drilldown, rich user dropdown
4
21
 
5
22
  **New features (minor — no breaking changes):**
@@ -21,6 +38,12 @@
21
38
  - New **session drilldown**: once a user or the anonymous bucket is selected, a second picker lists that group's individual sessions (short `session_id` + step count) and filters to one via the existing `session_id` param.
22
39
  - No prop or public-type changes — `JourneyPanelProps` and `resolveUserProfiles` are unchanged, so consumers upgrade with no code changes.
23
40
 
41
+ ### `JourneyPanel` — date-range filter + session timestamps
42
+
43
+ - New **date filter** in the toolbar: a preset dropdown (`24 hours` · `7 days` · `30 days` · `90 days` · `All time`) plus optional **from/to date pickers** that override the preset. Maps to the `since` / `until` query params.
44
+ - `getJourney` now also parses `until=<iso>` (previously only `since`) and passes it through to `userJourney`.
45
+ - Session step lists now show an **absolute timestamp** per step (local `HH:MM:SS`, above the existing time-to-next duration), and each session header shows its **start time** (`Mon D, HH:MM`).
46
+
24
47
  ## 1.7.0 — Search query analytics: `searchQueries`, `SearchQueriesPanel`, `getIdentity`
25
48
 
26
49
  **New features (minor — no breaking changes):**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"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"}
package/dist/api/index.js CHANGED
@@ -860,6 +860,7 @@ export function createBehaviorHandlers(options) {
860
860
  const session_id = url.searchParams.get('session_id') ?? undefined;
861
861
  const anonymous = url.searchParams.get('anonymous') === '1';
862
862
  const since = url.searchParams.get('since') ?? undefined;
863
+ const until = url.searchParams.get('until') ?? undefined;
863
864
  const splitByPage = url.searchParams.get('split_by_page') === '1';
864
865
  const pageFlow = url.searchParams.get('page_flow') === '1';
865
866
  const gate = await gateAuth(req, {
@@ -874,7 +875,7 @@ export function createBehaviorHandlers(options) {
874
875
  const adapter = await options.getAdapter();
875
876
  const catalog = await _listActions(adapter, { app_id });
876
877
  const catalogMap = new Map(catalog.map((a) => [a.action_key, { label: a.label, datapoint_schema: a.datapoint_schema }]));
877
- const result = await _userJourney(adapter, { app_id, user_id, session_id, anonymous, since, catalogMap, splitByPage, pageFlow });
878
+ const result = await _userJourney(adapter, { app_id, user_id, session_id, anonymous, since, until, catalogMap, splitByPage, pageFlow });
878
879
  return jsonOk(result);
879
880
  },
880
881
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"behavior.d.ts","sourceRoot":"","sources":["../../src/server/behavior.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"behavior.d.ts","sourceRoot":"","sources":["../../src/server/behavior.ts"],"names":[],"mappings":"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"}
@@ -41,6 +41,11 @@ function topN(tally, n, catalogMap) {
41
41
  function dayBucket(iso) {
42
42
  return iso.slice(0, 10);
43
43
  }
44
+ // Second-precision bucket: "YYYY-MM-DD HH:MM:SS". Handles both ISO ("…T…Z")
45
+ // and SQLite space-separated timestamps.
46
+ function tsBucket(iso) {
47
+ return iso.slice(0, 19).replace('T', ' ');
48
+ }
44
49
  async function fetchWindow(adapter, params) {
45
50
  const crud = getCrud(adapter);
46
51
  return crud.list((qb) => {
@@ -624,9 +629,9 @@ export async function searchQueries(adapter, opts) {
624
629
  }
625
630
  if (row.captured_at > acc.lastAt)
626
631
  acc.lastAt = row.captured_at;
627
- // Day bucket
628
- const day = dayBucket(row.captured_at);
629
- dayMap.set(day, (dayMap.get(day) ?? 0) + 1);
632
+ // Timestamp bucket (second precision) for fine-grained "over time"
633
+ const ts = tsBucket(row.captured_at);
634
+ dayMap.set(ts, (dayMap.get(ts) ?? 0) + 1);
630
635
  // Per-user
631
636
  if (uid) {
632
637
  if (!userMap.has(uid))
@@ -648,8 +653,10 @@ export async function searchQueries(adapter, opts) {
648
653
  .filter((e) => e.zeroResults > 0)
649
654
  .sort((a, b) => b.zeroResults - a.zeroResults)
650
655
  .slice(0, topN_);
656
+ // Keep the most recent 100 timestamp buckets, chronological order.
651
657
  const over_time = [...dayMap.entries()]
652
658
  .sort((a, b) => a[0].localeCompare(b[0]))
659
+ .slice(-100)
653
660
  .map(([day, count]) => ({ day, count }));
654
661
  const per_user = [...userMap.entries()]
655
662
  .map(([user_id, { count, terms }]) => ({
@@ -1 +1 @@
1
- {"version":3,"file":"journey_panel.d.ts","sourceRoot":"","sources":["../../src/ui/journey_panel.tsx"],"names":[],"mappings":"AAiBA,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,KAAK,EAAE,YAAY,CAAC;CACrB;AAMD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAC1I,iGAAiG;IACjG,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oFAAoF;IACpF,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAsrBD,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,mBAAmB,EAAE,cAAqB,EAAE,YAAmB,EAAE,EAAE,iBAAiB,+BAiNrJ"}
1
+ {"version":3,"file":"journey_panel.d.ts","sourceRoot":"","sources":["../../src/ui/journey_panel.tsx"],"names":[],"mappings":"AAiBA,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,KAAK,EAAE,YAAY,CAAC;CACrB;AAMD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAC1I,iGAAiG;IACjG,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oFAAoF;IACpF,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAotBD,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,mBAAmB,EAAE,cAAqB,EAAE,YAAmB,EAAE,EAAE,iBAAiB,+BA+QrJ"}
@@ -14,6 +14,29 @@ function fmtMs(ms) {
14
14
  return `${(ms / 1000).toFixed(1)}s`;
15
15
  return `${(ms / 60000).toFixed(1)}m`;
16
16
  }
17
+ /** Absolute clock time for a step (local), e.g. "22:29:07". */
18
+ function fmtClock(iso) {
19
+ const d = new Date(iso);
20
+ if (isNaN(d.getTime()))
21
+ return '—';
22
+ return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
23
+ }
24
+ /** Session-start stamp for a header, e.g. "Jul 9, 22:29". */
25
+ function fmtDateTime(iso) {
26
+ const d = new Date(iso);
27
+ if (isNaN(d.getTime()))
28
+ return '—';
29
+ return d.toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false });
30
+ }
31
+ /** Date-range presets for the journey filter. `days: null` = all time. */
32
+ const RANGE_PRESETS = [
33
+ { key: '24h', label: '24 hours', days: 1 },
34
+ { key: '7d', label: '7 days', days: 7 },
35
+ { key: '30d', label: '30 days', days: 30 },
36
+ { key: '90d', label: '90 days', days: 90 },
37
+ { key: 'all', label: 'All time', days: null },
38
+ ];
39
+ const ALL_TIME_SINCE = '2000-01-01T00:00:00.000Z';
17
40
  function kindColor(kind) {
18
41
  switch (kind) {
19
42
  case 'action': return '#6366f1';
@@ -329,7 +352,7 @@ function SessionDetail({ session, index, profile }) {
329
352
  const headingText = session.user_id
330
353
  ? (profile?.name ?? profile?.email ?? session.user_id.slice(0, 16))
331
354
  : `Anonymous (sid ${session.session_id.slice(0, 8)}…)`;
332
- return (_jsxs("div", { className: "rounded-lg border bg-white", children: [_jsxs("button", { type: "button", onClick: () => setOpen((v) => !v), className: "w-full flex items-center gap-2 px-4 py-3 text-left hover:bg-gray-50 transition-colors", children: [profile?.profile_picture_url && (_jsx("img", { src: profile.profile_picture_url, alt: "", width: 20, height: 20, className: "rounded-full object-cover flex-shrink-0", style: { width: 20, height: 20 } })), _jsx("span", { className: "text-sm font-mono text-gray-700 flex-1 truncate", children: headingText }), _jsxs("span", { className: "text-xs text-gray-400", children: [session.steps.length, " steps"] }), _jsx("span", { className: "text-gray-400 text-xs", children: open ? '▴' : '▾' })] }), open && (_jsx("div", { className: "border-t divide-y", children: session.steps.map((step, i) => (_jsxs("div", { className: "flex items-start gap-3 px-4 py-2", children: [_jsx("span", { className: "mt-0.5 w-2 h-2 rounded-full flex-shrink-0", style: { backgroundColor: kindColor(step.kind) } }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "text-xs font-medium text-gray-800 truncate", children: step.label }), step.path && (_jsx("p", { className: "text-[10px] text-gray-400 font-mono truncate", children: step.path }))] }), _jsx("span", { className: "text-[10px] text-gray-400 tabular-nums flex-shrink-0 mt-0.5", children: fmtMs(step.durationMsToNext) })] }, i))) }))] }));
355
+ return (_jsxs("div", { className: "rounded-lg border bg-white", children: [_jsxs("button", { type: "button", onClick: () => setOpen((v) => !v), className: "w-full flex items-center gap-2 px-4 py-3 text-left hover:bg-gray-50 transition-colors", children: [profile?.profile_picture_url && (_jsx("img", { src: profile.profile_picture_url, alt: "", width: 20, height: 20, className: "rounded-full object-cover flex-shrink-0", style: { width: 20, height: 20 } })), _jsx("span", { className: "text-sm font-mono text-gray-700 flex-1 truncate", children: headingText }), session.steps[0] && (_jsx("span", { className: "text-[10px] text-gray-400 tabular-nums flex-shrink-0", title: "Session start", children: fmtDateTime(session.steps[0].captured_at) })), _jsxs("span", { className: "text-xs text-gray-400", children: [session.steps.length, " steps"] }), _jsx("span", { className: "text-gray-400 text-xs", children: open ? '▴' : '▾' })] }), open && (_jsx("div", { className: "border-t divide-y", children: session.steps.map((step, i) => (_jsxs("div", { className: "flex items-start gap-3 px-4 py-2", children: [_jsx("span", { className: "mt-0.5 w-2 h-2 rounded-full flex-shrink-0", style: { backgroundColor: kindColor(step.kind) } }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "text-xs font-medium text-gray-800 truncate", children: step.label }), step.path && (_jsx("p", { className: "text-[10px] text-gray-400 font-mono truncate", children: step.path }))] }), _jsxs("span", { className: "flex flex-col items-end flex-shrink-0 mt-0.5 leading-tight", children: [_jsx("span", { className: "text-[10px] text-gray-500 tabular-nums", title: "Timestamp", children: fmtClock(step.captured_at) }), _jsx("span", { className: "text-[10px] text-gray-400 tabular-nums", title: "Time to next step", children: fmtMs(step.durationMsToNext) })] })] }, i))) }))] }));
333
356
  }
334
357
  // ---------------------------------------------------------------------------
335
358
  // IdentityDropdown — custom user filter (avatars + email, anonymous bucket)
@@ -404,6 +427,30 @@ export function JourneyPanel({ appId, apiBase, userId, sessionId, resolveUserPro
404
427
  const [splitByPage, setSplitByPage] = useState(false);
405
428
  // Page-flow: server returns only pageviews, linked page→page (skips clicks).
406
429
  const [pageFlow, setPageFlow] = useState(false);
430
+ // Date range: a preset window, overridden by explicit from/to dates when set.
431
+ const [rangePreset, setRangePreset] = useState('30d');
432
+ const [fromDate, setFromDate] = useState(''); // yyyy-mm-dd
433
+ const [toDate, setToDate] = useState(''); // yyyy-mm-dd
434
+ const customRange = Boolean(fromDate || toDate);
435
+ // Resolve the active window to since/until ISO params.
436
+ let sinceParam;
437
+ let untilParam;
438
+ if (customRange) {
439
+ sinceParam = fromDate ? new Date(`${fromDate}T00:00:00`).toISOString() : ALL_TIME_SINCE;
440
+ if (toDate)
441
+ untilParam = new Date(`${toDate}T23:59:59.999`).toISOString();
442
+ }
443
+ else {
444
+ const preset = RANGE_PRESETS.find((p) => p.key === rangePreset);
445
+ if (preset?.days != null) {
446
+ const d = new Date();
447
+ d.setDate(d.getDate() - preset.days);
448
+ sinceParam = d.toISOString();
449
+ }
450
+ else {
451
+ sinceParam = ALL_TIME_SINCE; // all time
452
+ }
453
+ }
407
454
  useEffect(() => {
408
455
  if (!appId)
409
456
  return;
@@ -420,6 +467,10 @@ export function JourneyPanel({ appId, apiBase, userId, sessionId, resolveUserPro
420
467
  const effSession = selectedSessionId || sessionId;
421
468
  if (effSession)
422
469
  params.set('session_id', effSession);
470
+ if (sinceParam)
471
+ params.set('since', sinceParam);
472
+ if (untilParam)
473
+ params.set('until', untilParam);
423
474
  if (splitByPage)
424
475
  params.set('split_by_page', '1');
425
476
  if (pageFlow)
@@ -476,11 +527,11 @@ export function JourneyPanel({ appId, apiBase, userId, sessionId, resolveUserPro
476
527
  setLoading(false);
477
528
  });
478
529
  return () => { cancelled = true; };
479
- }, [appId, apiBase, effectiveUserId, isAnon, selectedSessionId, sessionId, splitByPage, pageFlow]);
530
+ }, [appId, apiBase, effectiveUserId, isAnon, selectedSessionId, sessionId, sinceParam, untilParam, splitByPage, pageFlow]);
480
531
  const sessionCount = result?.sessions.length ?? 0;
481
532
  const totalSteps = result?.sessions.reduce((s, sess) => s + sess.steps.length, 0) ?? 0;
482
533
  // Single source of truth for the data-fetch controls — rendered in the toolbar,
483
534
  // and passed into the flow graph so they stay reachable in graph fullscreen.
484
- const toolbarControls = (_jsxs(_Fragment, { children: [_jsxs("label", { className: "flex items-center gap-1 cursor-pointer select-none", title: "Show only page-to-page navigation (each page links to the next page visited)", children: [_jsx("input", { type: "checkbox", checked: pageFlow, onChange: (e) => setPageFlow(e.target.checked), className: "w-3 h-3 cursor-pointer", style: { accentColor: '#10b981' } }), "Pages only"] }), _jsxs("label", { className: "flex items-center gap-1 cursor-pointer select-none", title: "Split each event into one box per page", children: [_jsx("input", { type: "checkbox", checked: splitByPage, onChange: (e) => setSplitByPage(e.target.checked), className: "w-3 h-3 cursor-pointer" }), "Group by page"] }), _jsx("label", { className: "text-gray-500 ml-1", children: "User" }), _jsx(IdentityDropdown, { value: effectiveIdentity, onChange: (v) => { setSelectedIdentity(v); setSelectedSessionId(''); }, userIds: identity.userIds, userSessions: identity.userSessions, anonCount: identity.anonCount, profiles: userProfiles }), (effectiveUserId || isAnon) && groupSessions.length > 1 && (_jsxs("select", { value: selectedSessionId, onChange: (e) => setSelectedSessionId(e.target.value), className: "rounded border px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-indigo-400", title: "Drill into one session", children: [_jsxs("option", { value: "", children: ["All sessions (", groupSessions.length, ")"] }), groupSessions.map((s) => (_jsxs("option", { value: s.session_id, children: [s.session_id.slice(0, 8), "\u2026 \u00B7 ", s.steps.length, " steps"] }, s.session_id)))] }))] }));
535
+ const toolbarControls = (_jsxs(_Fragment, { children: [_jsx("label", { className: "text-gray-500", children: "Date" }), _jsx("select", { value: rangePreset, onChange: (e) => { setRangePreset(e.target.value); setFromDate(''); setToDate(''); }, disabled: customRange, className: "rounded border px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-indigo-400 disabled:opacity-40", title: customRange ? 'Clear the from/to dates to use a preset' : 'Preset date range', children: RANGE_PRESETS.map((p) => (_jsx("option", { value: p.key, children: p.label }, p.key))) }), _jsx("input", { type: "date", value: fromDate, max: toDate || undefined, onChange: (e) => setFromDate(e.target.value), className: "rounded border px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-indigo-400", title: "From date (overrides preset)" }), _jsx("span", { className: "text-gray-400", children: "\u2013" }), _jsx("input", { type: "date", value: toDate, min: fromDate || undefined, onChange: (e) => setToDate(e.target.value), className: "rounded border px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-indigo-400", title: "To date (overrides preset)" }), customRange && (_jsx("button", { type: "button", onClick: () => { setFromDate(''); setToDate(''); }, className: "text-xs text-gray-400 hover:text-gray-600", title: "Clear date range", children: "\u2715" })), _jsxs("label", { className: "flex items-center gap-1 cursor-pointer select-none ml-1", title: "Show only page-to-page navigation (each page links to the next page visited)", children: [_jsx("input", { type: "checkbox", checked: pageFlow, onChange: (e) => setPageFlow(e.target.checked), className: "w-3 h-3 cursor-pointer", style: { accentColor: '#10b981' } }), "Pages only"] }), _jsxs("label", { className: "flex items-center gap-1 cursor-pointer select-none", title: "Split each event into one box per page", children: [_jsx("input", { type: "checkbox", checked: splitByPage, onChange: (e) => setSplitByPage(e.target.checked), className: "w-3 h-3 cursor-pointer" }), "Group by page"] }), _jsx("label", { className: "text-gray-500 ml-1", children: "User" }), _jsx(IdentityDropdown, { value: effectiveIdentity, onChange: (v) => { setSelectedIdentity(v); setSelectedSessionId(''); }, userIds: identity.userIds, userSessions: identity.userSessions, anonCount: identity.anonCount, profiles: userProfiles }), (effectiveUserId || isAnon) && groupSessions.length > 1 && (_jsxs("select", { value: selectedSessionId, onChange: (e) => setSelectedSessionId(e.target.value), className: "rounded border px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-indigo-400", title: "Drill into one session", children: [_jsxs("option", { value: "", children: ["All sessions (", groupSessions.length, ")"] }), groupSessions.map((s) => (_jsxs("option", { value: s.session_id, children: [s.session_id.slice(0, 8), "\u2026 \u00B7 ", s.steps.length, " steps"] }, s.session_id)))] }))] }));
485
536
  return (_jsxs("div", { className: "flex flex-col gap-4 p-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-4 text-xs text-gray-500", children: [_jsxs("span", { children: [_jsx("span", { className: "font-semibold text-gray-700", children: sessionCount }), " session", sessionCount !== 1 ? 's' : ''] }), _jsxs("span", { children: [_jsx("span", { className: "font-semibold text-gray-700", children: totalSteps }), " total steps"] }), _jsx("div", { className: "ml-auto flex items-center gap-3", children: toolbarControls })] }), error && (_jsx("div", { className: "rounded-lg border border-red-200 bg-red-50 p-3 text-xs text-red-700", children: error })), loading && !result && (_jsx("div", { className: "p-4 text-xs text-gray-400", children: "Loading journey data\u2026" })), result && (_jsxs(_Fragment, { children: [result.graph.nodes.length > 0 && (_jsx(JourneyFlowGraph, { graph: result.graph, userFilter: toolbarControls, loading: loading, multiSidePorts: multiSidePorts, backEdgeLoop: backEdgeLoop })), result.sessions.length === 0 ? (_jsx("div", { className: "rounded-lg border p-6 text-center text-sm text-gray-400", children: "No journey sessions found." })) : (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wide", children: "Sessions" }), result.sessions.map((session, i) => (_jsx(SessionDetail, { session: session, index: i, profile: session.user_id ? userProfiles.get(session.user_id) : undefined }, session.session_id)))] }))] })), !loading && !result && !error && (_jsx("div", { className: "rounded-lg border p-6 text-center text-sm text-gray-400", children: "No data yet." }))] }));
486
537
  }
@@ -1 +1 @@
1
- {"version":3,"file":"search_queries_panel.d.ts","sourceRoot":"","sources":["../../src/ui/search_queries_panel.tsx"],"names":[],"mappings":"AA4BA,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA8CD,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;CACjE;AAED,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,mBAAmB,EAAE,EAAE,uBAAuB,+BAiJzG"}
1
+ {"version":3,"file":"search_queries_panel.d.ts","sourceRoot":"","sources":["../../src/ui/search_queries_panel.tsx"],"names":[],"mappings":"AA4BA,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAoDD,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;CACjE;AAED,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,mBAAmB,EAAE,EAAE,uBAAuB,+BAmJzG"}
@@ -4,6 +4,12 @@ import { useState, useEffect } from 'react';
4
4
  // ---------------------------------------------------------------------------
5
5
  // MiniBar
6
6
  // ---------------------------------------------------------------------------
7
+ // Format a stored timestamp ("…T…Z" or "YYYY-MM-DD HH:MM:SS") to second precision.
8
+ function fmtTs(iso) {
9
+ if (!iso)
10
+ return '';
11
+ return iso.slice(0, 19).replace('T', ' ');
12
+ }
7
13
  function MiniBar({ value, max, color = '#6366f1' }) {
8
14
  const pct = max > 0 ? (value / max) * 100 : 0;
9
15
  return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex-1 h-2 rounded bg-gray-100 overflow-hidden", children: _jsx("div", { className: "h-full rounded", style: { width: `${pct}%`, backgroundColor: color } }) }), _jsx("span", { className: "text-xs tabular-nums text-gray-600 w-8 text-right", children: value })] }));
@@ -63,7 +69,7 @@ export function SearchQueriesPanel({ appId, apiBase, since, resolveUserProfiles
63
69
  const maxTermCount = data.top_terms.length > 0 ? Math.max(...data.top_terms.map((t) => t.count), 1) : 1;
64
70
  const maxZeroCount = data.zero_result_terms.length > 0 ? Math.max(...data.zero_result_terms.map((t) => t.zeroResults), 1) : 1;
65
71
  const maxDayCount = data.over_time.length > 0 ? Math.max(...data.over_time.map((d) => d.count), 1) : 1;
66
- return (_jsxs("div", { className: "flex flex-col gap-6 p-4", children: [_jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [_jsx(SummaryCard, { label: "Total Searches", value: data.total_searches }), _jsx(SummaryCard, { label: "Unique Terms", value: data.unique_terms }), _jsx(SummaryCard, { label: "Unique Searchers", value: data.unique_searchers }), _jsx(SummaryCard, { label: "Zero-Result Searches", value: data.zero_result_searches })] }), _jsxs("div", { className: "rounded-lg border bg-white p-4 flex flex-col gap-3", children: [_jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Top Search Terms" }), data.top_terms.length === 0 ? (_jsx("p", { className: "text-xs text-gray-400", children: "No searches recorded yet." })) : (_jsx("div", { className: "flex flex-col gap-1", children: data.top_terms.map((t) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-xs text-gray-600 w-32 truncate", title: t.term, children: t.term }), _jsx(MiniBar, { value: t.count, max: maxTermCount })] }, t.term))) }))] }), data.zero_result_terms.length > 0 && (_jsxs("div", { className: "rounded-lg border border-amber-200 bg-amber-50 p-4 flex flex-col gap-3", children: [_jsxs("h3", { className: "text-sm font-semibold text-amber-800", children: ["Zero-Result Terms ", _jsx("span", { className: "text-xs font-normal text-amber-600", children: "(content gaps)" })] }), _jsx("div", { className: "flex flex-col gap-1", children: data.zero_result_terms.map((t) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-xs text-amber-700 w-32 truncate", title: t.term, children: t.term }), _jsx(MiniBar, { value: t.zeroResults, max: maxZeroCount, color: "#f59e0b" })] }, t.term))) })] })), data.over_time.length > 0 && (_jsxs("div", { className: "rounded-lg border bg-white p-4 flex flex-col gap-3", children: [_jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Searches Over Time" }), _jsx("div", { className: "flex flex-col gap-1", children: data.over_time.map((d) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-[10px] text-gray-400 w-20", children: d.day }), _jsx(MiniBar, { value: d.count, max: maxDayCount, color: "#10b981" })] }, d.day))) })] })), data.per_user.length > 0 && (_jsxs("div", { className: "rounded-lg border bg-white p-4 flex flex-col gap-3", children: [_jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Per-User Searches" }), _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-xs border-collapse", children: [_jsx("thead", { children: _jsxs("tr", { className: "text-left text-gray-400 border-b", children: [_jsx("th", { className: "py-1 pr-4 font-medium", children: "User" }), _jsx("th", { className: "py-1 pr-4 font-medium tabular-nums", children: "Searches" }), _jsx("th", { className: "py-1 pr-4 font-medium tabular-nums", children: "Distinct Terms" }), _jsx("th", { className: "py-1 font-medium", children: "Recent Terms" })] }) }), _jsx("tbody", { children: data.per_user.map((u) => {
72
+ return (_jsxs("div", { className: "flex flex-col gap-6 p-4", children: [_jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [_jsx(SummaryCard, { label: "Total Searches", value: data.total_searches }), _jsx(SummaryCard, { label: "Unique Terms", value: data.unique_terms }), _jsx(SummaryCard, { label: "Unique Searchers", value: data.unique_searchers }), _jsx(SummaryCard, { label: "Zero-Result Searches", value: data.zero_result_searches })] }), _jsxs("div", { className: "rounded-lg border bg-white p-4 flex flex-col gap-3", children: [_jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Top Search Terms" }), data.top_terms.length === 0 ? (_jsx("p", { className: "text-xs text-gray-400", children: "No searches recorded yet." })) : (_jsx("div", { className: "flex flex-col gap-1", children: data.top_terms.map((t) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-xs text-gray-600 w-32 truncate", title: t.term, children: t.term }), _jsx(MiniBar, { value: t.count, max: maxTermCount }), _jsx("span", { className: "text-[10px] text-gray-400 tabular-nums whitespace-nowrap w-32 text-right", title: `Last searched ${t.lastSearchedAt}`, children: fmtTs(t.lastSearchedAt) })] }, t.term))) }))] }), data.zero_result_terms.length > 0 && (_jsxs("div", { className: "rounded-lg border border-amber-200 bg-amber-50 p-4 flex flex-col gap-3", children: [_jsxs("h3", { className: "text-sm font-semibold text-amber-800", children: ["Zero-Result Terms ", _jsx("span", { className: "text-xs font-normal text-amber-600", children: "(content gaps)" })] }), _jsx("div", { className: "flex flex-col gap-1", children: data.zero_result_terms.map((t) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-xs text-amber-700 w-32 truncate", title: t.term, children: t.term }), _jsx(MiniBar, { value: t.zeroResults, max: maxZeroCount, color: "#f59e0b" }), _jsx("span", { className: "text-[10px] text-amber-600 tabular-nums whitespace-nowrap w-32 text-right", title: `Last searched ${t.lastSearchedAt}`, children: fmtTs(t.lastSearchedAt) })] }, t.term))) })] })), data.over_time.length > 0 && (_jsxs("div", { className: "rounded-lg border bg-white p-4 flex flex-col gap-3", children: [_jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Searches Over Time" }), _jsx("div", { className: "flex flex-col gap-1", children: data.over_time.map((d) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-[10px] text-gray-400 tabular-nums w-36 whitespace-nowrap", children: fmtTs(d.day) }), _jsx(MiniBar, { value: d.count, max: maxDayCount, color: "#10b981" })] }, d.day))) })] })), data.per_user.length > 0 && (_jsxs("div", { className: "rounded-lg border bg-white p-4 flex flex-col gap-3", children: [_jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Per-User Searches" }), _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-xs border-collapse", children: [_jsx("thead", { children: _jsxs("tr", { className: "text-left text-gray-400 border-b", children: [_jsx("th", { className: "py-1 pr-4 font-medium", children: "User" }), _jsx("th", { className: "py-1 pr-4 font-medium tabular-nums", children: "Searches" }), _jsx("th", { className: "py-1 pr-4 font-medium tabular-nums", children: "Distinct Terms" }), _jsx("th", { className: "py-1 font-medium", children: "Recent Terms" })] }) }), _jsx("tbody", { children: data.per_user.map((u) => {
67
73
  const profile = profiles.get(u.user_id);
68
74
  const displayName = profile?.name ?? profile?.email ?? u.user_id.slice(0, 8) + '…';
69
75
  return (_jsxs("tr", { className: "border-b border-gray-50", children: [_jsxs("td", { className: "py-1 pr-4 text-gray-700", children: [profile?.avatar_url && (_jsx("img", { src: profile.avatar_url, alt: "", className: "inline-block w-4 h-4 rounded-full mr-1 align-middle" })), displayName] }), _jsx("td", { className: "py-1 pr-4 tabular-nums text-gray-600", children: u.count }), _jsx("td", { className: "py-1 pr-4 tabular-nums text-gray-600", children: u.distinct_terms }), _jsxs("td", { className: "py-1 text-gray-500", children: [u.terms.slice(0, 5).join(', '), u.terms.length > 5 ? '…' : ''] })] }, u.user_id));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hazo_umetrics",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
4
4
  "description": "Product analytics for hazo apps — GA4 hybrid + first-party stat store + feature flags",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",
@@ -51,7 +51,7 @@
51
51
  "hazo_auth": "^10.8.0",
52
52
  "hazo_secure": "^1.3.0",
53
53
  "hazo_audit": "^2.1.2",
54
- "hazo_ui": "^4.10.0",
54
+ "hazo_ui": "^5.0.0",
55
55
  "next": "^14.0.0 || ^16.0.0",
56
56
  "react": "^18.0.0 || ^19.0.0",
57
57
  "react-dom": "^18.0.0 || ^19.0.0"