Optimized stats aggregation code for Admin dashboard

fix https://linear.app/tryghost/issue/SLO-168/rangeerror-maximum-call-stack-size-exceeded

- this code takes the API output and reduces it down to collect together
  stats per date
- the current code is recursive, and we've seen errors with the
  recursion hitting a `RangeError: Maximum call stack size exceeded`
  error
- as well as that, we're doing a lot of array concat'ing and cloning,
  which burns memory and CPU time
- instead, we can just use `.reduce`
- the new implementation is much faster than the existing one (1ms vs
  85ms) and uses no recursion, so those errors should go away
- I've also verified that the output is the same between the two
  functions
This commit is contained in:
Daniel Lockyer 2024-06-26 15:01:50 +02:00 committed by Daniel Lockyer
parent 43bb83f7bb
commit f250898a3b

View File

@ -1,31 +1,26 @@
export default function mergeDates(list, entry) {
const [current, ...rest] = list;
export default function mergeStatsByDate(list) {
const reducedStatsByDate = list.reduce((acc, current) => {
const currentDate = current.date;
if (!current) {
return entry ? [entry] : [];
}
if (!acc[currentDate]) {
acc[currentDate] = {
date: currentDate,
count: 0,
positiveDelta: 0,
negativeDelta: 0,
signups: 0,
cancellations: 0
};
}
if (!entry) {
return mergeDates(rest, {
date: current.date,
count: current.count,
positiveDelta: current.positive_delta,
negativeDelta: current.negative_delta,
signups: current.signups,
cancellations: current.cancellations
});
}
acc[currentDate].count += current.count;
acc[currentDate].positiveDelta += current.positive_delta;
acc[currentDate].negativeDelta += current.negative_delta;
acc[currentDate].signups += current.signups;
acc[currentDate].cancellations += current.cancellations;
if (current.date === entry.date) {
return mergeDates(rest, {
date: entry.date,
count: entry.count + current.count,
positiveDelta: entry.positiveDelta + current.positive_delta,
negativeDelta: entry.negativeDelta + current.negative_delta,
signups: entry.signups + current.signups,
cancellations: entry.cancellations + current.cancellations
});
}
return acc;
}, {});
return [entry].concat(mergeDates(list));
return Object.values(reducedStatsByDate);
}