Ghost/ghost/admin/app/helpers/parse-member-event.js
Simon Backx 076e3c02b2
Added linking between member and subscription created events (#15693)
fixes https://github.com/TryGhost/Team/issues/2160

- Adds a `batch_id` to both events that contain the same ID if they were created at the same time.
- Removes duplicate signup/conversion events using the batch_id
- Requires an update in mongo-knex to work (refs https://ghost.slack.com/archives/C02G9E68C/p1666773313272409?thread_ts=1666767872.375009&cid=C02G9E68C)
- Some dependencies needed an update to load the latest mongo-knex
- Added tiers to membersUtils, loaded on startup (we can start to use this instead of fetching it every time)
2022-10-27 11:44:19 +02:00

301 lines
8.9 KiB
JavaScript

import Helper from '@ember/component/helper';
import moment from 'moment-timezone';
import {getNonDecimal, getSymbol} from 'ghost-admin/utils/currency';
import {inject as service} from '@ember/service';
export default class ParseMemberEventHelper extends Helper {
@service feature;
@service utils;
@service membersUtils;
compute([event, hasMultipleNewsletters]) {
const subject = event.data.member.name || event.data.member.email;
const icon = this.getIcon(event);
const action = this.getAction(event, hasMultipleNewsletters);
const info = this.getInfo(event);
const description = this.getDescription(event);
const join = this.getJoin(event);
const object = this.getObject(event);
const url = this.getURL(event);
const timestamp = moment(event.data.created_at);
const source = this.getSource(event);
return {
memberId: event.data.member_id ?? event.data.member?.id,
member: event.data.member,
emailId: event.data.email_id,
email: event.data.email,
icon,
subject,
action,
join,
object,
source,
info,
description,
url,
timestamp
};
}
/* internal helper functions */
getIcon(event) {
let icon;
if (event.type === 'login_event') {
icon = 'logged-in';
}
if (event.type === 'payment_event') {
icon = 'subscriptions';
}
if (event.type === 'newsletter_event') {
if (event.data.subscribed) {
icon = 'subscribed-to-email';
} else {
icon = 'unsubscribed-from-email';
}
}
if (event.type === 'subscription_event') {
icon = 'subscriptions';
if (event.data.type === 'canceled') {
icon = 'canceled-subscription';
}
}
if (event.type === 'signup_event' || (event.type === 'subscription_event' && event.data.type === 'created' && event.data.signup)) {
icon = 'signed-up';
}
if (event.type === 'email_opened_event') {
icon = 'opened-email';
}
if (event.type === 'email_delivered_event' || event.type === 'email_sent_event') {
icon = 'received-email';
}
if (event.type === 'email_failed_event') {
icon = 'email-delivery-failed';
}
if (event.type === 'comment_event') {
icon = 'comment';
}
if (event.type === 'click_event') {
icon = 'click';
}
if (event.type === 'feedback_event') {
if (event.data.score === 1) {
icon = 'more-like-this';
} else {
icon = 'less-like-this';
}
}
return 'event-' + icon + (this.feature.get('memberAttribution') ? '--feature-attribution' : '');
}
getAction(event, hasMultipleNewsletters) {
if (event.type === 'signup_event' || (event.type === 'subscription_event' && event.data.type === 'created' && event.data.signup)) {
return 'signed up';
}
if (event.type === 'login_event') {
return 'logged in';
}
if (event.type === 'payment_event') {
return 'made payment';
}
if (event.type === 'newsletter_event') {
let newsletter = 'newsletter';
if (hasMultipleNewsletters && event.data.newsletter && event.data.newsletter.name) {
newsletter = event.data.newsletter.name;
}
if (event.data.subscribed) {
return 'subscribed to ' + newsletter;
} else {
return 'unsubscribed from ' + newsletter;
}
}
if (event.type === 'subscription_event') {
if (event.data.type === 'created') {
return 'started paid subscription';
}
if (event.data.type === 'updated') {
return 'changed paid subscription';
}
if (event.data.type === 'canceled') {
return 'canceled paid subscription';
}
if (event.data.type === 'reactivated') {
return 'reactivated paid subscription';
}
if (event.data.type === 'expired') {
return 'ended paid subscription';
}
return 'changed paid subscription';
}
if (event.type === 'email_opened_event') {
return 'opened email';
}
if (event.type === 'email_delivered_event' || event.type === 'email_sent_event') {
return 'received email';
}
if (event.type === 'email_failed_event') {
return 'failed to receive email';
}
if (event.type === 'comment_event') {
if (event.data.parent) {
return 'replied to comment';
}
return 'commented';
}
if (event.type === 'click_event') {
return 'clicked link in email';
}
if (event.type === 'feedback_event') {
if (event.data.score === 1) {
return 'more like this';
}
return 'less like this';
}
}
/**
* When we need to append the action and object in one sentence, you can add extra words here.
* E.g.,
* action: 'Signed up'.
* object: 'My blog post'
* When both words need to get appended, we'll add 'on'
* -> do this by returning 'on' in getJoin()
* This string is not added when action and object are in a separete table column, or when the getObject/getURL is empty
*/
getJoin(event) {
if (event.type === 'signup_event' || event.type === 'subscription_event') {
if (event.data.attribution?.title) {
return 'on';
}
}
if (event.type === 'comment_event') {
if (event.data.post) {
return 'on';
}
}
return '';
}
/**
* Clickable object, shown between action and info, or in a separate column in some views
*/
getObject(event) {
if (event.type === 'signup_event' || event.type === 'subscription_event') {
if (event.data.attribution?.title) {
return event.data.attribution.title;
}
}
if (event.type === 'comment_event') {
if (event.data.post) {
return event.data.post.title;
}
}
if (event.type === 'click_event' || event.type === 'feedback_event') {
if (event.data.post) {
return event.data.post.title;
}
}
return '';
}
/**
* Clickable object, shown between action and info, or in a separate column in some views
*/
getSource(event) {
if (event.data?.attribution?.referrer_source) {
return {
name: event.data.attribution.referrer_source,
url: event.data.attribution.referrer_url ?? null
};
}
return null;
}
getInfo(event) {
if (event.type === 'subscription_event') {
let mrrDelta = getNonDecimal(event.data.mrr_delta, event.data.currency);
if (mrrDelta === 0) {
return;
}
const symbol = getSymbol(event.data.currency);
if (event.data.type === 'created') {
const sign = mrrDelta > 0 ? '' : '-';
const tierName = this.membersUtils.hasMultipleTiers ? (event.data.tierName ?? 'MRR') : 'paid';
return `(${tierName} - ${sign}${symbol}${Math.abs(mrrDelta)}/month)`;
}
const sign = mrrDelta > 0 ? '+' : '-';
return `(MRR - ${sign}${symbol}${Math.abs(mrrDelta)})`;
}
if (event.type === 'signup_event' && this.membersUtils.paidMembersEnabled) {
return '(free)';
}
return;
}
getDescription(event) {
if (event.type === 'click_event') {
// Clean URL
try {
return this.utils.cleanTrackedUrl(event.data.link.to, true);
} catch (e) {
// Invalid URL
}
return event.data.link.to;
}
return;
}
/**
* Make the object clickable
*/
getURL(event) {
if (event.type === 'comment_event' || event.type === 'click_event' || event.type === 'feedback_event') {
if (event.data.post) {
return event.data.post.url;
}
}
if (event.type === 'signup_event' || event.type === 'subscription_event') {
if (event.data.attribution && event.data.attribution.url) {
return event.data.attribution.url;
}
}
return;
}
}