0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Added flag to disable lastSeenAt updater for member link clicks (#21802)

ref https://linear.app/ghost/issue/ENG-1814

This provides a way for us, if needed, to disable the least important piece of the member click event cascade.
This commit is contained in:
Steve Larson 2024-12-04 10:53:01 -06:00 committed by GitHub
parent 4c13f188ce
commit 3da2a9f64e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 100 additions and 11 deletions

View file

@ -3,6 +3,7 @@ const DomainEvents = require('@tryghost/domain-events');
const events = require('../../lib/common/events');
const settingsCache = require('../../../shared/settings-cache');
const members = require('../members');
const config = require('../../../shared/config');
class MembersEventsServiceWrapper {
init() {
@ -43,7 +44,8 @@ class MembersEventsServiceWrapper {
},
db,
events,
lastSeenAtCache: this.lastSeenAtCache
lastSeenAtCache: this.lastSeenAtCache,
config
});
// Subscribe to domain events

View file

@ -193,7 +193,8 @@
"enableStripePromoCodes": false,
"emailAnalytics": true,
"backgroundJobs": {
"emailAnalytics": true
"emailAnalytics": true,
"clickTrackingLastSeenAtUpdater": true
},
"portal": {
"url": "https://cdn.jsdelivr.net/ghost/portal@~{version}/umd/portal.min.js",

View file

@ -18,6 +18,7 @@ class LastSeenAtUpdater {
* @param {any} deps.db Database connection
* @param {any} deps.events The event emitter
* @param {any} deps.lastSeenAtCache An instance of the last seen at cache
* @param {any} deps.config Ghost config for click tracking
*/
constructor({
services: {
@ -26,7 +27,8 @@ class LastSeenAtUpdater {
getMembersApi,
db,
events,
lastSeenAtCache
lastSeenAtCache,
config
}) {
if (!getMembersApi) {
throw new IncorrectUsageError({message: 'Missing option getMembersApi'});
@ -37,6 +39,7 @@ class LastSeenAtUpdater {
this._db = db;
this._events = events;
this._lastSeenAtCache = lastSeenAtCache || new LastSeenAtCache({services: {settingsCache}});
this._config = config;
}
/**
* Subscribe to events of this domainEvents service
@ -52,14 +55,18 @@ class LastSeenAtUpdater {
}
});
domainEvents.subscribe(MemberLinkClickEvent, async (event) => {
try {
await this.cachedUpdateLastSeenAt(event.data.memberId, event.data.memberLastSeenAt, event.timestamp);
} catch (err) {
logging.error(`Error in LastSeenAtUpdater.MemberLinkClickEvent listener for member ${event.data.memberId}`);
logging.error(err);
}
});
// Only disable if explicitly set to false in config
const shouldUpdateForClickTracking = !this._config || this._config.get('backgroundJobs:clickTrackingLastSeenAtUpdater') !== false;
if (shouldUpdateForClickTracking) {
domainEvents.subscribe(MemberLinkClickEvent, async (event) => {
try {
await this.cachedUpdateLastSeenAt(event.data.memberId, event.data.memberLastSeenAt, event.timestamp);
} catch (err) {
logging.error(`Error in LastSeenAtUpdater.MemberLinkClickEvent listener for member ${event.data.memberId}`);
logging.error(err);
}
});
}
domainEvents.subscribe(MemberCommentEvent, async (event) => {
try {

View file

@ -285,6 +285,85 @@ describe('LastSeenAtUpdater', function () {
await DomainEvents.allSettled();
assert(spy.notCalled, 'The LastSeenAtUpdater should never fire on MemberSubscribeEvent events.');
});
describe('Disable via config', function () {
it('MemberLinkClickEvent should not be fired when disabled', async function () {
const now = moment.utc('2022-02-28T18:00:00Z');
const previousLastSeen = moment.utc('2022-02-27T22:59:00Z').toDate();
const stub = sinon.stub().resolves();
const settingsCache = sinon.stub();
settingsCache.returns('Europe/Brussels'); // Default return for other settings
const configStub = sinon.stub();
configStub.withArgs('backgroundJobs:clickTrackingLastSeenAtUpdater').returns(false);
const updater = new LastSeenAtUpdater({
services: {
settingsCache: {
get: settingsCache
}
},
config: {
get: configStub
},
getMembersApi() {
return {
members: {
update: stub,
get: () => {
return {
id: '1',
get: () => {
return previousLastSeen;
}
};
}
}
};
},
events
});
updater.subscribe(DomainEvents);
sinon.stub(updater, 'updateLastSeenAt');
DomainEvents.dispatch(MemberLinkClickEvent.create({memberId: '1', memberLastSeenAt: previousLastSeen, url: '/'}, now.toDate()));
assert(updater.updateLastSeenAt.notCalled, 'The LastSeenAtUpdater should not attempt a member update when disabled');
});
it('MemberLinkClickEvent should be fired when enabled/empty', async function () {
const now = moment.utc('2022-02-28T18:00:00Z');
const previousLastSeen = moment.utc('2022-02-27T22:59:00Z').toDate();
const stub = sinon.stub().resolves();
const settingsCache = sinon.stub();
settingsCache.returns('Europe/Brussels'); // Default return for other settings
const updater = new LastSeenAtUpdater({
services: {
settingsCache: {
get: settingsCache
}
},
getMembersApi() {
return {
members: {
update: stub,
get: () => {
return {
id: '1',
get: () => {
return previousLastSeen;
}
};
}
}
};
},
events
});
updater.subscribe(DomainEvents);
sinon.stub(updater, 'updateLastSeenAt');
DomainEvents.dispatch(MemberLinkClickEvent.create({memberId: '1', memberLastSeenAt: previousLastSeen, url: '/'}, now.toDate()));
assert(updater.updateLastSeenAt.calledOnce, 'The LastSeenAtUpdater should attempt a member update when not disabled');
});
});
});
describe('updateLastSeenAt', function () {