0
Fork 0
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:
Kevin Ansfield 2022-01-25 11:50:10 +00:00
parent 5da8ee3c76
commit acbc4233a8
5 changed files with 143 additions and 139 deletions

View file

@ -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">

View file

@ -1 +0,0 @@
{{yield this.parsedEvents}}

View file

@ -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;
}
}

View file

@ -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}}

View 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;
}