Skip to content

Commit

Permalink
Added email sent events (#15682)
Browse files Browse the repository at this point in the history
fixes https://github.com/TryGhost/Team/issues/2137

For the analytics page, we need the sent events to show up immediately
after sending an email. Otherwise we need to wait for emails to be
marked as received (which takes too long) before being able to show them
on the analytics page.

This adds the email_sent_event, which is hidden by default everywhere
and used on the analytics page.
  • Loading branch information
SimonBackx committed Oct 24, 2022
1 parent a650ae2 commit fd91f7e
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 15 deletions.
2 changes: 1 addition & 1 deletion ghost/admin/app/components/member/activity-feed.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</div>
</div>
{{else}}
{{#let (members-event-fetcher filter=(members-event-filter member=@member.id) pageSize=5) as |eventsFetcher|}}
{{#let (members-event-fetcher filter=(members-event-filter member=@member.id excludedEvents=this.excludedEventTypes) pageSize=5) as |eventsFetcher|}}
<div class="gh-main-section-content grey {{if eventsFetcher.data "" "mt8"}}">
<div class="gh-member-feed {{if eventsFetcher.data "" "gh-member-feed-no-data"}}" ...attributes>
<div class="flex-auto flex flex-column items-stretch {{if eventsFetcher.data "justify-between" "h-100 justify-center"}}">
Expand Down
3 changes: 2 additions & 1 deletion ghost/admin/app/components/member/activity-feed.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {action} from '@ember/object';

export default class ActivityFeed extends Component {
linkScrollerTimeout = null; // needs to be global so can be cleared when needed across functions
excludedEventTypes = ['email_sent_event'];

@action
enterLinkURL(event) {
Expand All @@ -29,4 +30,4 @@ export default class ActivityFeed extends Component {
child.style.transform = 'translateX(0)';
parent.classList.remove('scroller');
}
}
}
3 changes: 2 additions & 1 deletion ghost/admin/app/components/posts/post-activity-feed.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ const allEvents = [
'click_event',
'signup_event',
'subscription_event',
'email_sent_event',
'email_delivered_event',
'email_opened_event',
'email_failed_event',
'feedback_event'
];

const eventTypes = {
sent: ['email_delivered_event'],
sent: ['email_sent_event'],
opened: ['email_opened_event'],
clicked: ['click_event'],
feedback: ['feedback_event'],
Expand Down
3 changes: 3 additions & 0 deletions ghost/admin/app/controllers/members-activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export default class MembersActivityController extends Controller {

if (!this.member) {
hiddenEvents.push(...EMAIL_EVENTS);
} else {
// Always hide sent event
hiddenEvents.push('email_sent_event');
}

if (this.settings.editorDefaultEmailRecipients === 'disabled') {
Expand Down
2 changes: 1 addition & 1 deletion ghost/admin/app/helpers/members-event-filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import classic from 'ember-classic-decorator';
import {isBlank} from '@ember/utils';
import {inject as service} from '@ember/service';

export const EMAIL_EVENTS = ['email_delivered_event','email_opened_event','email_failed_event'];
export const EMAIL_EVENTS = ['email_sent_event', 'email_delivered_event', 'email_opened_event','email_failed_event'];
export const NEWSLETTER_EVENTS = ['newsletter_event'];

@classic
Expand Down
4 changes: 2 additions & 2 deletions ghost/admin/app/helpers/parse-member-event.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export default class ParseMemberEventHelper extends Helper {
icon = 'opened-email';
}

if (event.type === 'email_delivered_event') {
if (event.type === 'email_delivered_event' || event.type === 'email_sent_event') {
icon = 'received-email';
}

Expand Down Expand Up @@ -149,7 +149,7 @@ export default class ParseMemberEventHelper extends Helper {
return 'opened email';
}

if (event.type === 'email_delivered_event') {
if (event.type === 'email_delivered_event' || event.type === 'email_sent_event') {
return 'received email';
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,35 @@ Object {
"data": Any<Object>,
"type": Any<String>,
},
Object {
"data": Any<Object>,
"type": Any<String>,
},
Object {
"data": Any<Object>,
"type": Any<String>,
},
Object {
"data": Any<Object>,
"type": Any<String>,
},
Object {
"data": Any<Object>,
"type": Any<String>,
},
Object {
"data": Any<Object>,
"type": Any<String>,
},
],
"meta": Object {
"pagination": Object {
"limit": 10,
"limit": "20",
"next": null,
"page": null,
"pages": 1,
"prev": null,
"total": 10,
"total": 15,
},
},
}
Expand All @@ -61,7 +81,7 @@ exports[`Activity Feed API Can filter events by post id 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": "17358",
"content-length": "23031",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Version, Origin, Accept-Encoding",
Expand All @@ -86,9 +106,9 @@ Object {
"limit": "2",
"next": null,
"page": null,
"pages": 5,
"pages": 8,
"prev": null,
"total": 10,
"total": 15,
},
},
}
Expand Down Expand Up @@ -422,6 +442,55 @@ Object {
}
`;

exports[`Activity Feed API Returns email sent events in activity feed 1: [body] 1`] = `
Object {
"events": Array [
Object {
"data": Any<Object>,
"type": Any<String>,
},
Object {
"data": Any<Object>,
"type": Any<String>,
},
Object {
"data": Any<Object>,
"type": Any<String>,
},
Object {
"data": Any<Object>,
"type": Any<String>,
},
Object {
"data": Any<Object>,
"type": Any<String>,
},
],
"meta": Object {
"pagination": Object {
"limit": 10,
"next": null,
"page": null,
"pages": 1,
"prev": null,
"total": 5,
},
},
}
`;

exports[`Activity Feed API Returns email sent events in activity feed 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": "5774",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Version, Origin, Accept-Encoding",
"x-powered-by": "Express",
}
`;

exports[`Activity Feed API Returns feedback events in activity feed 1: [body] 1`] = `
Object {
"events": Array [
Expand Down
29 changes: 25 additions & 4 deletions ghost/core/test/e2e-api/admin/activity-feed.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,26 @@ describe('Activity Feed API', function () {
});
});

it('Returns email sent events in activity feed', async function () {
// Check activity feed
await agent
.get(`/members/events?filter=type:email_sent_event`)
.expectStatus(200)
.matchHeaderSnapshot({
etag: anyEtag
})
.matchBodySnapshot({
events: new Array(5).fill({
type: anyString,
data: anyObject
})
})
.expect(({body}) => {
assert(body.events.find(e => e.type === 'email_sent_event'), 'Expected an email sent event');
assert(!body.events.find(e => e.type !== 'email_sent_event'), 'Expected only email sent events');
});
});

it('Returns email delivered events in activity feed', async function () {
// Check activity feed
await agent
Expand Down Expand Up @@ -170,13 +190,13 @@ describe('Activity Feed API', function () {
const postId = fixtureManager.get('posts', 0).id;

await agent
.get(`/members/events?filter=data.post_id:${postId}`)
.get(`/members/events?filter=data.post_id:${postId}&limit=20`)
.expectStatus(200)
.matchHeaderSnapshot({
etag: anyEtag
})
.matchBodySnapshot({
events: new Array(10).fill({
events: new Array(15).fill({
type: anyString,
data: anyObject
})
Expand All @@ -191,10 +211,11 @@ describe('Activity Feed API', function () {
assert(body.events.find(e => e.type === 'signup_event'), 'Expected a signup event');
assert(body.events.find(e => e.type === 'subscription_event'), 'Expected a subscription event');
assert(body.events.find(e => e.type === 'email_delivered_event'), 'Expected an email delivered event');
assert(body.events.find(e => e.type === 'email_sent_event'), 'Expected an email sent event');
assert(body.events.find(e => e.type === 'email_opened_event'), 'Expected an email opened event');

// Assert total is correct
assert.equal(body.meta.pagination.total, 10);
assert.equal(body.meta.pagination.total, 15);
});
});

Expand All @@ -216,7 +237,7 @@ describe('Activity Feed API', function () {
assert(!body.events.find(e => (e.data?.post?.id ?? e.data?.attribution?.id ?? e.data?.email?.post_id) !== postId), 'Should only return events for the post');

// Assert total is correct
assert.equal(body.meta.pagination.total, 10);
assert.equal(body.meta.pagination.total, 15);
});
});
});
41 changes: 41 additions & 0 deletions ghost/members-api/lib/repositories/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ module.exports = class EventRepository {
}

if (this._EmailRecipient) {
pageActions.push({type: 'email_sent_event', action: 'getEmailSentEvents'});
pageActions.push({type: 'email_delivered_event', action: 'getEmailDeliveredEvents'});
pageActions.push({type: 'email_opened_event', action: 'getEmailOpenedEvents'});
pageActions.push({type: 'email_failed_event', action: 'getEmailFailedEvents'});
Expand Down Expand Up @@ -431,6 +432,46 @@ module.exports = class EventRepository {
};
}

async getEmailSentEvents(options = {}, filters = {}) {
options = {
...options,
withRelated: ['member', 'email'],
filter: ['failed_at:null', 'processed_at:-null']
};
if (filters['data.created_at']) {
options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'processed_at:'));
}
if (filters['data.member_id']) {
options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
}
if (filters['data.post_id']) {
options.filter.push(filters['data.post_id'].replace(/data.post_id:/g, 'email.post_id:'));
}
options.filter = options.filter.join('+');
options.order = options.order.replace(/created_at/g, 'processed_at');

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

const data = models.map((model) => {
return {
type: 'email_sent_event',
data: {
member_id: model.get('member_id'),
created_at: model.get('processed_at'),
member: model.related('member').toJSON(),
email: model.related('email').toJSON()
}
};
});

return {
data,
meta
};
}

async getEmailDeliveredEvents(options = {}, filters = {}) {
options = {
...options,
Expand Down

0 comments on commit fd91f7e

Please sign in to comment.