0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-17 23:44:39 -05:00

Improved anchor chart and reloading of charts

refs https://github.com/TryGhost/Team/issues/1510

- Fixes site status not loading (https://github.com/TryGhost/Team/issues/1510) for mocked data because of tasks were already pending (reloadAll now cancels them first)
- Reload no longer required when switching anchor chart type
- Removed paidOptionSelected (fixes bug when swiching)
- Moved did-insert of anchor chart to topmost element
- Renamed chartDisplay values
This commit is contained in:
Simon Backx 2022-04-13 14:52:12 +02:00
parent 2072209d4d
commit 960536dcda
4 changed files with 61 additions and 50 deletions

View file

@ -1,4 +1,4 @@
<section class="gh-dashboard5-section gh-dashboard5-anchor">
<section class="gh-dashboard5-section gh-dashboard5-anchor" {{did-insert this.loadCharts}}>
<article class="gh-dashboard5-box">
<div class="gh-dashboard5-hero {{unless this.hasPaidTiers 'is-solo'}}">
{{#unless this.hasPaidTiers}}
@ -15,17 +15,25 @@
<div class="gh-dashboard5-chart-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/>
{{else}}
<div class="gh-dashboard5-chart-container">
<EmberChart
@type={{this.chartType}}
@data={{this.chartData}}
@options={{this.chartOptions}}
@height={{if this.hasPaidTiers this.chartHeight this.chartHeightSmall}} />
{{#if (eq this.chartType 'bar')}}
<EmberChart
@type="bar"
@data={{this.chartData}}
@options={{this.chartOptions}}
@height={{if this.hasPaidTiers this.chartHeight this.chartHeightSmall}} />
{{else}}
<EmberChart
@type="line"
@data={{this.chartData}}
@options={{this.chartOptions}}
@height={{if this.hasPaidTiers this.chartHeight this.chartHeightSmall}} />
{{/if}}
</div>
{{/if}}
</div>
{{#if this.hasPaidTiers}}
<div class="gh-dashboard5-stats{{unless this.hasPaidTiers ' is-solo'}}" {{did-insert this.loadCharts}}>
<div class="gh-dashboard5-stats{{unless this.hasPaidTiers ' is-solo'}}">
<button class="gh-dashboard5-stats-button {{if this.chartShowingTotal 'is-selected'}}" type="button" {{on "click" (fn this.changeChartDisplay "total")}}>
<Dashboard::v5::Parts::Metric
@label={{gh-pluralize this.totalMembers "Total member" without-count=true}}
@ -34,7 +42,7 @@
@percentage={{this.totalMembersTrend}}
@large={{true}} />
</button>
<button class="gh-dashboard5-stats-button {{if this.chartShowingPaid 'is-selected'}}" type="button" {{on "click" (fn this.changeChartDisplay "paid")}}>
<button class="gh-dashboard5-stats-button {{if this.chartShowingPaid 'is-selected'}}" type="button" {{on "click" (fn this.changeChartDisplay "paid-total")}}>
<Dashboard::v5::Parts::Metric
@label={{gh-pluralize this.paidMembers "Total paid member" without-count=true}}
@value={{format-number this.paidMembers}}
@ -42,7 +50,7 @@
@percentage={{this.paidMembersTrend}}
@large={{true}} />
</button>
<button class="gh-dashboard5-stats-button {{if this.chartShowingMonthly 'is-selected'}}" type="button" {{on "click" (fn this.changeChartDisplay "monthly")}}>
<button class="gh-dashboard5-stats-button {{if this.chartShowingMrr 'is-selected'}}" type="button" {{on "click" (fn this.changeChartDisplay "mrr")}}>
<Dashboard::v5::Parts::Metric
@label="MRR"
@value="${{gh-price-amount this.currentMRR}}"

View file

@ -22,17 +22,16 @@ const DAYS_OPTIONS = [{
const PAID_OPTIONS = [{
name: 'Total Paid Members',
value: 'paid'
value: 'paid-total'
}, {
name: 'Paid Members By Day',
value: 'breakdown'
value: 'paid-breakdown'
}];
export default class Anchor extends Component {
@service dashboardStats;
@service feature;
@tracked chartDisplay = 'total';
@tracked paidOptionSelected = 'paid';
daysOptions = DAYS_OPTIONS;
paidOptions = PAID_OPTIONS;
@ -59,13 +58,15 @@ export default class Anchor extends Component {
@action
changeChartDisplay(type) {
this.chartDisplay = type;
this.loadCharts();
}
@action
onPaidChange(selected) {
this.paidOptionSelected = selected.value;
this.changeChartDisplay(selected.value);
// The graph won't switch correctly from line -> bar
// So we need to recreate it somehow.
// Solution: recreate the DOM by using an #if in hbs
}
@action
@ -78,7 +79,7 @@ export default class Anchor extends Component {
}
get selectedPaidOption() {
return this.paidOptions.find(d => d.value === this.paidOptionSelected);
return this.paidOptions.find(d => d.value === this.chartDisplay) ?? this.paidOptions[0];
}
get chartShowingTotal() {
@ -86,25 +87,21 @@ export default class Anchor extends Component {
}
get chartShowingPaid() {
return (this.chartDisplay === 'paid' || this.chartDisplay === 'breakdown');
return (this.chartDisplay === 'paid-total' || this.chartDisplay === 'paid-breakdown');
}
get chartShowingBreakdown() {
return (this.chartDisplay === 'breakdown');
}
get chartShowingMonthly() {
return (this.chartDisplay === 'monthly');
get chartShowingMrr() {
return (this.chartDisplay === 'mrr');
}
get loading() {
if (this.chartDisplay === 'total') {
return this.dashboardStats.memberCountStats === null;
} else if (this.chartDisplay === 'paid') {
} else if (this.chartDisplay === 'paid-total') {
return this.dashboardStats.memberCountStats === null;
} else if (this.chartDisplay === 'breakdown') {
} else if (this.chartDisplay === 'paid-breakdown') {
return this.dashboardStats.memberCountStats === null;
} else if (this.chartDisplay === 'monthly') {
} else if (this.chartDisplay === 'mrr') {
return this.dashboardStats.mrrStats === null;
}
return true;
@ -118,10 +115,6 @@ export default class Anchor extends Component {
return this.dashboardStats.memberCounts?.paid ?? 0;
}
get paidBreakdown() {
return this.dashboardStats.memberCounts?.breakdown ?? 0;
}
get freeMembers() {
return this.dashboardStats.memberCounts?.free ?? 0;
}
@ -131,7 +124,10 @@ export default class Anchor extends Component {
}
get hasTrends() {
return this.dashboardStats.memberCounts !== null && this.dashboardStats.memberCountsTrend !== null;
return this.dashboardStats.memberCounts !== null
&& this.dashboardStats.memberCountsTrend !== null
&& this.dashboardStats.currentMRR !== null
&& this.dashboardStats.currentMRRTrend !== null;
}
get totalMembersTrend() {
@ -142,10 +138,6 @@ export default class Anchor extends Component {
return this.calculatePercentage(this.dashboardStats.memberCountsTrend.paid, this.dashboardStats.memberCounts.paid);
}
get paidBreakdownTrend() {
return '40%';
}
get freeMembersTrend() {
return this.calculatePercentage(this.dashboardStats.memberCountsTrend.free, this.dashboardStats.memberCounts.free);
}
@ -159,7 +151,7 @@ export default class Anchor extends Component {
}
get chartType() {
if (this.chartDisplay === 'breakdown') {
if (this.chartDisplay === 'paid-breakdown') {
return 'bar';
}
@ -173,7 +165,7 @@ export default class Anchor extends Component {
let newData;
let canceledData;
if (this.chartDisplay === 'breakdown') {
if (this.chartDisplay === 'paid-breakdown') {
stats = this.dashboardStats.filledMemberCountStats;
labels = stats.map(stat => stat.date);
newData = stats.map(stat => stat.paidSubscribed);
@ -204,13 +196,13 @@ export default class Anchor extends Component {
data = stats.map(stat => stat.paid + stat.free + stat.comped);
}
if (this.chartDisplay === 'paid') {
if (this.chartDisplay === 'paid-total') {
stats = this.dashboardStats.filledMemberCountStats;
labels = stats.map(stat => stat.date);
data = stats.map(stat => stat.paid);
}
if (this.chartDisplay === 'monthly') {
if (this.chartDisplay === 'mrr') {
stats = this.dashboardStats.filledMrrStats;
labels = stats.map(stat => stat.date);
data = stats.map(stat => stat.mrr);
@ -249,7 +241,7 @@ export default class Anchor extends Component {
maxNumberOfTicks = 20;
}
if (this.chartDisplay === 'breakdown') {
if (this.chartDisplay === 'paid-breakdown') {
return {
responsive: true,
maintainAspectRatio: false,
@ -386,10 +378,10 @@ export default class Anchor extends Component {
if (this.chartDisplay === 'total') {
return `Total members: ${valueText}`;
}
if (this.chartDisplay === 'paid') {
if (this.chartDisplay === 'paid-total') {
return `Paid members: ${valueText}`;
}
if (this.chartDisplay === 'monthly') {
if (this.chartDisplay === 'mrr') {
return `Monthly revenue (MRR): ${valueText}`;
}
},

View file

@ -86,7 +86,6 @@ export default class ControlPanel extends Component {
const parsed = JSON.parse(savedStatus);
if (parsed) {
this.dashboardMocks.siteStatus = {...this.dashboardMocks.siteStatus, ...parsed};
//this.dashboardStats.loadSiteStatus();
}
}
@ -118,18 +117,16 @@ export default class ControlPanel extends Component {
this.dashboardMocks.updateMockedData(this.state);
}
updateState() {
this.dashboardStats.reloadAll();
this.saveState();
}
// Convenience mappers
get enabled() {
return this.dashboardMocks.enabled;
}
updateState() {
this.dashboardStats.siteStatus = null;
this.dashboardStats.loadSiteStatus();
this.dashboardStats.reloadAll();
this.saveState();
}
set enabled(val) {
this.dashboardMocks.enabled = val;
this.updateState();

View file

@ -591,7 +591,21 @@ export default class DashboardStatsService extends Service {
* For now this is only used when reloading all the graphs after changing the mocked data
* @todo: reload only data that we loaded earlier
*/
reloadAll() {
async reloadAll() {
// Clear all pending tasks (if any)
// Promise.all doesn't work here because they sometimes return undefined
await this._loadSiteStatus.cancelAll();
await this._loadMrrStats.cancelAll();
await this._loadMemberCountStats.cancelAll();
await this._loadLastSeen.cancelAll();
await this._loadPaidMembersByCadence.cancelAll();
await this._loadNewsletterSubscribers.cancelAll();
await this._loadEmailsSent.cancelAll();
await this._loadEmailOpenRateStats.cancelAll();
// Restart tasks
this.loadSiteStatus();
this.loadMrrStats();
this.loadMemberCountStats();
this.loadLastSeen();