Extracted stats aggregation function to util

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

- this extracts a function to a util so we can unit test it
- this function is about to be optimized but having unit tests allows us
  to make the change with confidence
This commit is contained in:
Daniel Lockyer 2024-06-26 14:20:48 +02:00 committed by Daniel Lockyer
parent 019f417c7d
commit 43bb83f7bb
3 changed files with 219 additions and 34 deletions

View File

@ -3,6 +3,8 @@ import moment from 'moment-timezone';
import {task} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';
import mergeStatsByDate from 'ghost-admin/utils/merge-stats-by-date';
/**
* @typedef MrrStat
* @type {Object}
@ -461,39 +463,7 @@ export default class DashboardStatsService extends Service {
}
}
function mergeDates(list, entry) {
const [current, ...rest] = list;
if (!current) {
return entry ? [entry] : [];
}
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
});
}
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 [entry].concat(mergeDates(list));
}
const subscriptionCountStats = mergeDates(result.stats);
const subscriptionCountStats = mergeStatsByDate(result.stats);
this.paidMembersByCadence = paidMembersByCadence;
this.paidMembersByTier = paidMembersByTier;
@ -524,7 +494,7 @@ export default class DashboardStatsService extends Service {
this.memberCountStats = this.dashboardMocks.memberCountStats;
return;
}
const stats = yield this.membersStats.fetchMemberCount();
this.memberCountStats = stats.stats.map((d) => {
return {

View File

@ -0,0 +1,31 @@
export default function mergeDates(list, entry) {
const [current, ...rest] = list;
if (!current) {
return entry ? [entry] : [];
}
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
});
}
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 [entry].concat(mergeDates(list));
}

View File

@ -0,0 +1,184 @@
import mergeStatsByDate from 'ghost-admin/utils/merge-stats-by-date';
import {describe, it} from 'mocha';
import {expect} from 'chai';
const STATS_DATA = [
{
date: '2024-06-22',
tier: '111111111111111111111111',
cadence: 'month',
positive_delta: 0,
negative_delta: 0,
signups: 0,
cancellations: 0,
count: 456
},
{
date: '2024-06-22',
tier: '111111111111111111111111',
cadence: 'year',
positive_delta: 1,
negative_delta: 1,
signups: 0,
cancellations: 0,
count: 1354
},
{
date: '2024-06-23',
tier: '111111111111111111111111',
cadence: 'month',
positive_delta: 0,
negative_delta: 0,
signups: 0,
cancellations: 0,
count: 456
},
{
date: '2024-06-23',
tier: '111111111111111111111111',
cadence: 'year',
positive_delta: 1,
negative_delta: 1,
signups: 0,
cancellations: 0,
count: 1354
},
{
date: '2024-06-23',
tier: '111111111111111111111113',
cadence: 'year',
positive_delta: 0,
negative_delta: 0,
signups: 0,
cancellations: 0,
count: 400
},
{
date: '2024-06-24',
tier: '111111111111111111111111',
cadence: 'year',
positive_delta: 3,
negative_delta: 2,
signups: 1,
cancellations: 0,
count: 1355
},
{
date: '2024-06-24',
tier: '111111111111111111111113',
cadence: 'year',
positive_delta: 2,
negative_delta: 1,
signups: 2,
cancellations: 1,
count: 401
},
{
date: '2024-06-24',
tier: '111111111111111111111112',
cadence: 'year',
positive_delta: 1,
negative_delta: 0,
signups: 1,
cancellations: 0,
count: 55
},
{
date: '2024-06-25',
tier: '111111111111111111111111',
cadence: 'month',
positive_delta: 0,
negative_delta: 1,
signups: 0,
cancellations: 1,
count: 455
},
{
date: '2024-06-25',
tier: '111111111111111111111111',
cadence: 'year',
positive_delta: 2,
negative_delta: 5,
signups: 1,
cancellations: 4,
count: 1352
},
{
date: '2024-06-25',
tier: '111111111111111111111113',
cadence: 'year',
positive_delta: 1,
negative_delta: 2,
signups: 1,
cancellations: 2,
count: 400
},
{
date: '2024-06-26',
tier: '111111111111111111111111',
cadence: 'year',
positive_delta: 2,
negative_delta: 2,
signups: 0,
cancellations: 0,
count: 1352
},
{
date: '2024-06-26',
tier: '111111111111111111111113',
cadence: 'year',
positive_delta: 0,
negative_delta: 0,
signups: 0,
cancellations: 0,
count: 400
}
];
describe('mergeStatsByDate', function () {
it('merges stats as expected', function () {
const result = mergeStatsByDate(STATS_DATA);
expect(result).to.deep.equal([
{
date: '2024-06-22',
count: 1810,
positiveDelta: 1,
negativeDelta: 1,
signups: 0,
cancellations: 0
},
{
date: '2024-06-23',
count: 2210,
positiveDelta: 1,
negativeDelta: 1,
signups: 0,
cancellations: 0
},
{
date: '2024-06-24',
count: 1811,
positiveDelta: 6,
negativeDelta: 3,
signups: 4,
cancellations: 1
},
{
date: '2024-06-25',
count: 2207,
positiveDelta: 3,
negativeDelta: 8,
signups: 2,
cancellations: 7
},
{
date: '2024-06-26',
count: 1752,
positiveDelta: 2,
negativeDelta: 2,
signups: 0,
cancellations: 0
}
]);
});
});