Ghost/ghost/admin/app/helpers/members-event-fetcher.js

197 lines
32 KiB
JavaScript
Raw Normal View History

import moment from 'moment-timezone';
import {Resource} from 'ember-could-get-used-to-this';
import {TrackedArray} from 'tracked-built-ins';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';
export default class MembersEventsFetcher extends Resource {
@service ajax;
@service ghostPaths;
@service store;
@service feature;
@tracked data = new TrackedArray([]);
@tracked isLoading = false;
@tracked isError = false;
@tracked errorMessage = null;
@tracked hasReachedEnd = false;
/**
* Keep track whether we have multiple newsletters (required for parsing events)
*/
@tracked hasMultipleNewsletters = null;
cursor = null;
get value() {
return {
isLoading: this.isLoading,
isError: this.isError,
errorMessage: this.errorMessage,
data: this.data,
loadNextPage: this.loadNextPage,
hasReachedEnd: this.hasReachedEnd,
hasMultipleNewsletters: this.hasMultipleNewsletters
};
}
async setup() {
this.cursor = moment.utc().format('YYYY-MM-DD HH:mm:ss');
let filter = `data.created_at:<'${this.cursor}'`;
if (this.args.named.filter) {
filter += `+${this.args.named.filter}`;
}
// Can't get this working with Promise.all, somehow results in an infinite loop
await this.loadEventsTask.perform({filter});
await this.loadMultipleNewslettersTask.perform();
}
@action
loadNextPage() {
// NOTE: assumes data is always ordered by created_at desc
const lastEvent = this.data[this.data.length - 1];
if (!lastEvent?.data?.created_at) {
this.hasReachedEnd = true;
return;
}
const cursor = moment.utc(lastEvent.data.created_at).format('YYYY-MM-DD HH:mm:ss');
if (cursor === this.cursor) {
this.hasReachedEnd = true;
return;
}
this.cursor = cursor;
let filter = `data.created_at:<'${this.cursor}'`;
if (this.args.named.filter) {
filter += `+${this.args.named.filter}`;
}
this.loadEventsTask.perform({filter});
}
/**
* We need to know whether we have multiple newsletters so we can hide/show the newsletter name
*/
@task
*loadMultipleNewslettersTask() {
try {
const res = yield this.store.query('newsletter', {filter: 'status:active', include: 'none', limit: 1});
const newsletterCount = res.meta.pagination.total;
this.hasMultipleNewsletters = newsletterCount > 1;
} catch (e) {
// Default to true (harms the least)
this.hasMultipleNewsletters = true;
console.error(e); // eslint-disable-line
}
}
@task
*loadEventsTask(queryParams) {
try {
this.isLoading = true;
const url = this.ghostPaths.url.api('members/events');
const data = Object.assign({}, queryParams, {limit: this.args.named.pageSize});
const {events} = yield this.ajax.request(url, {data});
if (events.length < data.limit) {
this.hasReachedEnd = true;
}
this.data.push(...events);
// todo: remove all condition block when backend will be ready
if (this.feature.suppressionList && !queryParams.filter.includes('email_delivered_event')) {
const memberId = this.args.named.memberId;
if (!this.args.named.memberId) {
return;
}
const member = yield this.store.findRecord('member', memberId);
if (member.email === 'spam@ghost.org') {
this.data.unshift(mockData('email_delivered_event'));
this.data.unshift(mockData('email_complaint_event'));
}
if (member.email === 'fail@ghost.org') {
this.data.unshift(mockData('email_failed_event'));
}
}
} catch (e) {
this.isError = true;
const errorMessage = e.payload?.errors?.[0]?.message;
if (errorMessage) {
this.errorMessage = errorMessage;
}
// TODO: log to Sentry
console.error(e); // eslint-disable-line
} finally {
this.isLoading = false;
}
}
}
function mockData(eventType) {
return ({
type: eventType,
data: {
id: '6375cc411ebedb499bef54c7',
member_id: '63737a1719675aed3b7cc988',
created_at: moment.utc(),
member: {
id: '63737a1719675aed3b7cc988',
uuid: '5c753e47-9f49-43ad-86d4-c5c0168519a2',
email: 'spam@ghost.org',
status: 'free',
name: 'Spam',
expertise: null,
note: null,
geolocation: null,
enable_comment_notifications: true,
email_count: 6,
email_opened_count: 2,
email_open_rate: 33,
last_seen_at: '2022-11-16T04:44:19.000Z',
last_commented_at: null,
created_at: '2022-11-15T11:37:59.000Z',
updated_at: '2022-11-17T05:33:13.000Z',
avatar_image: 'https://www.gravatar.com/avatar/0e0d23869265932bca724acda6c7e529?s=250&r=g&d=blank'
},
email: {
id: '6375cc411ebedb499bef54c4',
post_id: '6375cc3e1ebedb499bef54b5',
uuid: '01f393bd-b487-4540-995f-d09ad47059d8',
status: 'submitted',
recipient_filter: 'all',
error: null,
error_data: '[]',
email_count: 2,
delivered_count: 2,
opened_count: 0,
failed_count: 0,
subject: 'Spam test',
from: '"local"<localhost@example.com>',
reply_to: 'noreply@localhost',
html: '<!doctype html>\n<html>\n\n<head>\n<meta name="viewport" content="width=device-width">\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\n<!--[if mso]><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch><o:AllowPNG/></o:OfficeDocumentSettings></xml><![endif]-->\n<title>Spam test</title>\n<style>\n.post-title-link {\n color: #15212A;\n display: block;\n text-align: center;\n margin-top: 50px;\n}\n.post-title-link-left {\n text-align: left;\n}\n.view-online-link {\n word-wrap: none;\n white-space: nowrap;\n color: #15212A;\n}\n.kg-nft-link {\n display: block;\n text-decoration: none !important;\n color: #15212A !important;\n font-family: inherit !important;\n font-size: 14px;\n line-height: 1.3em;\n padding-top: 4px;\n padding-right: 20px;\n padding-left: 20px;\n padding-bottom: 4px;\n}\n.kg-twitter-link {\n display: block;\n text-decoration: none !important;\n color: #15212A !important;\n font-family: inherit !important;\n font-size: 15px;\n padding: 8px;\n line-height: 1.3em;\n}\n@media only screen and (max-width: 620px) {\n table.body {\n width: 100%;\n min-width: 100%;\n }\n\n table.body p,\ntable.body ul,\ntable.body ol,\ntable.body td,\ntable.body span {\n font-size: 16px !important;\n }\n\n table.body pre {\n white-space: pre-wrap !important;\n word-break: break-word !important;\n }\n\n table.body .wrapper,\ntable.body .article {\n padding: 0 10px !important;\n }\n\n table.body .content {\n padding: 0 !important;\n }\n\n table.body .container {\n padding: 0 !important;\n width: 100% !important;\n }\n\n table.body .main {\n border-left-width: 0 !important;\n border-radius: 0 !important;\n border-right-width: 0 !important;\n }\n\n table.body .btn table {\n width: 100% !important;\n }\n\n table.body .btn a {\n width: 100% !important;\n }\n\n table.body .img-responsive {\n height: auto !important;\n max-width: 100% !important;\n width: auto !important;\n }\n\n table.body .site-icon img {\n width: 40px !important;\n height: 40px !important;\n }\n\n table.body .site-url a {\n font-size: 14px !important;\n padding-bottom: 15px !important;\n }\n\n table.body .post-meta {\n white-space: normal !important;\n font-size: 12px !important;\n line-height: 1.5em;\n }\n\n table.body .view-online-link,\ntable.body .footer,\ntable.body .footer a {\n font-size: 12px !important;\n }\n\n table.body .post-title a {\n font-size: 32px !important;\n line-height: 1.15em !important;\n }\n\n table.body .kg-bookmark-card {\n width: 90vw !important;\n }\n\n table.body .kg-bookmark-thumbnail {\n display: none !important;\n }\n\n table.body .kg-bookmark-metadata span {\n font-size: 13px !important;\n }\n\n table.body .kg-embed-card {\n max-width: 90vw !important;\n }\n\n table.body h1 {\n font-size: 32px !important;\n line-height: 1.3em !important;\n }\n\n table.body h2 {\n font-size: 26px !important;\n line-height: 1.22em !important;\n }\n\n table.body h3 {\n font-size: 21px !important;\n line-height: 1.25em !important;\n }\n\n table.body h4 {\n font-size: 19px !important;\n line-height: 1.3em !important;\n }\n\n table.body h5 {\n font-size: 16px !important;\n line-height: 1.4em !important;\n }\n\n table.body h6 {\n font-size: 16px !important;\n line-height: 1.4em !important;\n }\n\n table.body blockquote {\n font-size: 17px;\n line-height: 1.6em;\n margin-bottom: 0;\n padding-left: 15px;\n }\n\n table.body blockquote.kg-blockquote-alt {\n border-left: 0 none !important;\n margin: 0 0 2.5em 0 !important;\n padding: 0 50px 0 50px !important;\n font-size: 1.2em;\n }\n\n table.body blockquote + * {\n margin-top: 1.5em !important;\n }\n\n table.body hr {\n margin: 2em 0 !important;\n }\n\n table.body figcaption,\ntable.body figcaption a {\n font-size: 13px !important;\n }\n}\n@media all {\n .ExternalClass {\n width: 100%;\n }\n\n .ExternalClass,
plaintext: null,
track_opens: true,
track_clicks: true,
submitted_at: '2022-11-17T05:53:05.000Z',
newsletter_id: '123',
created_at: '2022-11-17T05:53:05.000Z',
updated_at: '2022-11-17T05:53:07.000Z',
feedback_enabled: true
}
}
});
}