Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added email sent events #15682

Merged
merged 1 commit into from
Oct 24, 2022
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
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 [email protected]) pageSize=5) as |eventsFetcher|}}
{{#let (members-event-fetcher filter=(members-event-filter [email protected] 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