Skip to content

Commit add6620

Browse files
stats: display separately counted uniques (#1403)
1 parent a493ba2 commit add6620

6 files changed

Lines changed: 200 additions & 114 deletions

File tree

znai-docs/znai/release-notes/1.84.1/fix-2026-01-02-doc-stats-pass-missing-doc-id.md renamed to znai-docs/znai/release-notes/1.85/fix-2026-01-02-doc-stats-pass-missing-doc-id.md

File renamed without changes.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
# 1.84.1
1+
# 1.85
22

3-
:include-markdowns: 1.84.1
3+
:include-markdowns: 1.85

znai-enterprise-sample-server/znai-enterprise-sample-server.py

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,10 @@ def load_tracking_events():
264264
def calculate_page_stats(events, start_time=None):
265265
page_views = defaultdict(int)
266266
page_unique = defaultdict(set)
267+
chapter_views = defaultdict(int)
268+
chapter_unique = defaultdict(set)
269+
overall_views = 0
270+
overall_unique = set()
267271

268272
for event in events:
269273
if event.get('eventType') != 'pageOpen':
@@ -282,36 +286,66 @@ def calculate_page_stats(events, start_time=None):
282286
if not page_id:
283287
continue
284288

285-
page_views[page_id] += 1
286-
287289
data_str = event.get('data', '{}')
288290
try:
289291
data = json.loads(data_str) if data_str else {}
290292
except json.JSONDecodeError:
291293
data = {}
292294

293295
visitor_id = data.get('visitorId', timestamp_str)
296+
297+
# Page stats
298+
page_views[page_id] += 1
294299
page_unique[page_id].add(visitor_id)
295300

296-
result = {}
301+
# Chapter stats (key is dirName portion of pageId)
302+
chapter_key = page_id.split('/')[0] if '/' in page_id else ''
303+
chapter_views[chapter_key] += 1
304+
chapter_unique[chapter_key].add(visitor_id)
305+
306+
# Overall stats
307+
overall_views += 1
308+
overall_unique.add(visitor_id)
309+
310+
pages = {}
297311
all_pages = set(page_views.keys()) | set(page_unique.keys())
298312
for page_id in all_pages:
299-
result[page_id] = {
313+
pages[page_id] = {
300314
'totalViews': page_views[page_id],
301315
'uniqueViews': len(page_unique[page_id])
302316
}
303317

304-
return result
318+
chapters = {}
319+
all_chapters = set(chapter_views.keys()) | set(chapter_unique.keys())
320+
for chapter_key in all_chapters:
321+
chapters[chapter_key] = {
322+
'totalViews': chapter_views[chapter_key],
323+
'uniqueViews': len(chapter_unique[chapter_key])
324+
}
305325

306-
def apply_stats_multiplier(page_stats, multiplier):
307-
result = {}
308-
for page_id, stats in page_stats.items():
309-
total_views = stats['totalViews'] * multiplier
310-
result[page_id] = {
311-
'totalViews': int(total_views),
312-
'uniqueViews': int(total_views * 0.2)
326+
return {
327+
'overall': {'totalViews': overall_views, 'uniqueViews': len(overall_unique)},
328+
'chapters': chapters,
329+
'pages': pages
330+
}
331+
332+
def apply_stats_multiplier(stats, multiplier):
333+
def multiply_stat(stat):
334+
total_views = stat['totalViews'] * multiplier
335+
total_views_int = int(total_views)
336+
unique_views = int(total_views * 0.2)
337+
if total_views_int > 0 and unique_views == 0:
338+
unique_views = 1
339+
return {
340+
'totalViews': total_views_int,
341+
'uniqueViews': unique_views
313342
}
314-
return result
343+
344+
return {
345+
'overall': multiply_stat(stats['overall']),
346+
'chapters': {k: multiply_stat(v) for k, v in stats['chapters'].items()},
347+
'pages': {k: multiply_stat(v) for k, v in stats['pages'].items()}
348+
}
315349

316350
@app.route('/doc-stats', methods=['GET'])
317351
def get_doc_stats():

znai-reactjs/src/screens/doc-stats/DocStatsScreen.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@ import { DocStatsView, PageStats, TimePeriod } from "./DocStatsView";
2222

2323
const AVAILABLE_PERIODS: TimePeriod[] = ["week", "month", "year", "total"];
2424

25-
export type DocStatsResponse = Record<TimePeriod, Record<string, PageStats>>;
25+
export interface PeriodStats {
26+
overall: PageStats;
27+
chapters: Record<string, PageStats>;
28+
pages: Record<string, PageStats>;
29+
}
30+
31+
export type DocStatsResponse = Record<TimePeriod, PeriodStats>;
2632

2733
export interface DocStatsScreenProps {
2834
toc: TocItem[];
@@ -77,13 +83,15 @@ export function DocStatsScreen({ toc, docMeta }: DocStatsScreenProps) {
7783
return null;
7884
}
7985

80-
const pageStats = statsByPeriod[selectedPeriod] || {};
86+
const periodStats = statsByPeriod[selectedPeriod] || { overall: { totalViews: 0, uniqueViews: 0 }, chapters: {}, pages: {} };
8187

8288
return (
8389
<DocStatsView
8490
guideName={getDocMeta().title}
8591
toc={toc}
86-
pageStats={pageStats}
92+
overallStats={periodStats.overall}
93+
chapterStats={periodStats.chapters}
94+
pageStats={periodStats.pages}
8795
selectedPeriod={selectedPeriod}
8896
availablePeriods={AVAILABLE_PERIODS}
8997
onPeriodChange={setSelectedPeriod}

znai-reactjs/src/screens/doc-stats/DocStatsView.demo.tsx

Lines changed: 122 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -96,88 +96,139 @@ const demoToc: TocItem[] = [
9696
},
9797
];
9898

99-
const statsByPeriod: Record<TimePeriod, Record<string, PageStats>> = {
99+
interface PeriodStats {
100+
overall: PageStats;
101+
chapters: Record<string, PageStats>;
102+
pages: Record<string, PageStats>;
103+
}
104+
105+
const statsByPeriod: Record<TimePeriod, PeriodStats> = {
100106
week: {
101-
"getting-started": { totalViews: 85, uniqueViews: 42 },
102-
"introduction/what-is-this": { totalViews: 68, uniqueViews: 35 },
103-
"introduction/installation": { totalViews: 54, uniqueViews: 28 },
104-
"introduction/quick-start": { totalViews: 47, uniqueViews: 24 },
105-
"core-concepts/architecture": { totalViews: 0, uniqueViews: 0 },
106-
"core-concepts/configuration": { totalViews: 0, uniqueViews: 0 },
107-
"core-concepts/plugins": { totalViews: 0, uniqueViews: 0 },
108-
"core-concepts/theming": { totalViews: 0, uniqueViews: 0 },
109-
"api/rest-endpoints": { totalViews: 43, uniqueViews: 22 },
110-
"api/authentication": { totalViews: 36, uniqueViews: 19 },
111-
"api/error-handling": { totalViews: 14, uniqueViews: 7 },
112-
"advanced/performance": { totalViews: 11, uniqueViews: 5 },
113-
"advanced/extensions": { totalViews: 6, uniqueViews: 3 },
114-
"legacy/old-api": { totalViews: 23, uniqueViews: 12 },
115-
"removed/deprecated-feature": { totalViews: 8, uniqueViews: 4 },
107+
overall: { totalViews: 395, uniqueViews: 120 },
108+
chapters: {
109+
"": { totalViews: 85, uniqueViews: 42 },
110+
"introduction": { totalViews: 169, uniqueViews: 60 },
111+
"core-concepts": { totalViews: 0, uniqueViews: 0 },
112+
"api": { totalViews: 93, uniqueViews: 35 },
113+
"advanced": { totalViews: 17, uniqueViews: 6 },
114+
},
115+
pages: {
116+
"getting-started": { totalViews: 85, uniqueViews: 42 },
117+
"introduction/what-is-this": { totalViews: 68, uniqueViews: 35 },
118+
"introduction/installation": { totalViews: 54, uniqueViews: 28 },
119+
"introduction/quick-start": { totalViews: 47, uniqueViews: 24 },
120+
"core-concepts/architecture": { totalViews: 0, uniqueViews: 0 },
121+
"core-concepts/configuration": { totalViews: 0, uniqueViews: 0 },
122+
"core-concepts/plugins": { totalViews: 0, uniqueViews: 0 },
123+
"core-concepts/theming": { totalViews: 0, uniqueViews: 0 },
124+
"api/rest-endpoints": { totalViews: 43, uniqueViews: 22 },
125+
"api/authentication": { totalViews: 36, uniqueViews: 19 },
126+
"api/error-handling": { totalViews: 14, uniqueViews: 7 },
127+
"advanced/performance": { totalViews: 11, uniqueViews: 5 },
128+
"advanced/extensions": { totalViews: 6, uniqueViews: 3 },
129+
"legacy/old-api": { totalViews: 23, uniqueViews: 12 },
130+
"removed/deprecated-feature": { totalViews: 8, uniqueViews: 4 },
131+
},
116132
},
117133
month: {
118-
"getting-started": { totalViews: 1542, uniqueViews: 893 },
119-
"introduction/what-is-this": { totalViews: 1235, uniqueViews: 721 },
120-
"introduction/installation": { totalViews: 987, uniqueViews: 543 },
121-
"introduction/quick-start": { totalViews: 854, uniqueViews: 432 },
122-
"core-concepts/architecture": { totalViews: 567, uniqueViews: 345 },
123-
"core-concepts/configuration": { totalViews: 432, uniqueViews: 234 },
124-
"core-concepts/plugins": { totalViews: 321, uniqueViews: 198 },
125-
"core-concepts/theming": { totalViews: 210, uniqueViews: 123 },
126-
"api/rest-endpoints": { totalViews: 789, uniqueViews: 456 },
127-
"api/authentication": { totalViews: 654, uniqueViews: 389 },
128-
"api/error-handling": { totalViews: 234, uniqueViews: 145 },
129-
"advanced/performance": { totalViews: 189, uniqueViews: 102 },
130-
"advanced/extensions": { totalViews: 98, uniqueViews: 65 },
131-
"legacy/old-api": { totalViews: 156, uniqueViews: 89 },
132-
"removed/deprecated-feature": { totalViews: 67, uniqueViews: 34 },
134+
overall: { totalViews: 8112, uniqueViews: 2800 },
135+
chapters: {
136+
"": { totalViews: 1542, uniqueViews: 893 },
137+
"introduction": { totalViews: 3076, uniqueViews: 1100 },
138+
"core-concepts": { totalViews: 1530, uniqueViews: 650 },
139+
"api": { totalViews: 1677, uniqueViews: 700 },
140+
"advanced": { totalViews: 287, uniqueViews: 140 },
141+
},
142+
pages: {
143+
"getting-started": { totalViews: 1542, uniqueViews: 893 },
144+
"introduction/what-is-this": { totalViews: 1235, uniqueViews: 721 },
145+
"introduction/installation": { totalViews: 987, uniqueViews: 543 },
146+
"introduction/quick-start": { totalViews: 854, uniqueViews: 432 },
147+
"core-concepts/architecture": { totalViews: 567, uniqueViews: 345 },
148+
"core-concepts/configuration": { totalViews: 432, uniqueViews: 234 },
149+
"core-concepts/plugins": { totalViews: 321, uniqueViews: 198 },
150+
"core-concepts/theming": { totalViews: 210, uniqueViews: 123 },
151+
"api/rest-endpoints": { totalViews: 789, uniqueViews: 456 },
152+
"api/authentication": { totalViews: 654, uniqueViews: 389 },
153+
"api/error-handling": { totalViews: 234, uniqueViews: 145 },
154+
"advanced/performance": { totalViews: 189, uniqueViews: 102 },
155+
"advanced/extensions": { totalViews: 98, uniqueViews: 65 },
156+
"legacy/old-api": { totalViews: 156, uniqueViews: 89 },
157+
"removed/deprecated-feature": { totalViews: 67, uniqueViews: 34 },
158+
},
133159
},
134160
year: {
135-
"getting-started": { totalViews: 12000, uniqueViews: 7000 },
136-
"introduction/what-is-this": { totalViews: 9500, uniqueViews: 5500 },
137-
"introduction/installation": { totalViews: 7500, uniqueViews: 4200 },
138-
"introduction/quick-start": { totalViews: 6500, uniqueViews: 3300 },
139-
"core-concepts/architecture": { totalViews: 4200, uniqueViews: 2600 },
140-
"core-concepts/configuration": { totalViews: 3200, uniqueViews: 1800 },
141-
"core-concepts/plugins": { totalViews: 2400, uniqueViews: 1500 },
142-
"core-concepts/theming": { totalViews: 1600, uniqueViews: 950 },
143-
"api/rest-endpoints": { totalViews: 5900, uniqueViews: 3400 },
144-
"api/authentication": { totalViews: 4900, uniqueViews: 2900 },
145-
"api/error-handling": { totalViews: 1750, uniqueViews: 1100 },
146-
"advanced/performance": { totalViews: 1400, uniqueViews: 780 },
147-
"advanced/extensions": { totalViews: 740, uniqueViews: 490 },
148-
"legacy/old-api": { totalViews: 1230, uniqueViews: 678 },
149-
"removed/deprecated-feature": { totalViews: 543, uniqueViews: 276 },
161+
overall: { totalViews: 63140, uniqueViews: 22000 },
162+
chapters: {
163+
"": { totalViews: 12000, uniqueViews: 7000 },
164+
"introduction": { totalViews: 23500, uniqueViews: 9000 },
165+
"core-concepts": { totalViews: 11400, uniqueViews: 5000 },
166+
"api": { totalViews: 12550, uniqueViews: 5500 },
167+
"advanced": { totalViews: 2140, uniqueViews: 1000 },
168+
},
169+
pages: {
170+
"getting-started": { totalViews: 12000, uniqueViews: 7000 },
171+
"introduction/what-is-this": { totalViews: 9500, uniqueViews: 5500 },
172+
"introduction/installation": { totalViews: 7500, uniqueViews: 4200 },
173+
"introduction/quick-start": { totalViews: 6500, uniqueViews: 3300 },
174+
"core-concepts/architecture": { totalViews: 4200, uniqueViews: 2600 },
175+
"core-concepts/configuration": { totalViews: 3200, uniqueViews: 1800 },
176+
"core-concepts/plugins": { totalViews: 2400, uniqueViews: 1500 },
177+
"core-concepts/theming": { totalViews: 1600, uniqueViews: 950 },
178+
"api/rest-endpoints": { totalViews: 5900, uniqueViews: 3400 },
179+
"api/authentication": { totalViews: 4900, uniqueViews: 2900 },
180+
"api/error-handling": { totalViews: 1750, uniqueViews: 1100 },
181+
"advanced/performance": { totalViews: 1400, uniqueViews: 780 },
182+
"advanced/extensions": { totalViews: 740, uniqueViews: 490 },
183+
"legacy/old-api": { totalViews: 1230, uniqueViews: 678 },
184+
"removed/deprecated-feature": { totalViews: 543, uniqueViews: 276 },
185+
},
150186
},
151187
total: {
152-
"getting-started": { totalViews: 15420, uniqueViews: 8934 },
153-
"introduction/what-is-this": { totalViews: 12350, uniqueViews: 7210 },
154-
"introduction/installation": { totalViews: 9876, uniqueViews: 5432 },
155-
"introduction/quick-start": { totalViews: 8543, uniqueViews: 4321 },
156-
"core-concepts/architecture": { totalViews: 5678, uniqueViews: 3456 },
157-
"core-concepts/configuration": { totalViews: 4321, uniqueViews: 2345 },
158-
"core-concepts/plugins": { totalViews: 3210, uniqueViews: 1987 },
159-
"core-concepts/theming": { totalViews: 2100, uniqueViews: 1234 },
160-
"api/rest-endpoints": { totalViews: 7890, uniqueViews: 4567 },
161-
"api/authentication": { totalViews: 6543, uniqueViews: 3890 },
162-
"api/error-handling": { totalViews: 2345, uniqueViews: 1456 },
163-
"advanced/performance": { totalViews: 1890, uniqueViews: 1023 },
164-
"advanced/extensions": { totalViews: 987, uniqueViews: 654 },
165-
"legacy/old-api": { totalViews: 1567, uniqueViews: 834 },
166-
"removed/deprecated-feature": { totalViews: 712, uniqueViews: 389 },
188+
overall: { totalViews: 83420, uniqueViews: 28000 },
189+
chapters: {
190+
"": { totalViews: 15420, uniqueViews: 8934 },
191+
"introduction": { totalViews: 30769, uniqueViews: 12000 },
192+
"core-concepts": { totalViews: 15309, uniqueViews: 6500 },
193+
"api": { totalViews: 16778, uniqueViews: 7200 },
194+
"advanced": { totalViews: 2877, uniqueViews: 1300 },
195+
},
196+
pages: {
197+
"getting-started": { totalViews: 15420, uniqueViews: 8934 },
198+
"introduction/what-is-this": { totalViews: 12350, uniqueViews: 7210 },
199+
"introduction/installation": { totalViews: 9876, uniqueViews: 5432 },
200+
"introduction/quick-start": { totalViews: 8543, uniqueViews: 4321 },
201+
"core-concepts/architecture": { totalViews: 5678, uniqueViews: 3456 },
202+
"core-concepts/configuration": { totalViews: 4321, uniqueViews: 2345 },
203+
"core-concepts/plugins": { totalViews: 3210, uniqueViews: 1987 },
204+
"core-concepts/theming": { totalViews: 2100, uniqueViews: 1234 },
205+
"api/rest-endpoints": { totalViews: 7890, uniqueViews: 4567 },
206+
"api/authentication": { totalViews: 6543, uniqueViews: 3890 },
207+
"api/error-handling": { totalViews: 2345, uniqueViews: 1456 },
208+
"advanced/performance": { totalViews: 1890, uniqueViews: 1023 },
209+
"advanced/extensions": { totalViews: 987, uniqueViews: 654 },
210+
"legacy/old-api": { totalViews: 1567, uniqueViews: 834 },
211+
"removed/deprecated-feature": { totalViews: 712, uniqueViews: 389 },
212+
},
167213
},
168214
};
169215

170216
const [getSelectedPeriod, setSelectedPeriod] = simulateState<TimePeriod>("total");
171217

172218
export function docStatsViewDemo(registry: Registry) {
173-
registry.add("default", () => (
174-
<DocStatsView
175-
guideName={"My guide"}
176-
toc={demoToc}
177-
pageStats={statsByPeriod[getSelectedPeriod()]}
178-
selectedPeriod={getSelectedPeriod()}
179-
availablePeriods={["week", "month", "year", "total"]}
180-
onPeriodChange={setSelectedPeriod}
181-
/>
182-
));
219+
registry.add("default", () => {
220+
const periodStats = statsByPeriod[getSelectedPeriod()];
221+
return (
222+
<DocStatsView
223+
guideName={"My guide"}
224+
toc={demoToc}
225+
overallStats={periodStats.overall}
226+
chapterStats={periodStats.chapters}
227+
pageStats={periodStats.pages}
228+
selectedPeriod={getSelectedPeriod()}
229+
availablePeriods={["week", "month", "year", "total"]}
230+
onPeriodChange={setSelectedPeriod}
231+
/>
232+
);
233+
});
183234
}

0 commit comments

Comments
 (0)