Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ghost/admin/app/helpers/parse-member-event.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ export default class ParseMemberEventHelper extends Helper {
icon = 'gift';
}

if (event.type === 'gift_ended_event') {
icon = 'subscriptions';
}

if (event.type === 'email_change_event') {
icon = 'email-changed';
}
Expand Down Expand Up @@ -279,6 +283,10 @@ export default class ParseMemberEventHelper extends Helper {
if (event.type === 'gift_redemption_event') {
return 'started paid subscription via gift';
}

if (event.type === 'gift_ended_event') {
return 'ended paid subscription';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ok being the same as subscription_event.expired? I don't think we can end up with the same event twice right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, it's either a gift or a paid subscription, so won't be logged twice for the same subscription. The copy / icon for the event are intentionally the same as with a paid subscription:

Image

}
}

/**
Expand Down
2 changes: 2 additions & 0 deletions ghost/admin/app/utils/member-event-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ export function toggleEventType(eventType, currentExcludedEvents = []) {
if (excludedEvents.has('subscription_event')) {
excludedEvents.delete('subscription_event');
excludedEvents.delete('gift_redemption_event');
excludedEvents.delete('gift_ended_event');
} else {
excludedEvents.add('subscription_event');
excludedEvents.add('gift_redemption_event');
excludedEvents.add('gift_ended_event');
}
} else if (eventType === 'payment_event') {
if (excludedEvents.has('payment_event')) {
Expand Down
14 changes: 14 additions & 0 deletions ghost/admin/tests/unit/helpers/parse-member-event-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,18 @@ describe('Unit: Helper: parse-member-event', function () {
expect(result.info).to.equal('Free');
});
});

describe('gift_ended_event', function () {
it('returns "ended paid subscription" action', function () {
const event = buildEvent({type: 'gift_ended_event'});
const result = helper.compute([event]);
expect(result.action).to.equal('ended paid subscription');
});

it('returns "event-subscriptions" icon', function () {
const event = buildEvent({type: 'gift_ended_event'});
const result = helper.compute([event]);
expect(result.icon).to.equal('event-subscriptions');
});
});
});
14 changes: 7 additions & 7 deletions ghost/admin/tests/unit/utils/member-event-types-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,34 +29,34 @@ describe('Unit | Utility | event-type-utils', function () {
expect(newExcludedEvents).to.equal('');
});

it('should toggle subscription_event together with gift_redemption_event', function () {
it('should toggle subscription_event together with gift_redemption_event and gift_ended_event', function () {
const newExcludedEvents = toggleEventType('subscription_event', []);

expect(newExcludedEvents).to.equal('subscription_event,gift_redemption_event');
expect(newExcludedEvents).to.equal('subscription_event,gift_redemption_event,gift_ended_event');
});

it('should toggle subscription_event group off when toggling subscription_event off', function () {
const newExcludedEvents = toggleEventType('subscription_event', ['subscription_event', 'gift_redemption_event']);
const newExcludedEvents = toggleEventType('subscription_event', ['subscription_event', 'gift_redemption_event', 'gift_ended_event']);

expect(newExcludedEvents).to.equal('');
});

it('should preserve previously-excluded payment group when toggling subscription_event', function () {
const newExcludedEvents = toggleEventType('subscription_event', ['payment_event', 'donation_event', 'gift_purchase_event']);

expect(newExcludedEvents).to.equal('payment_event,donation_event,gift_purchase_event,subscription_event,gift_redemption_event');
expect(newExcludedEvents).to.equal('payment_event,donation_event,gift_purchase_event,subscription_event,gift_redemption_event,gift_ended_event');
});

it('should preserve previously-excluded subscription group when toggling payment_event', function () {
const newExcludedEvents = toggleEventType('payment_event', ['subscription_event', 'gift_redemption_event']);
const newExcludedEvents = toggleEventType('payment_event', ['subscription_event', 'gift_redemption_event', 'gift_ended_event']);

expect(newExcludedEvents).to.equal('subscription_event,gift_redemption_event,payment_event,donation_event,gift_purchase_event');
expect(newExcludedEvents).to.equal('subscription_event,gift_redemption_event,gift_ended_event,payment_event,donation_event,gift_purchase_event');
});

it('should accept a comma-separated string for currentExcludedEvents', function () {
const newExcludedEvents = toggleEventType('subscription_event', 'payment_event,donation_event,gift_purchase_event');

expect(newExcludedEvents).to.equal('payment_event,donation_event,gift_purchase_event,subscription_event,gift_redemption_event');
expect(newExcludedEvents).to.equal('payment_event,donation_event,gift_purchase_event,subscription_event,gift_redemption_event,gift_ended_event');
});

it('should return correct divider need based on event groups', function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ module.exports = class EventRepository {
{type: 'payment_event', action: 'getPaymentEvents'},
{type: 'email_change_event', action: 'getEmailChangeEvent'},
{type: 'gift_purchase_event', action: 'getGiftPurchaseEvents'},
{type: 'gift_redemption_event', action: 'getGiftRedemptionEvents'}
{type: 'gift_redemption_event', action: 'getGiftRedemptionEvents'},
{type: 'gift_ended_event', action: 'getGiftEndedEvents'}
Comment thread
sagzy marked this conversation as resolved.
);

if (this._AutomatedEmailRecipient) {
Expand Down Expand Up @@ -136,6 +137,7 @@ module.exports = class EventRepository {
login_event: 0,
subscription_event: 1,
gift_redemption_event: 1,
gift_ended_event: 1,
newsletter_event: 2,
signup_event: 3
};
Expand Down Expand Up @@ -554,6 +556,46 @@ module.exports = class EventRepository {
};
}

async getGiftEndedEvents(options = {}, filter) {
options = {
...options,
withRelated: ['member'],
filter: 'from_status:gift+to_status:free+custom:true',
useBasicCount: true,
mongoTransformer: chainTransformers(
// First set the filter manually
replaceCustomFilterTransformer(filter),

// Map the used keys in that filter
...mapKeys({
'data.created_at': 'created_at',
'data.member_id': 'member_id'
Comment on lines +571 to +572
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this re-mapping if we are using the same named keys?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still needed to strip the data prefix (also what we do in getPaymentEvents, getLoginEvents, getNewsletterSubscriptionEvents, etc.)

})
)
};

const {data: models, meta} = await this._MemberStatusEvent.findPage(options);

const data = models.map((model) => {
const json = model.toJSON(options);

return {
type: 'gift_ended_event',
data: {
id: json.id,
member: json.member || null,
member_id: json.member_id,
created_at: json.created_at
}
};
});

return {
data,
meta
};
}

async getCommentEvents(options = {}, filter) {
options = {
...options,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -658,4 +658,102 @@ describe('EventRepository', function () {
assert.equal(event.data.member_id, null);
});
});

describe('getGiftEndedEvents', function () {
let eventRepository;
let fake;

before(function () {
fake = sinon.fake.returns({data: [{
toJSON: () => ({
id: 'status-event-1',
member_id: 'member-abc',
member: {id: 'member-abc', name: 'Test Member', email: 'member@example.com'},
from_status: 'gift',
to_status: 'free',
created_at: '2024-10-15T08:00:00.000Z'
})
}]});
eventRepository = new EventRepository({
EmailRecipient: null,
MemberSubscribeEvent: null,
MemberPaymentEvent: null,
MemberStatusEvent: {
findPage: fake
},
MemberLoginEvent: null,
MemberPaidSubscriptionEvent: null,
labsService: null
});
});

afterEach(function () {
fake.resetHistory();
});

it('queries with correct options', async function () {
await eventRepository.getGiftEndedEvents({
filter: 'not used',
order: 'created_at desc, id desc'
}, {
type: 'unused'
});

sinon.assert.calledOnceWithMatch(fake, {
withRelated: ['member'],
filter: 'from_status:gift+to_status:free+custom:true',
order: 'created_at desc, id desc'
});
});

it('returns correctly formatted gift_ended_event', async function () {
const result = await eventRepository.getGiftEndedEvents({
order: 'created_at desc, id desc'
}, {});

assert.equal(result.data.length, 1);

const event = result.data[0];

assert.equal(event.type, 'gift_ended_event');
assert.equal(event.data.id, 'status-event-1');
assert.equal(event.data.member_id, 'member-abc');
assert.equal(event.data.created_at, '2024-10-15T08:00:00.000Z');
assert.deepEqual(event.data.member, {
id: 'member-abc',
name: 'Test Member',
email: 'member@example.com'
});
});

it('sets member to null when member relation is not present', async function () {
const nullMemberFake = sinon.fake.returns({data: [{
toJSON: () => ({
id: 'status-event-2',
member_id: 'member-xyz',
member: null,
from_status: 'gift',
to_status: 'free',
created_at: '2024-11-01T12:00:00.000Z'
})
}]});
const repo = new EventRepository({
EmailRecipient: null,
MemberSubscribeEvent: null,
MemberPaymentEvent: null,
MemberStatusEvent: {
findPage: nullMemberFake
},
MemberLoginEvent: null,
MemberPaidSubscriptionEvent: null,
labsService: null
});

const result = await repo.getGiftEndedEvents({}, {});
const event = result.data[0];

assert.equal(event.data.member, null);
assert.equal(event.data.member_id, 'member-xyz');
});
});
});
Loading