mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 06:35:49 +03:00
Cleaned up old member analytics services
refs https://github.com/TryGhost/Team/issues/2216 This change removes old analytics code which was added under `membersActivity` flag as an experimental alpha feature to test the first versions of member analytics, and is no longer active or in use. This change removes the remaining services and its usage that were created to manage this version of analytics but is no longer active or maintained. - removes `members-analytics-ingress` service that was used to ingest events from Portal in this experimental feature - removes `member-analytics-service` service that managed the events from this experimental feature - removes usages of the 2 services and their dependency in `members-api` - removes `member-analytic-event` model as the corresponding table for it does not exist anymore and was dropped in 5.0
This commit is contained in:
parent
d4c3f86ce0
commit
d6af8fbb8f
@ -1,9 +0,0 @@
|
||||
const ghostBookshelf = require('./base');
|
||||
|
||||
const MemberAnalyticEvent = ghostBookshelf.Model.extend({
|
||||
tableName: 'temp_member_analytic_events'
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
MemberAnalyticEvent: ghostBookshelf.model('MemberAnalyticEvent', MemberAnalyticEvent)
|
||||
};
|
@ -185,7 +185,6 @@ function createApiInstance(config) {
|
||||
MemberPaymentEvent: models.MemberPaymentEvent,
|
||||
MemberStatusEvent: models.MemberStatusEvent,
|
||||
MemberProductEvent: models.MemberProductEvent,
|
||||
MemberAnalyticEvent: models.MemberAnalyticEvent,
|
||||
MemberCreatedEvent: models.MemberCreatedEvent,
|
||||
SubscriptionCreatedEvent: models.SubscriptionCreatedEvent,
|
||||
MemberLinkClickEvent: models.MemberClickEvent,
|
||||
|
@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: ['ghost'],
|
||||
extends: [
|
||||
'plugin:ghost/node'
|
||||
]
|
||||
};
|
@ -1,19 +0,0 @@
|
||||
const AnalyticEventRepository = require('./lib/AnalyticEventRepository');
|
||||
const EventHandler = require('./lib/EventHandler');
|
||||
|
||||
class MemberAnalyticsService {
|
||||
/**
|
||||
* @param {AnalyticEventRepository} analyticEventRepository
|
||||
*/
|
||||
constructor(analyticEventRepository) {
|
||||
this.eventHandler = new EventHandler(analyticEventRepository);
|
||||
}
|
||||
|
||||
static create(AnalyticEventModel) {
|
||||
const analyticEventRepository = new AnalyticEventRepository(AnalyticEventModel);
|
||||
|
||||
return new MemberAnalyticsService(analyticEventRepository);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MemberAnalyticsService;
|
@ -1,137 +0,0 @@
|
||||
const errors = require('@tryghost/errors');
|
||||
const tpl = require('@tryghost/tpl');
|
||||
const ObjectID = require('bson-objectid').default;
|
||||
|
||||
const messages = {
|
||||
missingMemberId: 'A memberId must be provided for analytic events',
|
||||
invalidEventName: 'Analytic events must be provided a "name"',
|
||||
missingSourceUrl: 'A sourceUrl must be provided for analytic events',
|
||||
invalidMemberStatus: 'A memberStatus of either "free", "paid" or "comped" must be provided'
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {object} AnalyticEventProps
|
||||
* @prop {ObjectID} id
|
||||
* @prop {string} name
|
||||
* @prop {Date} timestamp
|
||||
* @prop {ObjectID} memberId
|
||||
* @prop {'free'|'comped'|'paid'} memberStatus
|
||||
* @prop {ObjectID | null} entryId
|
||||
* @prop {string} sourceUrl
|
||||
* @prop {string | null} metadata
|
||||
*/
|
||||
class AnalyticEvent {
|
||||
get id() {
|
||||
return this.props.id.toHexString();
|
||||
}
|
||||
get name() {
|
||||
return this.props.name;
|
||||
}
|
||||
get timestamp() {
|
||||
return this.props.timestamp;
|
||||
}
|
||||
get memberId() {
|
||||
return this.props.memberId.toHexString();
|
||||
}
|
||||
get memberStatus() {
|
||||
return this.props.memberStatus;
|
||||
}
|
||||
get entryId() {
|
||||
return this.props.entryId.toHexString();
|
||||
}
|
||||
get sourceUrl() {
|
||||
return this.props.sourceUrl;
|
||||
}
|
||||
get metadata() {
|
||||
return this.props.metadata;
|
||||
}
|
||||
get isNew() {
|
||||
return !!this.options.isNew;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AnalyticEventProps} props
|
||||
* @param {object} options
|
||||
* @param {boolean} options.isNew
|
||||
*/
|
||||
constructor(props, options) {
|
||||
this.props = props;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} data
|
||||
* @param {ObjectID | string} [data.id]
|
||||
* @param {ObjectID | string} [data.entryId]
|
||||
* @param {string} [data.metadata]
|
||||
* @param {ObjectID | string} data.memberId
|
||||
* @param {string} data.sourceUrl
|
||||
* @param {string} data.name
|
||||
* @param {string} data.memberStatus
|
||||
* @param {Date} [data.timestamp]
|
||||
*/
|
||||
static create(data) {
|
||||
let isNew = false;
|
||||
let id;
|
||||
if (data.id instanceof ObjectID) {
|
||||
id = data.id;
|
||||
} else if (typeof data.id === 'string') {
|
||||
id = new ObjectID(data.id);
|
||||
} else {
|
||||
id = new ObjectID();
|
||||
isNew = true;
|
||||
}
|
||||
|
||||
let memberId;
|
||||
if (data.memberId instanceof ObjectID) {
|
||||
memberId = data.memberId;
|
||||
} else if (typeof data.memberId === 'string') {
|
||||
memberId = new ObjectID(data.memberId);
|
||||
} else {
|
||||
throw new errors.IncorrectUsageError({mesage: tpl(messages.missingMemberId)});
|
||||
}
|
||||
|
||||
let entryId;
|
||||
if (data.entryId instanceof ObjectID) {
|
||||
entryId = data.entryId;
|
||||
} else if (typeof data.entryId === 'string') {
|
||||
entryId = new ObjectID(data.entryId);
|
||||
} else {
|
||||
entryId = null;
|
||||
}
|
||||
|
||||
const name = data.name;
|
||||
if (typeof name !== 'string') {
|
||||
throw new errors.IncorrectUsageError({message: tpl(messages.invalidEventName)});
|
||||
}
|
||||
|
||||
const timestamp = data.timestamp || new Date();
|
||||
|
||||
const sourceUrl = data.sourceUrl;
|
||||
if (!sourceUrl) {
|
||||
throw new errors.IncorrectUsageError({message: tpl(messages.missingSourceUrl)});
|
||||
}
|
||||
|
||||
const memberStatus = data.memberStatus;
|
||||
if (memberStatus !== 'free' && memberStatus !== 'paid' && memberStatus !== 'comped') {
|
||||
throw new errors.IncorrectUsageError({message: tpl(messages.invalidMemberStatus)});
|
||||
}
|
||||
|
||||
const metadata = data.metadata || null;
|
||||
|
||||
return new AnalyticEvent({
|
||||
id,
|
||||
name,
|
||||
timestamp,
|
||||
memberId,
|
||||
memberStatus,
|
||||
entryId,
|
||||
sourceUrl,
|
||||
metadata
|
||||
}, {
|
||||
isNew
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnalyticEvent;
|
@ -1,47 +0,0 @@
|
||||
/**
|
||||
* @typedef {object} DBProps
|
||||
* @param {string} id
|
||||
* @param {string} event_name
|
||||
* @param {Date} created_at
|
||||
* @param {string} member_id
|
||||
* @param {string} member_status
|
||||
* @param {string} entry_id
|
||||
* @param {string} source_url
|
||||
* @param {string} metadata
|
||||
*/
|
||||
|
||||
class AnalyticEventRepository {
|
||||
/**
|
||||
* @param {any} AnalyticEventModel
|
||||
*/
|
||||
constructor(AnalyticEventModel) {
|
||||
/** @private */
|
||||
this.AnalyticEventModel = AnalyticEventModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('./AnalyticEvent')} event
|
||||
*/
|
||||
async save(event) {
|
||||
const data = {
|
||||
id: event.id,
|
||||
event_name: event.name,
|
||||
created_at: event.timestamp,
|
||||
member_id: event.memberId,
|
||||
member_status: event.memberStatus,
|
||||
entry_id: event.entryId,
|
||||
source_url: event.sourceUrl,
|
||||
metadata: event.metadata
|
||||
};
|
||||
|
||||
const model = this.AnalyticEventModel.forge(data);
|
||||
|
||||
if (event.isNew) {
|
||||
await model.save(null, {method: 'insert'});
|
||||
} else {
|
||||
await model.save(null, {method: 'update'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnalyticEventRepository;
|
@ -1,94 +0,0 @@
|
||||
const DomainEvents = require('@tryghost/domain-events');
|
||||
const {
|
||||
MemberEntryViewEvent,
|
||||
MemberUnsubscribeEvent,
|
||||
MemberSignupEvent,
|
||||
MemberPaidConverstionEvent,
|
||||
MemberPaidCancellationEvent
|
||||
} = require('@tryghost/member-events');
|
||||
|
||||
const AnalyticEvent = require('./AnalyticEvent');
|
||||
|
||||
class EventHandler {
|
||||
/**
|
||||
* @param {import('./AnalyticEventRepository')} repository
|
||||
*/
|
||||
constructor(repository) {
|
||||
/** @private */
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for member events and handles creating analytic events and storing them.
|
||||
*/
|
||||
setupSubscribers() {
|
||||
DomainEvents.subscribe(MemberEntryViewEvent, async (ev) => {
|
||||
const event = AnalyticEvent.create({
|
||||
name: 'entry_view',
|
||||
memberId: ev.data.memberId,
|
||||
memberStatus: ev.data.memberStatus,
|
||||
entryId: ev.data.entryId,
|
||||
sourceUrl: ev.data.entryUrl,
|
||||
timestamp: ev.timestamp
|
||||
});
|
||||
|
||||
await this.repository.save(event);
|
||||
});
|
||||
|
||||
DomainEvents.subscribe(MemberUnsubscribeEvent, async (ev) => {
|
||||
const event = AnalyticEvent.create({
|
||||
name: 'unsubscribe',
|
||||
memberId: ev.data.memberId,
|
||||
memberStatus: ev.data.memberStatus,
|
||||
entryId: ev.data.entryId,
|
||||
sourceUrl: ev.data.sourceUrl,
|
||||
timestamp: ev.timestamp
|
||||
});
|
||||
|
||||
await this.repository.save(event);
|
||||
});
|
||||
|
||||
DomainEvents.subscribe(MemberSignupEvent, async (ev) => {
|
||||
const event = AnalyticEvent.create({
|
||||
name: 'signup',
|
||||
memberId: ev.data.memberId,
|
||||
memberStatus: 'free',
|
||||
entryId: ev.data.entryId,
|
||||
sourceUrl: ev.data.sourceUrl,
|
||||
timestamp: ev.timestamp
|
||||
});
|
||||
|
||||
await this.repository.save(event);
|
||||
});
|
||||
|
||||
DomainEvents.subscribe(MemberPaidCancellationEvent, async (ev) => {
|
||||
const event = AnalyticEvent.create({
|
||||
name: 'paid_cancellation',
|
||||
memberId: ev.data.memberId,
|
||||
memberStatus: ev.data.memberStatus,
|
||||
entryId: ev.data.entryId,
|
||||
sourceUrl: ev.data.sourceUrl,
|
||||
metadata: ev.data.subscriptionId,
|
||||
timestamp: ev.timestamp
|
||||
});
|
||||
|
||||
await this.repository.save(event);
|
||||
});
|
||||
|
||||
DomainEvents.subscribe(MemberPaidConverstionEvent, async (ev) => {
|
||||
const event = AnalyticEvent.create({
|
||||
name: 'paid_conversion',
|
||||
memberId: ev.data.memberId,
|
||||
memberStatus: ev.data.memberStatus,
|
||||
entryId: ev.data.entryId,
|
||||
sourceUrl: ev.data.sourceUrl,
|
||||
metadata: ev.data.subscriptionId,
|
||||
timestamp: ev.timestamp
|
||||
});
|
||||
|
||||
await this.repository.save(event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EventHandler;
|
@ -1,32 +0,0 @@
|
||||
{
|
||||
"name": "@tryghost/member-analytics-service",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"author": "Ghost Foundation",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "echo \"Implement me!\"",
|
||||
"test:unit": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura mocha './test/**/*.test.js'",
|
||||
"test": "yarn test:unit",
|
||||
"lint": "eslint . --ext .js --cache"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"lib"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/bookshelf": "1.2.7",
|
||||
"c8": "7.12.0",
|
||||
"mocha": "10.1.0",
|
||||
"should": "13.2.3",
|
||||
"sinon": "14.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tryghost/domain-events": "0.0.0",
|
||||
"@tryghost/errors": "1.2.18",
|
||||
"@tryghost/member-events": "0.0.0",
|
||||
"@tryghost/tpl": "0.1.19",
|
||||
"bson-objectid": "2.0.3"
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: ['ghost'],
|
||||
extends: [
|
||||
'plugin:ghost/test'
|
||||
]
|
||||
};
|
@ -1,10 +0,0 @@
|
||||
// Switch these lines once there are useful utils
|
||||
// const testUtils = require('./utils');
|
||||
require('./utils');
|
||||
|
||||
describe('Hello world', function () {
|
||||
it('Runs a test', function () {
|
||||
// TODO: Write me!
|
||||
'hello'.should.eql('hello');
|
||||
});
|
||||
});
|
@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Custom Should Assertions
|
||||
*
|
||||
* Add any custom assertions to this file.
|
||||
*/
|
||||
|
||||
// Example Assertion
|
||||
// should.Assertion.add('ExampleAssertion', function () {
|
||||
// this.params = {operator: 'to be a valid Example Assertion'};
|
||||
// this.obj.should.be.an.Object;
|
||||
// });
|
@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Test Utilities
|
||||
*
|
||||
* Shared utils for writing tests
|
||||
*/
|
||||
|
||||
// Require overrides - these add globals for tests
|
||||
require('./overrides');
|
||||
|
||||
// Require assertions - adds custom should assertions
|
||||
require('./assertions');
|
@ -1,10 +0,0 @@
|
||||
// This file is required before any test is run
|
||||
|
||||
// Taken from the should wiki, this is how to make should global
|
||||
// Should is a global in our eslint test config
|
||||
global.should = require('should').noConflict();
|
||||
should.extend();
|
||||
|
||||
// Sinon is a simple case
|
||||
// Sinon is a global in our eslint test config
|
||||
global.sinon = require('sinon');
|
@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: ['ghost'],
|
||||
extends: [
|
||||
'plugin:ghost/node'
|
||||
]
|
||||
};
|
@ -1 +0,0 @@
|
||||
module.exports = require('./lib/EventsController');
|
@ -1,36 +0,0 @@
|
||||
const DomainEvents = require('@tryghost/domain-events');
|
||||
const {MemberEntryViewEvent} = require('@tryghost/member-events');
|
||||
|
||||
/**
|
||||
* @template Data
|
||||
* @typedef {object} IEvent
|
||||
* @prop {Date} timestamp
|
||||
* @prop {Data} data
|
||||
*/
|
||||
|
||||
class EventsController {
|
||||
static createEvents(req, res) {
|
||||
try {
|
||||
const {events} = req.body;
|
||||
for (const event of events) {
|
||||
if (event.type === 'entry_view') {
|
||||
const entryEvent = new MemberEntryViewEvent({
|
||||
entryId: event.entry_id,
|
||||
entryUrl: event.entry_url,
|
||||
memberId: req.member ? req.member.id : null,
|
||||
memberStatus: req.member ? req.member.status : null
|
||||
}, event.created_at);
|
||||
DomainEvents.dispatch(entryEvent);
|
||||
}
|
||||
}
|
||||
res.writeHead(201);
|
||||
return res.end('Created.');
|
||||
} catch (err) {
|
||||
const statusCode = (err && err.statusCode) || 500;
|
||||
res.writeHead(statusCode);
|
||||
return res.end('Internal Server Error.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EventsController;
|
@ -1,28 +0,0 @@
|
||||
{
|
||||
"name": "@tryghost/members-analytics-ingress",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"author": "Ghost Foundation",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "echo \"Implement me!\"",
|
||||
"test:unit": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura mocha './test/**/*.test.js'",
|
||||
"test": "yarn test:unit",
|
||||
"lint": "eslint . --ext .js --cache"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"lib"
|
||||
],
|
||||
"devDependencies": {
|
||||
"c8": "7.12.0",
|
||||
"mocha": "10.1.0",
|
||||
"should": "13.2.3",
|
||||
"sinon": "14.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tryghost/domain-events": "0.0.0",
|
||||
"@tryghost/member-events": "0.0.0"
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: ['ghost'],
|
||||
extends: [
|
||||
'plugin:ghost/test'
|
||||
]
|
||||
};
|
@ -1,10 +0,0 @@
|
||||
// Switch these lines once there are useful utils
|
||||
// const testUtils = require('./utils');
|
||||
require('./utils');
|
||||
|
||||
describe('Hello world', function () {
|
||||
it('Runs a test', function () {
|
||||
// TODO: Write me!
|
||||
'hello'.should.eql('hello');
|
||||
});
|
||||
});
|
@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Custom Should Assertions
|
||||
*
|
||||
* Add any custom assertions to this file.
|
||||
*/
|
||||
|
||||
// Example Assertion
|
||||
// should.Assertion.add('ExampleAssertion', function () {
|
||||
// this.params = {operator: 'to be a valid Example Assertion'};
|
||||
// this.obj.should.be.an.Object;
|
||||
// });
|
@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Test Utilities
|
||||
*
|
||||
* Shared utils for writing tests
|
||||
*/
|
||||
|
||||
// Require overrides - these add globals for tests
|
||||
require('./overrides');
|
||||
|
||||
// Require assertions - adds custom should assertions
|
||||
require('./assertions');
|
@ -1,10 +0,0 @@
|
||||
// This file is required before any test is run
|
||||
|
||||
// Taken from the should wiki, this is how to make should global
|
||||
// Should is a global in our eslint test config
|
||||
global.should = require('should').noConflict();
|
||||
should.extend();
|
||||
|
||||
// Sinon is a simple case
|
||||
// Sinon is a global in our eslint test config
|
||||
global.sinon = require('sinon');
|
@ -4,8 +4,6 @@ const MagicLink = require('@tryghost/magic-link');
|
||||
const errors = require('@tryghost/errors');
|
||||
const logging = require('@tryghost/logging');
|
||||
|
||||
const MemberAnalyticsService = require('@tryghost/member-analytics-service');
|
||||
const MembersAnalyticsIngress = require('@tryghost/members-analytics-ingress');
|
||||
const PaymentsService = require('@tryghost/members-payments');
|
||||
|
||||
const TokenService = require('./services/token');
|
||||
@ -48,7 +46,6 @@ module.exports = function MembersAPI({
|
||||
MemberStatusEvent,
|
||||
MemberProductEvent,
|
||||
MemberEmailChangeEvent,
|
||||
MemberAnalyticEvent,
|
||||
MemberCreatedEvent,
|
||||
SubscriptionCreatedEvent,
|
||||
MemberLinkClickEvent,
|
||||
@ -74,9 +71,6 @@ module.exports = function MembersAPI({
|
||||
issuer
|
||||
});
|
||||
|
||||
const memberAnalyticsService = MemberAnalyticsService.create(MemberAnalyticEvent);
|
||||
memberAnalyticsService.eventHandler.setupSubscribers();
|
||||
|
||||
const productRepository = new ProductRepository({
|
||||
Product,
|
||||
Settings,
|
||||
@ -322,10 +316,6 @@ module.exports = function MembersAPI({
|
||||
body.json(),
|
||||
forwardError((req, res) => routerController.createCheckoutSetupSession(req, res))
|
||||
),
|
||||
createEvents: Router().use(
|
||||
body.json(),
|
||||
forwardError((req, res) => MembersAnalyticsIngress.createEvents(req, res))
|
||||
),
|
||||
updateEmailAddress: Router().use(
|
||||
body.json(),
|
||||
forwardError((req, res) => memberController.updateEmailAddress(req, res))
|
||||
|
@ -32,9 +32,7 @@
|
||||
"@tryghost/errors": "1.2.18",
|
||||
"@tryghost/logging": "2.3.2",
|
||||
"@tryghost/magic-link": "0.0.0",
|
||||
"@tryghost/member-analytics-service": "0.0.0",
|
||||
"@tryghost/member-events": "0.0.0",
|
||||
"@tryghost/members-analytics-ingress": "0.0.0",
|
||||
"@tryghost/members-payments": "0.0.0",
|
||||
"@tryghost/nql": "0.11.0",
|
||||
"@tryghost/tpl": "0.1.19",
|
||||
|
Loading…
Reference in New Issue
Block a user