mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-28 22:43:30 +03:00
Added initial event timeline to dashboard (#1840)
refs https://github.com/TryGhost/Team/issues/469 * Updated stats service to fetch event timeline * Updated dashboard controller to load events * Added currency utils * Added GhEventTimeline component * Updated dashboard to render GhEventTimeline
This commit is contained in:
parent
f9bafa740a
commit
c56ba0c71e
14
ghost/admin/app/components/gh-event-timeline.hbs
Normal file
14
ghost/admin/app/components/gh-event-timeline.hbs
Normal file
@ -0,0 +1,14 @@
|
||||
<div class="gh-event-timeline">
|
||||
<ul class="gh-dashboard-activity-feed">
|
||||
{{#each this.parsedEvents as |event|}}
|
||||
<li>
|
||||
<div class="activity">
|
||||
<div>
|
||||
<span class="member">{{event.subject}}</span> {{event.action}} {{event.object}} <span class="highlight">{{event.info}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="time">{{event.timestamp}}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
98
ghost/admin/app/components/gh-event-timeline.js
Normal file
98
ghost/admin/app/components/gh-event-timeline.js
Normal file
@ -0,0 +1,98 @@
|
||||
import Component from '@glimmer/component';
|
||||
import moment from 'moment';
|
||||
import {getNonDecimal, getSymbol} from 'ghost-admin/utils/currency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class EventTimeline extends Component {
|
||||
@tracked
|
||||
parsedEvents = null;
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.parseEvents(this.args.events);
|
||||
}
|
||||
|
||||
getIcon(event) {
|
||||
return event.type;
|
||||
}
|
||||
|
||||
getAction(event) {
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
getObject(event) {
|
||||
if (event.type === 'login_event') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (event.type === 'payment_event') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (event.type === 'newsletter_event') {
|
||||
return 'emails';
|
||||
}
|
||||
|
||||
if (event.type === 'subscription_event') {
|
||||
return 'their subscription';
|
||||
}
|
||||
}
|
||||
|
||||
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}${mrrDelta})`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
parseEvents(events) {
|
||||
this.parsedEvents = events.map((event) => {
|
||||
let subject = event.data.member.name;
|
||||
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 {
|
||||
icon,
|
||||
subject,
|
||||
action,
|
||||
object,
|
||||
info,
|
||||
timestamp
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
@ -1,7 +1,32 @@
|
||||
import Controller from '@ember/controller';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class DashboardController extends Controller {
|
||||
@service feature;
|
||||
@service session;
|
||||
@service membersStats;
|
||||
|
||||
@tracked
|
||||
events = {
|
||||
data: null,
|
||||
error: null,
|
||||
loading: false
|
||||
};
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.loadEvents();
|
||||
}
|
||||
|
||||
loadEvents() {
|
||||
this.events.loading = true;
|
||||
this.membersStats.fetchTimeline().then(({events}) => {
|
||||
this.events.data = events;
|
||||
this.events.loading = false;
|
||||
}, (error) => {
|
||||
this.events.error = error;
|
||||
this.events.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ export default class MembersStatsService extends Service {
|
||||
|
||||
@tracked days = '30';
|
||||
@tracked stats = null;
|
||||
@tracked events = null;
|
||||
|
||||
fetch() {
|
||||
let daysChanged = this._lastFetchedDays !== this.days;
|
||||
@ -27,6 +28,20 @@ export default class MembersStatsService extends Service {
|
||||
return this._fetchTask.perform();
|
||||
}
|
||||
|
||||
fetchTimeline() {
|
||||
let staleData = this._lastFetchedTimeline && this._lastFetchedTimeline - new Date() > 1 * 60 * 1000;
|
||||
|
||||
if (this._fetchTimelineTask.isRunning) {
|
||||
return this._fetchTask.last;
|
||||
}
|
||||
|
||||
if (this.events && !this._forceRefresh && !staleData) {
|
||||
return Promise.resolve(this.events);
|
||||
}
|
||||
|
||||
return this._fetchTimelineTask.perform();
|
||||
}
|
||||
|
||||
invalidate() {
|
||||
this._forceRefresh = true;
|
||||
}
|
||||
@ -44,4 +59,13 @@ export default class MembersStatsService extends Service {
|
||||
this.stats = stats;
|
||||
return stats;
|
||||
}
|
||||
|
||||
@task
|
||||
*_fetchTimelineTask() {
|
||||
this._lastFetchedTimeline = new Date();
|
||||
let eventsUrl = this.ghostPaths.url.api('members/events');
|
||||
let events = yield this.ajax.request(eventsUrl);
|
||||
this.events = events;
|
||||
return events;
|
||||
}
|
||||
}
|
||||
|
@ -140,32 +140,18 @@
|
||||
<div class="gh-dashboard-box grey activity-feed">
|
||||
<h4 class="gh-dashboard-header">Activity feed</h4>
|
||||
<div class="content">
|
||||
<ul class="gh-dashboard-activity-feed">
|
||||
<li>
|
||||
<div class="activity">
|
||||
<div>
|
||||
<span class="member">Kadin Levin</span> subscribed <span class="highlight">(MRR +$38)</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="time">29 mins ago</span>
|
||||
</li>
|
||||
<li>
|
||||
<div class="activity">
|
||||
<div>
|
||||
<span class="member">Terry Stanton</span> subscribed <span class="highlight">(MRR +$38)</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="time">1 day ago</span>
|
||||
</li>
|
||||
<li>
|
||||
<div class="activity">
|
||||
<div>
|
||||
<span class="member">Angel Rhiel Madsen</span> subscribed <span class="highlight">(MRR +$38)</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="time">3 days ago</span>
|
||||
</li>
|
||||
</ul>
|
||||
{{#if this.events.loading}}
|
||||
Loading...
|
||||
{{else}}
|
||||
{{#if this.events.error}}
|
||||
<p class="error">
|
||||
There was an error loading events
|
||||
<code>{{this.events.error.message}}</code>
|
||||
</p>
|
||||
{{else}}
|
||||
<GhEventTimeline @events={{this.events.data}}/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-box">
|
||||
|
28
ghost/admin/app/utils/currency.js
Normal file
28
ghost/admin/app/utils/currency.js
Normal file
@ -0,0 +1,28 @@
|
||||
export function getSymbol(currency) {
|
||||
switch (currency) {
|
||||
case 'usd':
|
||||
case 'aud':
|
||||
case 'cad':
|
||||
return '$';
|
||||
case 'eur':
|
||||
return '€';
|
||||
case 'gbp':
|
||||
return '£';
|
||||
case 'inr':
|
||||
return '₹';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getNonDecimal(amount, currency) {
|
||||
switch (currency) {
|
||||
case 'usd':
|
||||
case 'aud':
|
||||
case 'cad':
|
||||
case 'eur':
|
||||
case 'gbp':
|
||||
case 'inr':
|
||||
return amount / 100;
|
||||
}
|
||||
return null;
|
||||
}
|
Loading…
Reference in New Issue
Block a user