mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-24 23:48:13 -05:00
Humanized event details on members activity screen
refs https://github.com/TryGhost/Team/issues/1277 - moved event parsing from a component to a helper - keeps code as a plain function - allows for per-event parsing which helps with rendering as we're not rebuilding/re-rendering a whole array each time the events data changes when a new page is loaded - updated to include full member and email objects (will be used later for email preview popup) - updated members-activity table row component to use the event parser helper for better event details
This commit is contained in:
parent
5da8ee3c76
commit
acbc4233a8
5 changed files with 143 additions and 139 deletions
|
@ -17,21 +17,25 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#unless (or eventsFetcher.isLoading eventsFetcher.isError)}}
|
{{#unless (or eventsFetcher.isLoading eventsFetcher.isError)}}
|
||||||
<GhMemberActivityEventParser @events={{eventsFetcher.data}} as |parsedEvents|>
|
|
||||||
<div class="gh-event-timeline">
|
<div class="gh-event-timeline">
|
||||||
{{#if parsedEvents}}
|
{{#if eventsFetcher.data}}
|
||||||
<ul class="gh-dashboard-activity-feed">
|
<ul class="gh-dashboard-activity-feed">
|
||||||
{{#each parsedEvents as |event|}}
|
{{#each eventsFetcher.data as |event|}}
|
||||||
|
{{#let (parse-member-event event) as |parsedEvent|}}
|
||||||
<li data-test-dashboard-member-activity-item>
|
<li data-test-dashboard-member-activity-item>
|
||||||
<LinkTo class="member-details" @route="member" @model="{{event.member_id}}">
|
<LinkTo class="member-details" @route="member" @model="{{parsedEvent.memberId}}">
|
||||||
<div class="activity">
|
<div class="activity">
|
||||||
<div>
|
<div>
|
||||||
<span class="member">{{event.subject}}</span> {{event.action}} {{event.object}} <span class="highlight">{{event.info}}</span>
|
<span class="member">{{parsedEvent.subject}}</span>
|
||||||
|
{{parsedEvent.action}}
|
||||||
|
{{parsedEvent.object}}
|
||||||
|
<span class="highlight">{{parsedEvent.info}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</LinkTo>
|
</LinkTo>
|
||||||
<span class="time">{{event.timestamp}}</span>
|
<span class="time">{{moment-from-now parsedEvent.timestamp}}</span>
|
||||||
</li>
|
</li>
|
||||||
|
{{/let}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
@ -41,7 +45,6 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</GhMemberActivityEventParser>
|
|
||||||
|
|
||||||
{{#if (feature "membersActivityFeed")}}
|
{{#if (feature "membersActivityFeed")}}
|
||||||
<div class="gh-dashboard-top-members-footer">
|
<div class="gh-dashboard-top-members-footer">
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
{{yield this.parsedEvents}}
|
|
|
@ -1,109 +0,0 @@
|
||||||
import Component from '@glimmer/component';
|
|
||||||
import moment from 'moment';
|
|
||||||
import {getNonDecimal, getSymbol} from 'ghost-admin/utils/currency';
|
|
||||||
|
|
||||||
export default class EventTimeline extends Component {
|
|
||||||
get parsedEvents() {
|
|
||||||
if (!this.args.events) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.args.events.map((event) => {
|
|
||||||
let subject = event.data.member.name || event.data.member.email;
|
|
||||||
let icon = this.getIcon(event);
|
|
||||||
let action = this.getAction(event);
|
|
||||||
let object = this.getObject(event);
|
|
||||||
let info = this.getInfo(event);
|
|
||||||
let timestamp = moment(event.data.created_at).fromNow();
|
|
||||||
|
|
||||||
return {
|
|
||||||
member_id: event.data.member_id,
|
|
||||||
icon,
|
|
||||||
subject,
|
|
||||||
action,
|
|
||||||
object,
|
|
||||||
info,
|
|
||||||
timestamp
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getIcon(event) {
|
|
||||||
return event.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
getAction(event) {
|
|
||||||
if (event.type === 'signup_event') {
|
|
||||||
return 'signed up';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type === 'login_event') {
|
|
||||||
return 'logged in';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type === 'payment_event') {
|
|
||||||
return 'made a payment';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type === 'newsletter_event') {
|
|
||||||
if (event.data.subscribed) {
|
|
||||||
return 'subscribed to';
|
|
||||||
} else {
|
|
||||||
return 'unsubscribed from';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type === 'subscription_event') {
|
|
||||||
if (event.data.from_plan === null) {
|
|
||||||
return 'started';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.data.to_plan === null) {
|
|
||||||
return 'cancelled';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'changed';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type === 'email_opened_event') {
|
|
||||||
return 'opened';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type === 'email_delivered_event') {
|
|
||||||
return 'received';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type === 'email_failed_event') {
|
|
||||||
return 'failed to receive';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getObject(event) {
|
|
||||||
if (event.type === 'newsletter_event') {
|
|
||||||
return 'emails';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type === 'subscription_event') {
|
|
||||||
return 'their subscription';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type.match?.(/^email_/)) {
|
|
||||||
return 'an email';
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
getInfo(event) {
|
|
||||||
if (event.type === 'subscription_event') {
|
|
||||||
let mrrDelta = getNonDecimal(event.data.mrr_delta, event.data.currency);
|
|
||||||
if (mrrDelta === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let sign = mrrDelta > 0 ? '+' : '-';
|
|
||||||
let symbol = getSymbol(event.data.currency);
|
|
||||||
return `(MRR ${sign}${symbol}${Math.abs(mrrDelta)})`;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,17 @@
|
||||||
<tr>
|
{{#let (parse-member-event @event) as |event|}}
|
||||||
|
<tr>
|
||||||
<div class="gh-list-data">
|
<div class="gh-list-data">
|
||||||
<LinkTo @route="member" @model={{@event.data.member.id}}>
|
<LinkTo @route="member" @model={{event.memberId}}>
|
||||||
<strong>{{@event.data.member.name}}</strong>
|
<strong>{{event.member.name}}</strong>
|
||||||
<br>
|
<br>
|
||||||
{{@event.data.member.email}}
|
{{event.member.email}}
|
||||||
</LinkTo>
|
</LinkTo>
|
||||||
</div>
|
</div>
|
||||||
<div class="gh-list-data">{{@event.type}}</div>
|
<div class="gh-list-data">
|
||||||
<div class="gh-list-data">{{moment-format @event.data.created_at "D MMM YYYY HH:MM"}}</div>
|
{{event.action}}
|
||||||
</tr>
|
{{event.object}}
|
||||||
|
<span class="highlight">{{event.info}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="gh-list-data">{{moment-format event.timestamp "D MMM YYYY HH:MM"}}</div>
|
||||||
|
</tr>
|
||||||
|
{{/let}}
|
105
ghost/admin/app/helpers/parse-member-event.js
Normal file
105
ghost/admin/app/helpers/parse-member-event.js
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import moment from 'moment';
|
||||||
|
import {getNonDecimal, getSymbol} from 'ghost-admin/utils/currency';
|
||||||
|
|
||||||
|
export default function parseMemberEvent(event) {
|
||||||
|
let subject = event.data.member.name || event.data.member.email;
|
||||||
|
let icon = getIcon(event);
|
||||||
|
let action = getAction(event);
|
||||||
|
let object = getObject(event);
|
||||||
|
let info = getInfo(event);
|
||||||
|
let timestamp = moment(event.data.created_at);
|
||||||
|
|
||||||
|
return {
|
||||||
|
memberId: event.data.member_id,
|
||||||
|
member: event.data.member,
|
||||||
|
emailId: event.data.email_id,
|
||||||
|
email: event.data.email,
|
||||||
|
icon,
|
||||||
|
subject,
|
||||||
|
action,
|
||||||
|
object,
|
||||||
|
info,
|
||||||
|
timestamp
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* internal helper functions */
|
||||||
|
|
||||||
|
function getIcon(event) {
|
||||||
|
return event.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAction(event) {
|
||||||
|
if (event.type === 'signup_event') {
|
||||||
|
return 'signed up';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'login_event') {
|
||||||
|
return 'logged in';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'payment_event') {
|
||||||
|
return 'made a payment';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'newsletter_event') {
|
||||||
|
if (event.data.subscribed) {
|
||||||
|
return 'subscribed to';
|
||||||
|
} else {
|
||||||
|
return 'unsubscribed from';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'subscription_event') {
|
||||||
|
if (event.data.from_plan === null) {
|
||||||
|
return 'started';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.data.to_plan === null) {
|
||||||
|
return 'cancelled';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'changed';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'email_opened_event') {
|
||||||
|
return 'opened';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'email_delivered_event') {
|
||||||
|
return 'received';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'email_failed_event') {
|
||||||
|
return 'failed to receive';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getObject(event) {
|
||||||
|
if (event.type === 'newsletter_event') {
|
||||||
|
return 'emails';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'subscription_event') {
|
||||||
|
return 'their subscription';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type.match?.(/^email_/)) {
|
||||||
|
return 'an email';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInfo(event) {
|
||||||
|
if (event.type === 'subscription_event') {
|
||||||
|
let mrrDelta = getNonDecimal(event.data.mrr_delta, event.data.currency);
|
||||||
|
if (mrrDelta === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let sign = mrrDelta > 0 ? '+' : '-';
|
||||||
|
let symbol = getSymbol(event.data.currency);
|
||||||
|
return `(MRR ${sign}${symbol}${Math.abs(mrrDelta)})`;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue