Skip to content

Commit 6887fbf

Browse files
authored
Merge pull request #598 from BloomBooks/BL15372_BookStatsOOM
Add paging to book stats report for avoid memory issues (BL-15372)
2 parents ada5a11 + dce5c9f commit 6887fbf

File tree

4 files changed

+75
-35
lines changed

4 files changed

+75
-35
lines changed

src/components/statistics/BookStatsReport.tsx

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@ import {
66
Grid,
77
TableHeaderRow,
88
Table,
9+
PagingPanel,
910
} from "@devexpress/dx-react-grid-material-ui";
10-
import { SortingState, IntegratedSorting } from "@devexpress/dx-react-grid";
11+
import {
12+
SortingState,
13+
IntegratedSorting,
14+
PagingState,
15+
IntegratedPaging,
16+
} from "@devexpress/dx-react-grid";
1117
import { IGridColumn } from "../Grid/GridColumns";
12-
import React, { useState, useContext } from "react";
18+
import React, { useState, useContext, useMemo } from "react";
1319
import { IStatsPageProps } from "./StatsInterfaces";
1420
import { useGetBookStats } from "./useGetBookStats";
1521
import { useProvideDataForExport } from "../../export/exportData";
@@ -22,21 +28,27 @@ export const BookStatsReport: React.FunctionComponent<IStatsPageProps> = (
2228
props
2329
) => {
2430
const l10n = useIntl();
25-
const stats = useGetBookStats(props);
26-
useProvideDataForExport(stats, props);
31+
const rawStats = useGetBookStats(props);
2732
const { languagesByBookCount: languages } = useContext(CachedTablesContext);
2833

29-
if (stats) {
30-
for (const stat of stats) {
34+
const stats = useMemo(() => {
35+
if (!rawStats) return undefined;
36+
37+
// Create a new array to avoid mutating the original
38+
return rawStats.map((stat) => {
3139
const languageDisplayName = getDisplayNamesFromLanguageCode(
3240
stat.language,
3341
languages
3442
)?.combined;
43+
3544
if (languageDisplayName) {
36-
stat.languageName = languageDisplayName;
45+
return { ...stat, languageName: languageDisplayName };
3746
}
38-
}
39-
}
47+
return stat;
48+
});
49+
}, [rawStats, languages]);
50+
51+
useProvideDataForExport(stats, props);
4052

4153
const columns: IGridColumn[] = [
4254
{ name: "title", title: "Book Title", l10nId: "bookTitle" },
@@ -132,6 +144,11 @@ export const BookStatsReport: React.FunctionComponent<IStatsPageProps> = (
132144
{ columnName: "startedCount" },
133145
]);
134146

147+
// Configure paging with 1000 records per page
148+
const [currentPage, setCurrentPage] = useState(0);
149+
const [pageSize, setPageSize] = useState(1000);
150+
const pageSizes = [100, 500, 1000, 2000];
151+
135152
return (
136153
<StatsGridWrapper stats={stats}>
137154
<Grid rows={stats!} columns={columns}>
@@ -141,6 +158,13 @@ export const BookStatsReport: React.FunctionComponent<IStatsPageProps> = (
141158
<IntegratedSorting
142159
columnExtensions={integratedSortingColumnExtensions}
143160
/>
161+
<PagingState
162+
currentPage={currentPage}
163+
onCurrentPageChange={setCurrentPage}
164+
pageSize={pageSize}
165+
onPageSizeChange={setPageSize}
166+
/>
167+
<IntegratedPaging />
144168
<Table
145169
columnExtensions={tableColumnExtensions}
146170
//cellComponent={CustomTableCell}
@@ -149,6 +173,7 @@ export const BookStatsReport: React.FunctionComponent<IStatsPageProps> = (
149173
cellComponent={CustomTableHeaderCell}
150174
showSortingControls
151175
/>
176+
<PagingPanel pageSizes={pageSizes} />
152177
</Grid>
153178
</StatsGridWrapper>
154179
);
Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useMemo } from "react";
12
import { IStatsPageProps, IBookStat } from "./StatsInterfaces";
23
import {
34
useCollectionStats,
@@ -9,21 +10,27 @@ export function useGetBookStats(
910
): IBookStat[] | undefined {
1011
const { response } = useCollectionStats(props, "reading/per-book");
1112

12-
if (response && response["data"] && response["data"]["stats"])
13-
return response["data"]["stats"].map((s: any) => {
14-
return extractBookStatFromRawData(s);
15-
});
16-
return undefined;
13+
return useMemo(() => {
14+
if (response && response["data"] && response["data"]["stats"]) {
15+
return response["data"]["stats"].map((s: any) => {
16+
return extractBookStatFromRawData(s);
17+
});
18+
}
19+
return undefined;
20+
}, [response]);
1721
}
1822

1923
export function useGetBookComprehensionEventStats(
2024
props: IStatsPageProps
2125
): IBookStat[] | undefined {
2226
const { response } = useCollectionStats(props, "reading/per-book");
2327

24-
if (response && response["data"] && response["data"]["stats"])
25-
return response["data"]["stats"].map((s: any) => {
26-
return extractBookStatFromRawData(s);
27-
});
28-
return undefined;
28+
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]);
2936
}
Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useMemo } from "react";
12
import {
23
IStatsPageProps,
34
IDailyBookStat as IDailyBookEventStat,
@@ -9,17 +10,20 @@ export function useGetDailyBookEventStats(
910
): IDailyBookEventStat[] | undefined {
1011
const { response } = useCollectionStats(props, "reading/per-day");
1112

12-
if (response && response["data"] && response["data"]["stats"])
13-
return response["data"]["stats"].map((s: any) => {
14-
return {
15-
dateEventLocal: s.datelocal,
16-
branding: s.bookbranding,
17-
country: s.country,
18-
// parseInt is important.
19-
// Without it, js will treat the values like a strings even though typescript knows they are numbers.
20-
// Then the + operator will concatenate instead of add.
21-
bloomReaderSessions: parseInt(s.bloomreadersessions, 10),
22-
};
23-
});
24-
return undefined;
13+
return useMemo(() => {
14+
if (response && response["data"] && response["data"]["stats"]) {
15+
return response["data"]["stats"].map((s: any) => {
16+
return {
17+
dateEventLocal: s.datelocal,
18+
branding: s.bookbranding,
19+
country: s.country,
20+
// parseInt is important.
21+
// Without it, js will treat the values like a strings even though typescript knows they are numbers.
22+
// Then the + operator will concatenate instead of add.
23+
bloomReaderSessions: parseInt(s.bloomreadersessions, 10),
24+
};
25+
});
26+
}
27+
return undefined;
28+
}, [response]);
2529
}

src/components/statistics/useGetOverviewStats.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useMemo } from "react";
12
import { IStatsPageProps, IOverviewStats } from "./StatsInterfaces";
23
import { useCollectionStats } from "../../connection/LibraryQueryHooks";
34

@@ -6,7 +7,11 @@ export function useGetOverviewStats(
67
): IOverviewStats | undefined {
78
const { response } = useCollectionStats(props, "reading/overview");
89

9-
if (response && response["data"] && response["data"]["stats"]) {
10+
return useMemo(() => {
11+
if (!(response && response["data"] && response["data"]["stats"])) {
12+
return undefined;
13+
}
14+
1015
const stats = response["data"]["stats"][0];
1116

1217
if (!stats) {
@@ -59,6 +64,5 @@ export function useGetOverviewStats(
5964
countries: parseInt(stats.countrycount, 10),
6065
};
6166
return result;
62-
}
63-
return undefined;
67+
}, [response]);
6468
}

0 commit comments

Comments
 (0)