mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Added /stats/subscriptions API (#14547)
refs https://github.com/TryGhost/Team/issues/1505 refs https://github.com/TryGhost/Team/issues/1466 Exposes an API for historical counts broken down by tier and cadence. Counts backwards from the current stats like MRR to minimize inaccruate data due to missing/superfluous events.
This commit is contained in:
parent
756f86dbdc
commit
d94859f2e5
8 changed files with 151 additions and 8 deletions
|
@ -19,5 +19,14 @@ module.exports = {
|
|||
async query() {
|
||||
return await statsService.getMRRHistory();
|
||||
}
|
||||
},
|
||||
subscriptions: {
|
||||
permissions: {
|
||||
docName: 'members',
|
||||
method: 'browse'
|
||||
},
|
||||
async query() {
|
||||
return await statsService.getSubscriptionCountHistory();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -140,6 +140,7 @@ module.exports = function apiRoutes() {
|
|||
// ## Stats
|
||||
router.get('/stats/member_count', mw.authAdminApi, http(api.stats.memberCountHistory));
|
||||
router.get('/stats/mrr', mw.authAdminApi, http(api.stats.mrr));
|
||||
router.get('/stats/subscriptions', mw.authAdminApi, http(api.stats.subscriptions));
|
||||
|
||||
// ## Labels
|
||||
router.get('/labels', mw.authAdminApi, http(api.labels.browse));
|
||||
|
|
|
@ -105,7 +105,7 @@
|
|||
"@tryghost/session-service": "0.1.39",
|
||||
"@tryghost/settings-path-manager": "0.1.5",
|
||||
"@tryghost/social-urls": "0.1.29",
|
||||
"@tryghost/stats-service": "0.1.0",
|
||||
"@tryghost/stats-service": "0.2.1",
|
||||
"@tryghost/string": "0.1.23",
|
||||
"@tryghost/tpl": "0.1.16",
|
||||
"@tryghost/update-check-service": "0.3.2",
|
||||
|
|
|
@ -11,6 +11,11 @@ Object {
|
|||
],
|
||||
},
|
||||
"stats": Array [
|
||||
Object {
|
||||
"currency": "usd",
|
||||
"date": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/,
|
||||
"mrr": 0,
|
||||
},
|
||||
Object {
|
||||
"currency": "usd",
|
||||
"date": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/,
|
||||
|
@ -24,7 +29,7 @@ exports[`Stats API Can fetch MRR history 2: [headers] 1`] = `
|
|||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "111",
|
||||
"content-length": "158",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
|
@ -65,3 +70,54 @@ Object {
|
|||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Stats API Can fetch subscriptions history 1: [body] 1`] = `
|
||||
Object {
|
||||
"meta": Object {
|
||||
"cadences": Array [
|
||||
"year",
|
||||
"month",
|
||||
],
|
||||
"tiers": Array [
|
||||
StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
],
|
||||
"totals": Array [
|
||||
Object {
|
||||
"cadence": "month",
|
||||
"count": 1,
|
||||
"tier": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
},
|
||||
],
|
||||
},
|
||||
"stats": Array [
|
||||
Object {
|
||||
"cadence": "month",
|
||||
"count": 1,
|
||||
"date": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/,
|
||||
"negative_delta": 0,
|
||||
"positive_delta": 1,
|
||||
"tier": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
},
|
||||
Object {
|
||||
"cadence": "year",
|
||||
"count": 0,
|
||||
"date": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/,
|
||||
"negative_delta": 0,
|
||||
"positive_delta": 0,
|
||||
"tier": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Stats API Can fetch subscriptions history 2: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "403",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const {agentProvider, fixtureManager, matchers} = require('../../utils/e2e-framework');
|
||||
const {anyEtag, anyISODate} = matchers;
|
||||
const {anyEtag, anyISODate, anyObjectId} = matchers;
|
||||
|
||||
let agent;
|
||||
|
||||
|
@ -31,10 +31,36 @@ describe('Stats API', function () {
|
|||
.matchBodySnapshot({
|
||||
stats: [{
|
||||
date: anyISODate
|
||||
}, {
|
||||
date: anyISODate
|
||||
}]
|
||||
})
|
||||
.matchHeaderSnapshot({
|
||||
etag: anyEtag
|
||||
});
|
||||
});
|
||||
|
||||
it('Can fetch subscriptions history', async function () {
|
||||
await agent
|
||||
.get(`/stats/subscriptions`)
|
||||
.expectStatus(200)
|
||||
.matchBodySnapshot({
|
||||
stats: [{
|
||||
date: anyISODate,
|
||||
tier: anyObjectId
|
||||
}, {
|
||||
date: anyISODate,
|
||||
tier: anyObjectId
|
||||
}],
|
||||
meta: {
|
||||
tiers: [anyObjectId],
|
||||
totals: [{
|
||||
tier: anyObjectId
|
||||
}]
|
||||
}
|
||||
})
|
||||
.matchHeaderSnapshot({
|
||||
etag: anyEtag
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -562,6 +562,10 @@ const fixtures = {
|
|||
})}, {id: member.id});
|
||||
}
|
||||
}
|
||||
}).then(async function () {
|
||||
for (const event of DataGenerator.forKnex.members_paid_subscription_events) {
|
||||
await models.MemberPaidSubscriptionEvent.add(event);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -492,6 +492,42 @@ DataGenerator.Content = {
|
|||
}
|
||||
],
|
||||
|
||||
members_paid_subscription_events: [
|
||||
{
|
||||
id: ObjectId().toHexString(),
|
||||
type: 'created',
|
||||
mrr_delta: 1000,
|
||||
currency: 'usd',
|
||||
source: 'stripe',
|
||||
created_at: null,
|
||||
subscription_id: null,
|
||||
member_id: null,
|
||||
from_plan: null,
|
||||
to_plan: '173e16a1fffa7d232b398e4a9b08d266a456ae8f3d23e5f11cc608ced6730bb8'
|
||||
}, {
|
||||
id: ObjectId().toHexString(),
|
||||
type: 'created',
|
||||
mrr_delta: 0,
|
||||
currency: 'usd',
|
||||
source: 'stripe',
|
||||
created_at: null,
|
||||
subscription_id: null,
|
||||
member_id: null,
|
||||
from_plan: null,
|
||||
to_plan: '173e16a1fffa7d232b398e4a9b08d266a456ae8f3d23e5f11cc608ced6730bb9'
|
||||
}, {
|
||||
id: ObjectId().toHexString(),
|
||||
type: 'created',
|
||||
mrr_delta: 0,
|
||||
currency: 'usd',
|
||||
source: 'stripe',
|
||||
created_at: null,
|
||||
subscription_id: null,
|
||||
member_id: null,
|
||||
from_plan: null,
|
||||
to_plan: '173e16a1fffa7d232b398e4a9b08d266a456ae8f3d23e5f11cc608ced6730ba0'
|
||||
}
|
||||
],
|
||||
members_stripe_customers_subscriptions: [
|
||||
{
|
||||
id: ObjectId().toHexString(),
|
||||
|
@ -780,6 +816,9 @@ DataGenerator.Content.members_stripe_customers[1].member_id = DataGenerator.Cont
|
|||
DataGenerator.Content.members_stripe_customers[2].member_id = DataGenerator.Content.members[4].id;
|
||||
DataGenerator.Content.members_stripe_customers[3].member_id = DataGenerator.Content.members[6].id;
|
||||
DataGenerator.Content.members_stripe_customers[4].member_id = DataGenerator.Content.members[7].id;
|
||||
DataGenerator.Content.members_paid_subscription_events[0].member_id = DataGenerator.Content.members[2].id;
|
||||
DataGenerator.Content.members_paid_subscription_events[1].member_id = DataGenerator.Content.members[3].id;
|
||||
DataGenerator.Content.members_paid_subscription_events[2].member_id = DataGenerator.Content.members[4].id;
|
||||
|
||||
DataGenerator.forKnex = (function () {
|
||||
function createBasic(overrides) {
|
||||
|
@ -1473,6 +1512,12 @@ DataGenerator.forKnex = (function () {
|
|||
createBasic(DataGenerator.Content.members_stripe_customers_subscriptions[2])
|
||||
];
|
||||
|
||||
const members_paid_subscription_events = [
|
||||
createBasic(DataGenerator.Content.members_paid_subscription_events[0]),
|
||||
createBasic(DataGenerator.Content.members_paid_subscription_events[1]),
|
||||
createBasic(DataGenerator.Content.members_paid_subscription_events[2])
|
||||
];
|
||||
|
||||
const snippets = [
|
||||
createBasic(DataGenerator.Content.snippets[0])
|
||||
];
|
||||
|
@ -1537,7 +1582,9 @@ DataGenerator.forKnex = (function () {
|
|||
stripe_prices,
|
||||
stripe_products,
|
||||
snippets,
|
||||
custom_theme_settings
|
||||
custom_theme_settings,
|
||||
|
||||
members_paid_subscription_events
|
||||
};
|
||||
}());
|
||||
|
||||
|
|
|
@ -2447,10 +2447,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@tryghost/social-urls/-/social-urls-0.1.29.tgz#09177858959e9521244d0192bf219237f0fb6094"
|
||||
integrity sha512-PUgSQj/Y8x4Sbp0/Lq/Pq6ZN8ICSoAIHpVouJwblW3LvY/C0eeDrDLQMrUmR2SSBx59mS+Blavwy5mFlt23lwg==
|
||||
|
||||
"@tryghost/stats-service@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/stats-service/-/stats-service-0.1.0.tgz#5638ac07b6de6de40ea4f7a9963e832530eef2e2"
|
||||
integrity sha512-YJRdomtJ8y3f0UDvYjSnK4/RMu5xvZoMvtFAZfxiDC1d0yKA4YostxFQHAKh32t2y0FNO6IJzO7yJ5HnUXjueg==
|
||||
"@tryghost/stats-service@0.2.1":
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/stats-service/-/stats-service-0.2.1.tgz#d9b0488d705b9a697dc9ebff9ec8e64b33902649"
|
||||
integrity sha512-2w2C8f9BR7QjcyvgPLQmzNW+cDHT6eceCMa8Ihp3PoQtcikE8TKAOx1cwlEDxs+0Blf5BQ8HILA2bJYsfsylAg==
|
||||
dependencies:
|
||||
moment "^2.29.3"
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue