@@ -3,34 +3,120 @@ import { IStatsPageProps, IBookStat } from "./StatsInterfaces";
33import {
44 useCollectionStats ,
55 extractBookStatFromRawData ,
6+ IBasicBookInfo ,
7+ useSearchBooks ,
68} from "../../connection/LibraryQueryHooks" ;
9+ import { getFilterForCollectionAndChildren } from "../../model/Collections" ;
10+ import { BookOrderingScheme } from "../../model/ContentInterfaces" ;
711
8- export function useGetBookStats (
9- props : IStatsPageProps
12+ // Prefer the first Parse language pointer as a lightweight fallback language tag.
13+ function getLanguageTagFromBook ( book : IBasicBookInfo ) : string | undefined {
14+ return book . languages ?. [ 0 ] ?. isoCode ;
15+ }
16+
17+ // Build a bookInstanceId-to-language map, keeping the first tag we see for duplicate ids.
18+ // This is intentionally only graceful: if bookInstanceId is duplicated, any one matching
19+ // language tag is good enough for repairing missing stats rows.
20+ function buildLanguageMap ( books : IBasicBookInfo [ ] ) : Map < string , string > {
21+ const result = new Map < string , string > ( ) ;
22+
23+ books . forEach ( ( book ) => {
24+ if ( ! book . bookInstanceId ) {
25+ return ;
26+ }
27+
28+ const languageTag = getLanguageTagFromBook ( book ) ;
29+ if ( ! languageTag ) {
30+ return ;
31+ }
32+
33+ if ( ! result . has ( book . bookInstanceId ) ) {
34+ result . set ( book . bookInstanceId , languageTag ) ;
35+ }
36+ } ) ;
37+
38+ return result ;
39+ }
40+
41+ // Fill missing stats languages from Parse book language pointers.
42+ function useRawBookStatsWithParseLanguageFallback (
43+ props : IStatsPageProps ,
44+ urlSuffix : string
1045) : IBookStat [ ] | undefined {
11- const { response } = useCollectionStats ( props , "reading/per-book" ) ;
46+ const { response } = useCollectionStats ( props , urlSuffix ) ;
47+ const collectionFilter = props . collection . filter
48+ ? props . collection . filter
49+ : getFilterForCollectionAndChildren ( props . collection ) ;
50+
51+ // The stats API sometimes returns rows without a language tag
52+ // (specifically when we only have download info, not read info),
53+ // so we query matching books and repair missing stats.language from langPointers.
54+ // See BL-16000.
55+ const { books : parseBooks } = useSearchBooks (
56+ {
57+ include : "langPointers" ,
58+ keys : "bookInstanceId,langPointers" ,
59+ limit : 10000000 ,
60+ } ,
61+ collectionFilter || { } ,
62+ BookOrderingScheme . None ,
63+ undefined ,
64+ ! collectionFilter
65+ ) ;
66+
67+ const parseLanguageByInstanceId = useMemo ( ( ) => {
68+ return buildLanguageMap ( parseBooks ) ;
69+ } , [ parseBooks ] ) ;
1270
1371 return useMemo ( ( ) => {
1472 if ( response && response [ "data" ] && response [ "data" ] [ "stats" ] ) {
1573 return response [ "data" ] [ "stats" ] . map ( ( s : any ) => {
16- return extractBookStatFromRawData ( s ) ;
74+ const stats = extractBookStatFromRawData ( s ) ;
75+ if ( stats . language ) {
76+ return stats ;
77+ } else {
78+ const fallbackLanguage = parseLanguageByInstanceId . get (
79+ s . bookinstanceid
80+ ) ;
81+
82+ return fallbackLanguage
83+ ? { ...stats , language : fallbackLanguage }
84+ : stats ;
85+ }
1786 } ) ;
87+ } else {
88+ return undefined ;
1889 }
19- return undefined ;
20- } , [ response ] ) ;
90+ } , [ response , parseLanguageByInstanceId ] ) ;
2191}
2292
23- export function useGetBookComprehensionEventStats (
24- props : IStatsPageProps
93+ // Apply an optional client-side filter to repaired per-book stats.
94+ function useFilteredBookStats (
95+ props : IStatsPageProps ,
96+ urlSuffix : string ,
97+ predicate ?: ( bookStatInfo : IBookStat ) => boolean
2598) : IBookStat [ ] | undefined {
26- const { response } = useCollectionStats ( props , "reading/per-book" ) ;
99+ const stats = useRawBookStatsWithParseLanguageFallback ( props , urlSuffix ) ;
27100
28101 return useMemo ( ( ) => {
29- if ( response && response [ "data" ] && response [ "data" ] [ "stats" ] ) {
30- return response [ "data" ] [ "stats" ] . map ( ( s : any ) => {
31- return extractBookStatFromRawData ( s ) ;
32- } ) ;
33- }
34- return undefined ;
35- } , [ response ] ) ;
102+ return predicate ? stats ?. filter ( predicate ) : stats ;
103+ } , [ predicate , stats ] ) ;
104+ }
105+
106+ export function useGetBookStats (
107+ props : IStatsPageProps
108+ ) : IBookStat [ ] | undefined {
109+ return useFilteredBookStats ( props , "reading/per-book" ) ;
110+ }
111+
112+ const comprehensionStatsPredicate = ( bookStatInfo : IBookStat ) =>
113+ bookStatInfo . quizzesTaken > 0 && bookStatInfo . questions > 0 ;
114+ export function useGetBookComprehensionEventStats (
115+ props : IStatsPageProps
116+ ) : IBookStat [ ] | undefined {
117+ return useFilteredBookStats (
118+ props ,
119+ "reading/per-book" ,
120+ comprehensionStatsPredicate
121+ ) ;
36122}
0 commit comments