diff --git a/znai-docs/znai/release-notes/1.84.1/fix-2026-01-02-doc-stats-pass-missing-doc-id.md b/znai-docs/znai/release-notes/1.85/fix-2026-01-02-doc-stats-pass-missing-doc-id.md similarity index 100% rename from znai-docs/znai/release-notes/1.84.1/fix-2026-01-02-doc-stats-pass-missing-doc-id.md rename to znai-docs/znai/release-notes/1.85/fix-2026-01-02-doc-stats-pass-missing-doc-id.md diff --git a/znai-docs/znai/release-notes/2026.md b/znai-docs/znai/release-notes/2026.md index 56341e944..7801c2985 100644 --- a/znai-docs/znai/release-notes/2026.md +++ b/znai-docs/znai/release-notes/2026.md @@ -1,3 +1,3 @@ -# 1.84.1 +# 1.85 -:include-markdowns: 1.84.1 +:include-markdowns: 1.85 diff --git a/znai-enterprise-sample-server/znai-enterprise-sample-server.py b/znai-enterprise-sample-server/znai-enterprise-sample-server.py index fe418bd82..84d2bb81a 100644 --- a/znai-enterprise-sample-server/znai-enterprise-sample-server.py +++ b/znai-enterprise-sample-server/znai-enterprise-sample-server.py @@ -264,6 +264,10 @@ def load_tracking_events(): def calculate_page_stats(events, start_time=None): page_views = defaultdict(int) page_unique = defaultdict(set) + chapter_views = defaultdict(int) + chapter_unique = defaultdict(set) + overall_views = 0 + overall_unique = set() for event in events: if event.get('eventType') != 'pageOpen': @@ -282,8 +286,6 @@ def calculate_page_stats(events, start_time=None): if not page_id: continue - page_views[page_id] += 1 - data_str = event.get('data', '{}') try: data = json.loads(data_str) if data_str else {} @@ -291,27 +293,59 @@ def calculate_page_stats(events, start_time=None): data = {} visitor_id = data.get('visitorId', timestamp_str) + + # Page stats + page_views[page_id] += 1 page_unique[page_id].add(visitor_id) - result = {} + # Chapter stats (key is dirName portion of pageId) + chapter_key = page_id.split('/')[0] if '/' in page_id else '' + chapter_views[chapter_key] += 1 + chapter_unique[chapter_key].add(visitor_id) + + # Overall stats + overall_views += 1 + overall_unique.add(visitor_id) + + pages = {} all_pages = set(page_views.keys()) | set(page_unique.keys()) for page_id in all_pages: - result[page_id] = { + pages[page_id] = { 'totalViews': page_views[page_id], 'uniqueViews': len(page_unique[page_id]) } - return result + chapters = {} + all_chapters = set(chapter_views.keys()) | set(chapter_unique.keys()) + for chapter_key in all_chapters: + chapters[chapter_key] = { + 'totalViews': chapter_views[chapter_key], + 'uniqueViews': len(chapter_unique[chapter_key]) + } -def apply_stats_multiplier(page_stats, multiplier): - result = {} - for page_id, stats in page_stats.items(): - total_views = stats['totalViews'] * multiplier - result[page_id] = { - 'totalViews': int(total_views), - 'uniqueViews': int(total_views * 0.2) + return { + 'overall': {'totalViews': overall_views, 'uniqueViews': len(overall_unique)}, + 'chapters': chapters, + 'pages': pages + } + +def apply_stats_multiplier(stats, multiplier): + def multiply_stat(stat): + total_views = stat['totalViews'] * multiplier + total_views_int = int(total_views) + unique_views = int(total_views * 0.2) + if total_views_int > 0 and unique_views == 0: + unique_views = 1 + return { + 'totalViews': total_views_int, + 'uniqueViews': unique_views } - return result + + return { + 'overall': multiply_stat(stats['overall']), + 'chapters': {k: multiply_stat(v) for k, v in stats['chapters'].items()}, + 'pages': {k: multiply_stat(v) for k, v in stats['pages'].items()} + } @app.route('/doc-stats', methods=['GET']) def get_doc_stats(): diff --git a/znai-reactjs/src/screens/doc-stats/DocStatsScreen.tsx b/znai-reactjs/src/screens/doc-stats/DocStatsScreen.tsx index 632ce776a..2a121e901 100644 --- a/znai-reactjs/src/screens/doc-stats/DocStatsScreen.tsx +++ b/znai-reactjs/src/screens/doc-stats/DocStatsScreen.tsx @@ -22,7 +22,13 @@ import { DocStatsView, PageStats, TimePeriod } from "./DocStatsView"; const AVAILABLE_PERIODS: TimePeriod[] = ["week", "month", "year", "total"]; -export type DocStatsResponse = Record>; +export interface PeriodStats { + overall: PageStats; + chapters: Record; + pages: Record; +} + +export type DocStatsResponse = Record; export interface DocStatsScreenProps { toc: TocItem[]; @@ -77,13 +83,15 @@ export function DocStatsScreen({ toc, docMeta }: DocStatsScreenProps) { return null; } - const pageStats = statsByPeriod[selectedPeriod] || {}; + const periodStats = statsByPeriod[selectedPeriod] || { overall: { totalViews: 0, uniqueViews: 0 }, chapters: {}, pages: {} }; return ( > = { +interface PeriodStats { + overall: PageStats; + chapters: Record; + pages: Record; +} + +const statsByPeriod: Record = { week: { - "getting-started": { totalViews: 85, uniqueViews: 42 }, - "introduction/what-is-this": { totalViews: 68, uniqueViews: 35 }, - "introduction/installation": { totalViews: 54, uniqueViews: 28 }, - "introduction/quick-start": { totalViews: 47, uniqueViews: 24 }, - "core-concepts/architecture": { totalViews: 0, uniqueViews: 0 }, - "core-concepts/configuration": { totalViews: 0, uniqueViews: 0 }, - "core-concepts/plugins": { totalViews: 0, uniqueViews: 0 }, - "core-concepts/theming": { totalViews: 0, uniqueViews: 0 }, - "api/rest-endpoints": { totalViews: 43, uniqueViews: 22 }, - "api/authentication": { totalViews: 36, uniqueViews: 19 }, - "api/error-handling": { totalViews: 14, uniqueViews: 7 }, - "advanced/performance": { totalViews: 11, uniqueViews: 5 }, - "advanced/extensions": { totalViews: 6, uniqueViews: 3 }, - "legacy/old-api": { totalViews: 23, uniqueViews: 12 }, - "removed/deprecated-feature": { totalViews: 8, uniqueViews: 4 }, + overall: { totalViews: 395, uniqueViews: 120 }, + chapters: { + "": { totalViews: 85, uniqueViews: 42 }, + "introduction": { totalViews: 169, uniqueViews: 60 }, + "core-concepts": { totalViews: 0, uniqueViews: 0 }, + "api": { totalViews: 93, uniqueViews: 35 }, + "advanced": { totalViews: 17, uniqueViews: 6 }, + }, + pages: { + "getting-started": { totalViews: 85, uniqueViews: 42 }, + "introduction/what-is-this": { totalViews: 68, uniqueViews: 35 }, + "introduction/installation": { totalViews: 54, uniqueViews: 28 }, + "introduction/quick-start": { totalViews: 47, uniqueViews: 24 }, + "core-concepts/architecture": { totalViews: 0, uniqueViews: 0 }, + "core-concepts/configuration": { totalViews: 0, uniqueViews: 0 }, + "core-concepts/plugins": { totalViews: 0, uniqueViews: 0 }, + "core-concepts/theming": { totalViews: 0, uniqueViews: 0 }, + "api/rest-endpoints": { totalViews: 43, uniqueViews: 22 }, + "api/authentication": { totalViews: 36, uniqueViews: 19 }, + "api/error-handling": { totalViews: 14, uniqueViews: 7 }, + "advanced/performance": { totalViews: 11, uniqueViews: 5 }, + "advanced/extensions": { totalViews: 6, uniqueViews: 3 }, + "legacy/old-api": { totalViews: 23, uniqueViews: 12 }, + "removed/deprecated-feature": { totalViews: 8, uniqueViews: 4 }, + }, }, month: { - "getting-started": { totalViews: 1542, uniqueViews: 893 }, - "introduction/what-is-this": { totalViews: 1235, uniqueViews: 721 }, - "introduction/installation": { totalViews: 987, uniqueViews: 543 }, - "introduction/quick-start": { totalViews: 854, uniqueViews: 432 }, - "core-concepts/architecture": { totalViews: 567, uniqueViews: 345 }, - "core-concepts/configuration": { totalViews: 432, uniqueViews: 234 }, - "core-concepts/plugins": { totalViews: 321, uniqueViews: 198 }, - "core-concepts/theming": { totalViews: 210, uniqueViews: 123 }, - "api/rest-endpoints": { totalViews: 789, uniqueViews: 456 }, - "api/authentication": { totalViews: 654, uniqueViews: 389 }, - "api/error-handling": { totalViews: 234, uniqueViews: 145 }, - "advanced/performance": { totalViews: 189, uniqueViews: 102 }, - "advanced/extensions": { totalViews: 98, uniqueViews: 65 }, - "legacy/old-api": { totalViews: 156, uniqueViews: 89 }, - "removed/deprecated-feature": { totalViews: 67, uniqueViews: 34 }, + overall: { totalViews: 8112, uniqueViews: 2800 }, + chapters: { + "": { totalViews: 1542, uniqueViews: 893 }, + "introduction": { totalViews: 3076, uniqueViews: 1100 }, + "core-concepts": { totalViews: 1530, uniqueViews: 650 }, + "api": { totalViews: 1677, uniqueViews: 700 }, + "advanced": { totalViews: 287, uniqueViews: 140 }, + }, + pages: { + "getting-started": { totalViews: 1542, uniqueViews: 893 }, + "introduction/what-is-this": { totalViews: 1235, uniqueViews: 721 }, + "introduction/installation": { totalViews: 987, uniqueViews: 543 }, + "introduction/quick-start": { totalViews: 854, uniqueViews: 432 }, + "core-concepts/architecture": { totalViews: 567, uniqueViews: 345 }, + "core-concepts/configuration": { totalViews: 432, uniqueViews: 234 }, + "core-concepts/plugins": { totalViews: 321, uniqueViews: 198 }, + "core-concepts/theming": { totalViews: 210, uniqueViews: 123 }, + "api/rest-endpoints": { totalViews: 789, uniqueViews: 456 }, + "api/authentication": { totalViews: 654, uniqueViews: 389 }, + "api/error-handling": { totalViews: 234, uniqueViews: 145 }, + "advanced/performance": { totalViews: 189, uniqueViews: 102 }, + "advanced/extensions": { totalViews: 98, uniqueViews: 65 }, + "legacy/old-api": { totalViews: 156, uniqueViews: 89 }, + "removed/deprecated-feature": { totalViews: 67, uniqueViews: 34 }, + }, }, year: { - "getting-started": { totalViews: 12000, uniqueViews: 7000 }, - "introduction/what-is-this": { totalViews: 9500, uniqueViews: 5500 }, - "introduction/installation": { totalViews: 7500, uniqueViews: 4200 }, - "introduction/quick-start": { totalViews: 6500, uniqueViews: 3300 }, - "core-concepts/architecture": { totalViews: 4200, uniqueViews: 2600 }, - "core-concepts/configuration": { totalViews: 3200, uniqueViews: 1800 }, - "core-concepts/plugins": { totalViews: 2400, uniqueViews: 1500 }, - "core-concepts/theming": { totalViews: 1600, uniqueViews: 950 }, - "api/rest-endpoints": { totalViews: 5900, uniqueViews: 3400 }, - "api/authentication": { totalViews: 4900, uniqueViews: 2900 }, - "api/error-handling": { totalViews: 1750, uniqueViews: 1100 }, - "advanced/performance": { totalViews: 1400, uniqueViews: 780 }, - "advanced/extensions": { totalViews: 740, uniqueViews: 490 }, - "legacy/old-api": { totalViews: 1230, uniqueViews: 678 }, - "removed/deprecated-feature": { totalViews: 543, uniqueViews: 276 }, + overall: { totalViews: 63140, uniqueViews: 22000 }, + chapters: { + "": { totalViews: 12000, uniqueViews: 7000 }, + "introduction": { totalViews: 23500, uniqueViews: 9000 }, + "core-concepts": { totalViews: 11400, uniqueViews: 5000 }, + "api": { totalViews: 12550, uniqueViews: 5500 }, + "advanced": { totalViews: 2140, uniqueViews: 1000 }, + }, + pages: { + "getting-started": { totalViews: 12000, uniqueViews: 7000 }, + "introduction/what-is-this": { totalViews: 9500, uniqueViews: 5500 }, + "introduction/installation": { totalViews: 7500, uniqueViews: 4200 }, + "introduction/quick-start": { totalViews: 6500, uniqueViews: 3300 }, + "core-concepts/architecture": { totalViews: 4200, uniqueViews: 2600 }, + "core-concepts/configuration": { totalViews: 3200, uniqueViews: 1800 }, + "core-concepts/plugins": { totalViews: 2400, uniqueViews: 1500 }, + "core-concepts/theming": { totalViews: 1600, uniqueViews: 950 }, + "api/rest-endpoints": { totalViews: 5900, uniqueViews: 3400 }, + "api/authentication": { totalViews: 4900, uniqueViews: 2900 }, + "api/error-handling": { totalViews: 1750, uniqueViews: 1100 }, + "advanced/performance": { totalViews: 1400, uniqueViews: 780 }, + "advanced/extensions": { totalViews: 740, uniqueViews: 490 }, + "legacy/old-api": { totalViews: 1230, uniqueViews: 678 }, + "removed/deprecated-feature": { totalViews: 543, uniqueViews: 276 }, + }, }, total: { - "getting-started": { totalViews: 15420, uniqueViews: 8934 }, - "introduction/what-is-this": { totalViews: 12350, uniqueViews: 7210 }, - "introduction/installation": { totalViews: 9876, uniqueViews: 5432 }, - "introduction/quick-start": { totalViews: 8543, uniqueViews: 4321 }, - "core-concepts/architecture": { totalViews: 5678, uniqueViews: 3456 }, - "core-concepts/configuration": { totalViews: 4321, uniqueViews: 2345 }, - "core-concepts/plugins": { totalViews: 3210, uniqueViews: 1987 }, - "core-concepts/theming": { totalViews: 2100, uniqueViews: 1234 }, - "api/rest-endpoints": { totalViews: 7890, uniqueViews: 4567 }, - "api/authentication": { totalViews: 6543, uniqueViews: 3890 }, - "api/error-handling": { totalViews: 2345, uniqueViews: 1456 }, - "advanced/performance": { totalViews: 1890, uniqueViews: 1023 }, - "advanced/extensions": { totalViews: 987, uniqueViews: 654 }, - "legacy/old-api": { totalViews: 1567, uniqueViews: 834 }, - "removed/deprecated-feature": { totalViews: 712, uniqueViews: 389 }, + overall: { totalViews: 83420, uniqueViews: 28000 }, + chapters: { + "": { totalViews: 15420, uniqueViews: 8934 }, + "introduction": { totalViews: 30769, uniqueViews: 12000 }, + "core-concepts": { totalViews: 15309, uniqueViews: 6500 }, + "api": { totalViews: 16778, uniqueViews: 7200 }, + "advanced": { totalViews: 2877, uniqueViews: 1300 }, + }, + pages: { + "getting-started": { totalViews: 15420, uniqueViews: 8934 }, + "introduction/what-is-this": { totalViews: 12350, uniqueViews: 7210 }, + "introduction/installation": { totalViews: 9876, uniqueViews: 5432 }, + "introduction/quick-start": { totalViews: 8543, uniqueViews: 4321 }, + "core-concepts/architecture": { totalViews: 5678, uniqueViews: 3456 }, + "core-concepts/configuration": { totalViews: 4321, uniqueViews: 2345 }, + "core-concepts/plugins": { totalViews: 3210, uniqueViews: 1987 }, + "core-concepts/theming": { totalViews: 2100, uniqueViews: 1234 }, + "api/rest-endpoints": { totalViews: 7890, uniqueViews: 4567 }, + "api/authentication": { totalViews: 6543, uniqueViews: 3890 }, + "api/error-handling": { totalViews: 2345, uniqueViews: 1456 }, + "advanced/performance": { totalViews: 1890, uniqueViews: 1023 }, + "advanced/extensions": { totalViews: 987, uniqueViews: 654 }, + "legacy/old-api": { totalViews: 1567, uniqueViews: 834 }, + "removed/deprecated-feature": { totalViews: 712, uniqueViews: 389 }, + }, }, }; const [getSelectedPeriod, setSelectedPeriod] = simulateState("total"); export function docStatsViewDemo(registry: Registry) { - registry.add("default", () => ( - - )); + registry.add("default", () => { + const periodStats = statsByPeriod[getSelectedPeriod()]; + return ( + + ); + }); } diff --git a/znai-reactjs/src/screens/doc-stats/DocStatsView.tsx b/znai-reactjs/src/screens/doc-stats/DocStatsView.tsx index e6b4defe4..2f17bc929 100644 --- a/znai-reactjs/src/screens/doc-stats/DocStatsView.tsx +++ b/znai-reactjs/src/screens/doc-stats/DocStatsView.tsx @@ -31,6 +31,8 @@ export interface PageStats { export interface DocStatsViewProps { guideName: string; toc: TocItem[]; + overallStats: PageStats; + chapterStats: Record; pageStats: Record; selectedPeriod: TimePeriod; availablePeriods: TimePeriod[]; @@ -46,7 +48,7 @@ const TIME_PERIODS: { key: TimePeriod; label: string }[] = [ { key: "week", label: "Week" }, { key: "month", label: "Month" }, { key: "year", label: "Year" }, - { key: "total", label: "Total" }, + { key: "total", label: "All Time" }, ]; function buildPageId(dirName: string, fileName: string): string { @@ -77,16 +79,6 @@ function formatNumber(num: number): string { const ZERO_STATS: PageStats = { totalViews: 0, uniqueViews: 0 }; -function sumStats(statsArray: PageStats[]): PageStats { - return statsArray.reduce( - (acc, stats) => ({ - totalViews: acc.totalViews + stats.totalViews, - uniqueViews: acc.uniqueViews + stats.uniqueViews, - }), - ZERO_STATS - ); -} - function StatsCounters({ stats }: { stats: PageStats }) { return (
@@ -136,26 +128,25 @@ function PageItem({ item, stats }: PageItemProps) { interface ChapterSectionProps { chapter: TocItem; + chapterStats: Record; pageStats: Record; } -function ChapterSection({ chapter, pageStats }: ChapterSectionProps) { +function ChapterSection({ chapter, chapterStats, pageStats }: ChapterSectionProps) { const items = (chapter.items || []).filter((item) => !isTocItemIndex(item)); if (items.length === 0) { return null; } - const itemStats = items - .map((item) => pageStats[buildPageId(item.dirName, item.fileName)]) - .filter((stats): stats is PageStats => !!stats); - const chapterStats = sumStats(itemStats); + const chapterKey = items[0]?.dirName ?? ""; + const stats = chapterStats[chapterKey] ?? ZERO_STATS; return (
{chapter.chapterTitle} - +
{items.map((item) => { @@ -176,13 +167,10 @@ function OrphanedPagesSection({ orphanedPages }: OrphanedPagesSectionProps) { return null; } - const sectionStats = sumStats(orphanedPages.map(([, stats]) => stats)); - return (
Orphaned Pages -
{orphanedPages.map(([pageId, stats]) => ( @@ -221,6 +209,8 @@ function TimePeriodSwitcher({ selectedPeriod, availablePeriods, onPeriodChange } export function DocStatsView({ guideName, toc, + overallStats, + chapterStats, pageStats, selectedPeriod, availablePeriods, @@ -232,8 +222,6 @@ export function DocStatsView({ contentRef.current?.focus(); }, []); - const totalStats = sumStats(Object.values(pageStats)); - const tocPageIds = collectTocPageIds(toc); const orphanedPages = Object.entries(pageStats).filter(([pageId]) => !tocPageIds.has(pageId)); @@ -251,12 +239,17 @@ export function DocStatsView({
- - + +
{toc.map((chapter, idx) => ( - + ))}