hazo_umetrics 1.6.3 → 1.7.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 +36 -0
- package/dist/api/index.d.ts +5 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +26 -1
- package/dist/index.client.d.ts +4 -0
- package/dist/index.client.d.ts.map +1 -1
- package/dist/index.client.js +3 -0
- package/dist/server/behavior.d.ts +25 -0
- package/dist/server/behavior.d.ts.map +1 -1
- package/dist/server/behavior.js +94 -2
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/search_queries_panel.d.ts +15 -0
- package/dist/ui/search_queries_panel.d.ts.map +1 -0
- package/dist/ui/search_queries_panel.js +71 -0
- package/package.json +1 -1
package/CHANGE_LOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# hazo_umetrics — Change Log
|
|
2
2
|
|
|
3
|
+
## 1.7.0 — Search query analytics: `searchQueries`, `SearchQueriesPanel`, `getIdentity`
|
|
4
|
+
|
|
5
|
+
**New features (minor — no breaking changes):**
|
|
6
|
+
|
|
7
|
+
### `getIdentity()` — exported client identity accessor
|
|
8
|
+
|
|
9
|
+
- New export from `hazo_umetrics/client`: `getIdentity(): { session_id: string; user_id: string | null }`.
|
|
10
|
+
- Lets consumers attribute custom events with the SDK's tracked identity without going through `useMetrics`.
|
|
11
|
+
|
|
12
|
+
### `searchQueries()` — server-side search aggregation
|
|
13
|
+
|
|
14
|
+
- New function in `src/server/behavior.ts`: `searchQueries(adapter, opts)`.
|
|
15
|
+
- Reads `action.search` rows; returns `SearchQueriesResult` with: `total_searches`, `unique_terms`, `unique_searchers`, `zero_result_searches`, `top_terms` (ranked by count), `zero_result_terms` (content gaps — searches with `result_count === 0`), `over_time` (per-day buckets), `per_user` (rows with real `user_id`).
|
|
16
|
+
- Term normalization: `.trim().toLowerCase()` for tallying; first-seen casing preserved for display.
|
|
17
|
+
- `result_count` missing ≠ zero; only explicit `0` counts as zero-result.
|
|
18
|
+
|
|
19
|
+
### `getSearchQueries` API handler
|
|
20
|
+
|
|
21
|
+
- Added to `createBehaviorHandlers`: `GET analytics/search-queries?app_id=…[&since=…][&top_n=…][&user_ids=…]`.
|
|
22
|
+
- Gates on `metrics.view`; returns `{ ok: true, data: SearchQueriesResult }`.
|
|
23
|
+
|
|
24
|
+
### `SearchQueriesPanel` — admin UI panel
|
|
25
|
+
|
|
26
|
+
- New client component exported from `hazo_umetrics/ui`: `SearchQueriesPanel({ appId, apiBase, since?, resolveUserProfiles? })`.
|
|
27
|
+
- Renders summary cards (total searches, unique terms, unique searchers, zero-result count), top-terms mini-bar chart, zero-result terms section (amber highlight — content gaps), searches-over-time day bars, and per-user table with optional profile resolution.
|
|
28
|
+
|
|
29
|
+
## 1.6.4 — Fix: empty `datapoint_schema` hid all datapoint dimensions
|
|
30
|
+
|
|
31
|
+
**Fix (patch — no breaking changes):**
|
|
32
|
+
|
|
33
|
+
### `datapointStats` treated an empty schema array as "restrict to zero keys"
|
|
34
|
+
|
|
35
|
+
- `getDatapoints` passes the action's `datapoint_schema` as `opts.schema`. For actions with no declared schema this is `[]`, which is truthy — so `datapointStats` built an empty `schemaKeys` Set and then filtered out **every** discovered dimension via `if (schemaKeys && !schemaKeys.has(k)) continue`.
|
|
36
|
+
- Result: any action without an explicit schema showed "No datapoint dimensions found" even when its events carried non-reserved dimensions (e.g. `timer`, `duration_bucket`).
|
|
37
|
+
- Now an empty/absent schema means "no constraint — surface all discovered dimensions"; a non-empty schema still restricts to the declared keys.
|
|
38
|
+
|
|
3
39
|
## 1.5.1 — `ui.pageexit.*` rename, smart auto-labels, `defaultActionLabels`, `ActionsPanel.refreshTrigger`
|
|
4
40
|
|
|
5
41
|
**Fixes / improvements (patch — no breaking changes for new installs; existing `ui.dwell.*` catalog rows need re-seeding):**
|
package/dist/api/index.d.ts
CHANGED
|
@@ -178,5 +178,10 @@ export declare function createBehaviorHandlers(options: ApiFactoryOptions): {
|
|
|
178
178
|
* Returns user journey sessions and aggregated graph. Requires metrics.view.
|
|
179
179
|
*/
|
|
180
180
|
getJourney(req: Request): Promise<Response>;
|
|
181
|
+
/**
|
|
182
|
+
* GET /...?app_id=<id>[&since=<iso>][&top_n=<n>][&user_ids=<csv>]
|
|
183
|
+
* Returns search query analytics. Requires metrics.view.
|
|
184
|
+
*/
|
|
185
|
+
getSearchQueries(req: Request): Promise<Response>;
|
|
181
186
|
};
|
|
182
187
|
//# 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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"}
|
package/dist/api/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { recordStat as _recordStat, getLatestStat as _getLatestStat, getStatSeri
|
|
|
9
9
|
import { resolveVariant } from '../server/flags.js';
|
|
10
10
|
import { getActivitySummary as _getActivitySummary, getActivityTrend as _getActivityTrend, } from '../server/activity.js';
|
|
11
11
|
import { ensureActionDef as _ensureActionDef, listActions as _listActions, getActionTree as _getActionTree, updateActionDef as _updateActionDef, updateActionDefBulk as _updateActionDefBulk, applyLinksBulk as _applyLinksBulk, getLinks as _getLinks, addLink as _addLink, removeLink as _removeLink, } from '../server/actions.js';
|
|
12
|
-
import { topActions as _topActions, firstSessionActions as _firstSessionActions, postReloginActions as _postReloginActions, actionsBeforeGoal as _actionsBeforeGoal, topErrors as _topErrors, tabDwell as _tabDwell, frequentActionsReturning as _frequentActionsReturning, eventsOverTime as _eventsOverTime, datapointStats as _datapointStats, userJourney as _userJourney, } from '../server/behavior.js';
|
|
12
|
+
import { topActions as _topActions, firstSessionActions as _firstSessionActions, postReloginActions as _postReloginActions, actionsBeforeGoal as _actionsBeforeGoal, topErrors as _topErrors, tabDwell as _tabDwell, frequentActionsReturning as _frequentActionsReturning, eventsOverTime as _eventsOverTime, datapointStats as _datapointStats, userJourney as _userJourney, searchQueries as _searchQueries, } from '../server/behavior.js';
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
// Default auth — dynamically imports hazo_auth/server-lib to avoid the
|
|
15
15
|
// server-only guard firing at module-evaluation time (e.g. in test runners).
|
|
@@ -876,5 +876,30 @@ export function createBehaviorHandlers(options) {
|
|
|
876
876
|
const result = await _userJourney(adapter, { app_id, user_id, session_id, since, catalogMap, splitByPage, pageFlow });
|
|
877
877
|
return jsonOk(result);
|
|
878
878
|
},
|
|
879
|
+
/**
|
|
880
|
+
* GET /...?app_id=<id>[&since=<iso>][&top_n=<n>][&user_ids=<csv>]
|
|
881
|
+
* Returns search query analytics. Requires metrics.view.
|
|
882
|
+
*/
|
|
883
|
+
async getSearchQueries(req) {
|
|
884
|
+
const url = new URL(req.url);
|
|
885
|
+
const app_id = url.searchParams.get('app_id') ?? '';
|
|
886
|
+
const since = url.searchParams.get('since') ?? undefined;
|
|
887
|
+
const topNParam = url.searchParams.get('top_n');
|
|
888
|
+
const topN = topNParam != null ? parseInt(topNParam, 10) : undefined;
|
|
889
|
+
const userIdsParam = url.searchParams.get('user_ids');
|
|
890
|
+
const userIds = userIdsParam ? userIdsParam.split(',').filter(Boolean) : undefined;
|
|
891
|
+
const gate = await gateAuth(req, {
|
|
892
|
+
required_permissions: ['metrics.view'],
|
|
893
|
+
app_id: app_id || undefined,
|
|
894
|
+
getAuth,
|
|
895
|
+
});
|
|
896
|
+
if (!gate.ok)
|
|
897
|
+
return gate.response;
|
|
898
|
+
if (!app_id)
|
|
899
|
+
return jsonError('BAD_REQUEST', 'app_id is required', 400);
|
|
900
|
+
const adapter = await options.getAdapter();
|
|
901
|
+
const result = await _searchQueries(adapter, { app_id, since, topN, userIds });
|
|
902
|
+
return jsonOk(result);
|
|
903
|
+
},
|
|
879
904
|
};
|
|
880
905
|
}
|
package/dist/index.client.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export declare function useMetrics(): MetricsContextValue;
|
|
|
18
18
|
export declare function getVariant(serverVariant: string): string;
|
|
19
19
|
export declare function isEnabled(serverVariant: string): boolean;
|
|
20
20
|
export declare function identify(userId: string): void;
|
|
21
|
+
export declare function getIdentity(): {
|
|
22
|
+
session_id: string;
|
|
23
|
+
user_id: string | null;
|
|
24
|
+
};
|
|
21
25
|
export declare function recordStat(_key: string, _value: number, _opts?: {
|
|
22
26
|
unit?: string;
|
|
23
27
|
}): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.client.d.ts","sourceRoot":"","sources":["../src/index.client.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,OAAO,CAAC;AAoD1B,UAAU,mBAAmB;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACjC,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC;IACjF,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;KAAE,KAAK,IAAI,CAAC;IAC1G,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;CAClE;AASD,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,wBAAwB,4EAsJxF;AAMD,wBAAgB,UAAU,IAAI,mBAAmB,CAEhD;AAMD,wBAAgB,UAAU,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,SAAS,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAK7C;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAExF"}
|
|
1
|
+
{"version":3,"file":"index.client.d.ts","sourceRoot":"","sources":["../src/index.client.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,OAAO,CAAC;AAoD1B,UAAU,mBAAmB;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACjC,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC;IACjF,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;KAAE,KAAK,IAAI,CAAC;IAC1G,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;CAClE;AASD,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,wBAAwB,4EAsJxF;AAMD,wBAAgB,UAAU,IAAI,mBAAmB,CAEhD;AAMD,wBAAgB,UAAU,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,SAAS,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAK7C;AAED,wBAAgB,WAAW,IAAI;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAE5E;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAExF"}
|
package/dist/index.client.js
CHANGED
|
@@ -199,6 +199,9 @@ export function identify(userId) {
|
|
|
199
199
|
localStorage.setItem('hazo_umetrics_uid', userId);
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
|
+
export function getIdentity() {
|
|
203
|
+
return { session_id: getSessionId(), user_id: getUserId() };
|
|
204
|
+
}
|
|
202
205
|
export function recordStat(_key, _value, _opts) {
|
|
203
206
|
// M1: client-side stat record via API
|
|
204
207
|
}
|
|
@@ -156,4 +156,29 @@ export declare function datapointStats(adapter: HazoConnectAdapter, opts: {
|
|
|
156
156
|
topN?: number;
|
|
157
157
|
schema?: DatapointSchema;
|
|
158
158
|
}): Promise<DatapointStatsResult>;
|
|
159
|
+
export interface SearchTermEntry {
|
|
160
|
+
term: string;
|
|
161
|
+
count: number;
|
|
162
|
+
searchers: number;
|
|
163
|
+
zeroResults: number;
|
|
164
|
+
lastSearchedAt: string;
|
|
165
|
+
}
|
|
166
|
+
export interface SearchQueriesResult {
|
|
167
|
+
total_searches: number;
|
|
168
|
+
unique_terms: number;
|
|
169
|
+
unique_searchers: number;
|
|
170
|
+
zero_result_searches: number;
|
|
171
|
+
top_terms: SearchTermEntry[];
|
|
172
|
+
zero_result_terms: SearchTermEntry[];
|
|
173
|
+
over_time: EventDayBucket[];
|
|
174
|
+
per_user: Array<{
|
|
175
|
+
user_id: string;
|
|
176
|
+
count: number;
|
|
177
|
+
distinct_terms: number;
|
|
178
|
+
terms: string[];
|
|
179
|
+
}>;
|
|
180
|
+
}
|
|
181
|
+
export declare function searchQueries(adapter: HazoConnectAdapter, opts: BehaviorOptions & {
|
|
182
|
+
topN?: number;
|
|
183
|
+
}): Promise<SearchQueriesResult>;
|
|
159
184
|
//# sourceMappingURL=behavior.d.ts.map
|
|
@@ -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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"}
|
package/dist/server/behavior.js
CHANGED
|
@@ -511,8 +511,11 @@ export async function datapointStats(adapter, opts) {
|
|
|
511
511
|
filtered = filtered.filter((r) => r.captured_at <= opts.until);
|
|
512
512
|
}
|
|
513
513
|
const total_events = filtered.length;
|
|
514
|
-
|
|
515
|
-
|
|
514
|
+
// An empty schema array means "no schema declared" — restrict to nothing would
|
|
515
|
+
// wrongly filter out every discovered dimension. Only constrain when non-empty.
|
|
516
|
+
const hasSchema = Array.isArray(opts.schema) && opts.schema.length > 0;
|
|
517
|
+
const schemaKeys = hasSchema ? new Set(opts.schema.map((f) => f.key)) : null;
|
|
518
|
+
const schemaMap = hasSchema ? new Map(opts.schema.map((f) => [f.key, f])) : null;
|
|
516
519
|
const allValues = new Map();
|
|
517
520
|
for (const row of filtered) {
|
|
518
521
|
const dims = parseDims(row.dimensions);
|
|
@@ -575,3 +578,92 @@ export async function datapointStats(adapter, opts) {
|
|
|
575
578
|
keys.sort((a, b) => a.key.localeCompare(b.key));
|
|
576
579
|
return { action_key: opts.action_key, total_events, keys };
|
|
577
580
|
}
|
|
581
|
+
export async function searchQueries(adapter, opts) {
|
|
582
|
+
const { app_id, scope_id, since, limit = 10000 } = opts;
|
|
583
|
+
const topN_ = opts.topN ?? 20;
|
|
584
|
+
const rows = await fetchWindow(adapter, { app_id, scope_id, since: since ?? defaultSince(), cap: limit });
|
|
585
|
+
const searchRows = rows.filter((r) => r.metric_key === 'action.search');
|
|
586
|
+
const termMap = new Map();
|
|
587
|
+
const dayMap = new Map();
|
|
588
|
+
const userMap = new Map();
|
|
589
|
+
let zeroResultSearches = 0;
|
|
590
|
+
const allSearchers = new Set();
|
|
591
|
+
for (const row of searchRows) {
|
|
592
|
+
const dims = parseDims(row.dimensions);
|
|
593
|
+
const rawTerm = typeof dims.term === 'string' ? dims.term : String(dims.term ?? '');
|
|
594
|
+
if (!rawTerm)
|
|
595
|
+
continue;
|
|
596
|
+
const normTerm = rawTerm.trim().toLowerCase();
|
|
597
|
+
const uid = dims.user_id;
|
|
598
|
+
const sid = dims.session_id;
|
|
599
|
+
const identity = uid ?? sid;
|
|
600
|
+
const resultCount = dims.result_count != null ? Number(dims.result_count) : null;
|
|
601
|
+
const isZero = resultCount === 0;
|
|
602
|
+
if (identity)
|
|
603
|
+
allSearchers.add(identity);
|
|
604
|
+
// Term accumulator
|
|
605
|
+
if (!termMap.has(normTerm)) {
|
|
606
|
+
termMap.set(normTerm, {
|
|
607
|
+
count: 0,
|
|
608
|
+
searchers: new Set(),
|
|
609
|
+
zeroResults: 0,
|
|
610
|
+
firstCasing: rawTerm.trim(),
|
|
611
|
+
lastAt: row.captured_at,
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
const acc = termMap.get(normTerm);
|
|
615
|
+
acc.count++;
|
|
616
|
+
if (identity)
|
|
617
|
+
acc.searchers.add(identity);
|
|
618
|
+
if (isZero) {
|
|
619
|
+
acc.zeroResults++;
|
|
620
|
+
zeroResultSearches++;
|
|
621
|
+
}
|
|
622
|
+
if (row.captured_at > acc.lastAt)
|
|
623
|
+
acc.lastAt = row.captured_at;
|
|
624
|
+
// Day bucket
|
|
625
|
+
const day = dayBucket(row.captured_at);
|
|
626
|
+
dayMap.set(day, (dayMap.get(day) ?? 0) + 1);
|
|
627
|
+
// Per-user
|
|
628
|
+
if (uid) {
|
|
629
|
+
if (!userMap.has(uid))
|
|
630
|
+
userMap.set(uid, { count: 0, terms: new Set() });
|
|
631
|
+
const ua = userMap.get(uid);
|
|
632
|
+
ua.count++;
|
|
633
|
+
ua.terms.add(normTerm);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
const allTermEntries = [...termMap.entries()].map(([, acc]) => ({
|
|
637
|
+
term: acc.firstCasing,
|
|
638
|
+
count: acc.count,
|
|
639
|
+
searchers: acc.searchers.size,
|
|
640
|
+
zeroResults: acc.zeroResults,
|
|
641
|
+
lastSearchedAt: acc.lastAt,
|
|
642
|
+
}));
|
|
643
|
+
const top_terms = [...allTermEntries].sort((a, b) => b.count - a.count).slice(0, topN_);
|
|
644
|
+
const zero_result_terms = allTermEntries
|
|
645
|
+
.filter((e) => e.zeroResults > 0)
|
|
646
|
+
.sort((a, b) => b.zeroResults - a.zeroResults)
|
|
647
|
+
.slice(0, topN_);
|
|
648
|
+
const over_time = [...dayMap.entries()]
|
|
649
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
650
|
+
.map(([day, count]) => ({ day, count }));
|
|
651
|
+
const per_user = [...userMap.entries()]
|
|
652
|
+
.map(([user_id, { count, terms }]) => ({
|
|
653
|
+
user_id,
|
|
654
|
+
count,
|
|
655
|
+
distinct_terms: terms.size,
|
|
656
|
+
terms: [...terms],
|
|
657
|
+
}))
|
|
658
|
+
.sort((a, b) => b.count - a.count);
|
|
659
|
+
return {
|
|
660
|
+
total_searches: searchRows.length,
|
|
661
|
+
unique_terms: termMap.size,
|
|
662
|
+
unique_searchers: allSearchers.size,
|
|
663
|
+
zero_result_searches: zeroResultSearches,
|
|
664
|
+
top_terms,
|
|
665
|
+
zero_result_terms,
|
|
666
|
+
over_time,
|
|
667
|
+
per_user,
|
|
668
|
+
};
|
|
669
|
+
}
|
package/dist/ui/index.d.ts
CHANGED
package/dist/ui/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAGA,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAGA,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC"}
|
package/dist/ui/index.js
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface UserProfile {
|
|
2
|
+
id: string;
|
|
3
|
+
name?: string;
|
|
4
|
+
email?: string;
|
|
5
|
+
avatar_url?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SearchQueriesPanelProps {
|
|
8
|
+
appId: string;
|
|
9
|
+
apiBase: string;
|
|
10
|
+
since?: string;
|
|
11
|
+
resolveUserProfiles?: (ids: string[]) => Promise<UserProfile[]>;
|
|
12
|
+
}
|
|
13
|
+
export declare function SearchQueriesPanel({ appId, apiBase, since, resolveUserProfiles }: SearchQueriesPanelProps): import("react").JSX.Element;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=search_queries_panel.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// MiniBar
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
function MiniBar({ value, max, color = '#6366f1' }) {
|
|
8
|
+
const pct = max > 0 ? (value / max) * 100 : 0;
|
|
9
|
+
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 })] }));
|
|
10
|
+
}
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// SummaryCard
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
function SummaryCard({ label, value }) {
|
|
15
|
+
return (_jsxs("div", { className: "rounded-lg border bg-white p-4 flex flex-col gap-1", children: [_jsx("p", { className: "text-[10px] text-gray-400 uppercase tracking-wide", children: label }), _jsx("p", { className: "text-2xl font-semibold text-gray-800 tabular-nums", children: value })] }));
|
|
16
|
+
}
|
|
17
|
+
export function SearchQueriesPanel({ appId, apiBase, since, resolveUserProfiles }) {
|
|
18
|
+
const [data, setData] = useState(null);
|
|
19
|
+
const [profiles, setProfiles] = useState(new Map());
|
|
20
|
+
const [loading, setLoading] = useState(false);
|
|
21
|
+
const [error, setError] = useState(null);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!appId)
|
|
24
|
+
return;
|
|
25
|
+
let cancelled = false;
|
|
26
|
+
setLoading(true);
|
|
27
|
+
setError(null);
|
|
28
|
+
const params = new URLSearchParams({ app_id: appId });
|
|
29
|
+
if (since)
|
|
30
|
+
params.set('since', since);
|
|
31
|
+
fetch(`${apiBase}/analytics/search-queries?${params}`, { credentials: 'include' })
|
|
32
|
+
.then((r) => r.json().catch(() => null))
|
|
33
|
+
.then((body) => {
|
|
34
|
+
if (cancelled)
|
|
35
|
+
return;
|
|
36
|
+
setData(body?.data ?? null);
|
|
37
|
+
setLoading(false);
|
|
38
|
+
})
|
|
39
|
+
.catch((err) => {
|
|
40
|
+
if (cancelled)
|
|
41
|
+
return;
|
|
42
|
+
setError(err instanceof Error ? err.message : 'Load error');
|
|
43
|
+
setLoading(false);
|
|
44
|
+
});
|
|
45
|
+
return () => { cancelled = true; };
|
|
46
|
+
}, [appId, apiBase, since]);
|
|
47
|
+
// Resolve user profiles after data loads
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!data || !resolveUserProfiles || data.per_user.length === 0)
|
|
50
|
+
return;
|
|
51
|
+
const ids = data.per_user.map((u) => u.user_id);
|
|
52
|
+
resolveUserProfiles(ids).then((resolved) => {
|
|
53
|
+
const map = new Map(resolved.map((p) => [p.id, p]));
|
|
54
|
+
setProfiles(map);
|
|
55
|
+
}).catch(() => { });
|
|
56
|
+
}, [data, resolveUserProfiles]);
|
|
57
|
+
if (loading)
|
|
58
|
+
return _jsx("div", { className: "p-4 text-xs text-gray-400", children: "Loading\u2026" });
|
|
59
|
+
if (error)
|
|
60
|
+
return _jsx("div", { className: "rounded-lg border border-red-200 bg-red-50 p-3 text-xs text-red-700", children: error });
|
|
61
|
+
if (!data)
|
|
62
|
+
return _jsx("div", { className: "rounded-lg border p-6 text-center text-sm text-gray-400", children: "No data yet." });
|
|
63
|
+
const maxTermCount = data.top_terms.length > 0 ? Math.max(...data.top_terms.map((t) => t.count), 1) : 1;
|
|
64
|
+
const maxZeroCount = data.zero_result_terms.length > 0 ? Math.max(...data.zero_result_terms.map((t) => t.zeroResults), 1) : 1;
|
|
65
|
+
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) => {
|
|
67
|
+
const profile = profiles.get(u.user_id);
|
|
68
|
+
const displayName = profile?.name ?? profile?.email ?? u.user_id.slice(0, 8) + '…';
|
|
69
|
+
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));
|
|
70
|
+
}) })] }) })] }))] }));
|
|
71
|
+
}
|