mirror of
https://github.com/flarum/core.git
synced 2025-08-06 08:27:42 +02:00
perf(statistics): split timed data into per-model XHR requests (#3601)
* chore: kill off timeset offset from statistics extension * perf: split timed data into per-model requests
This commit is contained in:
@@ -25,11 +25,14 @@ export default class StatisticsWidget extends DashboardWidget {
|
||||
|
||||
chart: any;
|
||||
|
||||
timedData: any;
|
||||
timedData: Record<string, undefined | any> = {};
|
||||
lifetimeData: any;
|
||||
|
||||
loadingLifetime = true;
|
||||
loadingTimed = true;
|
||||
loadingTimed: Record<string, 'unloaded' | 'loading' | 'loaded' | 'fail'> = this.entities.reduce((acc, curr) => {
|
||||
acc[curr] = 'unloaded';
|
||||
return acc;
|
||||
}, {} as Record<string, 'unloaded' | 'loading' | 'loaded' | 'fail'>);
|
||||
|
||||
selectedEntity = 'users';
|
||||
selectedPeriod: undefined | string;
|
||||
@@ -41,7 +44,6 @@ export default class StatisticsWidget extends DashboardWidget {
|
||||
super.oncreate(vnode);
|
||||
|
||||
this.loadLifetimeData();
|
||||
this.loadTimedData();
|
||||
}
|
||||
|
||||
async loadLifetimeData() {
|
||||
@@ -62,39 +64,43 @@ export default class StatisticsWidget extends DashboardWidget {
|
||||
m.redraw();
|
||||
}
|
||||
|
||||
async loadTimedData() {
|
||||
this.loadingTimed = true;
|
||||
async loadTimedData(model: string) {
|
||||
this.loadingTimed[model] = 'loading';
|
||||
m.redraw();
|
||||
|
||||
const data = await app.request({
|
||||
method: 'GET',
|
||||
url: app.forum.attribute('apiUrl') + '/statistics',
|
||||
});
|
||||
try {
|
||||
const data = await app.request({
|
||||
method: 'GET',
|
||||
url: app.forum.attribute('apiUrl') + '/statistics',
|
||||
params: {
|
||||
period: 'timed',
|
||||
model,
|
||||
},
|
||||
});
|
||||
|
||||
this.timedData = data;
|
||||
this.loadingTimed = false;
|
||||
this.timedData[model] = data;
|
||||
this.loadingTimed[model] = 'loaded';
|
||||
|
||||
// Create a Date object which represents the start of the day in the
|
||||
// configured timezone. To do this we convert a UTC time into that timezone,
|
||||
// reset to the first hour of the day, and then convert back into UTC time.
|
||||
// We'll be working with seconds rather than milliseconds throughout too.
|
||||
let todayDate = new Date();
|
||||
todayDate.setTime(todayDate.getTime() + this.timedData.timezoneOffset * 1000);
|
||||
todayDate.setUTCHours(0, 0, 0, 0);
|
||||
todayDate.setTime(todayDate.getTime() - this.timedData.timezoneOffset * 1000);
|
||||
// Create a Date object which represents the start of the day.
|
||||
let todayDate = new Date();
|
||||
todayDate.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
const today = todayDate.getTime() / 1000;
|
||||
const today = todayDate.getTime() / 1000;
|
||||
|
||||
this.periods = {
|
||||
today: { start: today, end: today + 86400, step: 3600 },
|
||||
last_7_days: { start: today - 86400 * 7, end: today, step: 86400 },
|
||||
previous_7_days: { start: today - 86400 * 14, end: today - 86400 * 7, step: 86400 },
|
||||
last_28_days: { start: today - 86400 * 28, end: today, step: 86400 },
|
||||
previous_28_days: { start: today - 86400 * 28 * 2, end: today - 86400 * 28, step: 86400 },
|
||||
last_12_months: { start: today - 86400 * 364, end: today, step: 86400 * 7 },
|
||||
};
|
||||
this.periods = {
|
||||
today: { start: today, end: today + 86400, step: 3600 },
|
||||
last_7_days: { start: today - 86400 * 7, end: today, step: 86400 },
|
||||
previous_7_days: { start: today - 86400 * 14, end: today - 86400 * 7, step: 86400 },
|
||||
last_28_days: { start: today - 86400 * 28, end: today, step: 86400 },
|
||||
previous_28_days: { start: today - 86400 * 28 * 2, end: today - 86400 * 28, step: 86400 },
|
||||
last_12_months: { start: today - 86400 * 364, end: today, step: 86400 * 7 },
|
||||
};
|
||||
|
||||
this.selectedPeriod = 'last_7_days';
|
||||
this.selectedPeriod = 'last_7_days';
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.loadingTimed[model] = 'fail';
|
||||
}
|
||||
|
||||
m.redraw();
|
||||
}
|
||||
@@ -104,7 +110,13 @@ export default class StatisticsWidget extends DashboardWidget {
|
||||
}
|
||||
|
||||
content() {
|
||||
const thisPeriod = this.loadingTimed ? null : this.periods![this.selectedPeriod!];
|
||||
const loadingSelectedEntity = this.loadingTimed[this.selectedEntity] !== 'loaded';
|
||||
|
||||
const thisPeriod = loadingSelectedEntity ? null : this.periods![this.selectedPeriod!];
|
||||
|
||||
if (!this.timedData[this.selectedEntity] && this.loadingTimed[this.selectedEntity] === 'unloaded') {
|
||||
this.loadTimedData(this.selectedEntity);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="StatisticsWidget-table">
|
||||
@@ -112,10 +124,10 @@ export default class StatisticsWidget extends DashboardWidget {
|
||||
<div className="StatisticsWidget-labels">
|
||||
<div className="StatisticsWidget-label">{app.translator.trans('flarum-statistics.admin.statistics.total_label')}</div>
|
||||
<div className="StatisticsWidget-label">
|
||||
{this.loadingTimed ? (
|
||||
{loadingSelectedEntity ? (
|
||||
<LoadingIndicator size="small" display="inline" />
|
||||
) : (
|
||||
<SelectDropdown disabled={this.loadingTimed} buttonClassName="Button Button--text" caretIcon="fas fa-caret-down">
|
||||
<SelectDropdown disabled={loadingSelectedEntity} buttonClassName="Button Button--text" caretIcon="fas fa-caret-down">
|
||||
{Object.keys(this.periods!).map((period) => (
|
||||
<Button
|
||||
key={period}
|
||||
@@ -133,14 +145,14 @@ export default class StatisticsWidget extends DashboardWidget {
|
||||
|
||||
{this.entities.map((entity) => {
|
||||
const totalCount = this.loadingLifetime ? app.translator.trans('flarum-statistics.admin.statistics.loading') : this.getTotalCount(entity);
|
||||
const thisPeriodCount = this.loadingTimed
|
||||
const thisPeriodCount = loadingSelectedEntity
|
||||
? app.translator.trans('flarum-statistics.admin.statistics.loading')
|
||||
: this.getPeriodCount(entity, thisPeriod!);
|
||||
const lastPeriodCount = this.loadingTimed
|
||||
const lastPeriodCount = loadingSelectedEntity
|
||||
? app.translator.trans('flarum-statistics.admin.statistics.loading')
|
||||
: this.getPeriodCount(entity, this.getLastPeriod(thisPeriod!));
|
||||
const periodChange =
|
||||
this.loadingTimed || lastPeriodCount === 0
|
||||
loadingSelectedEntity || lastPeriodCount === 0
|
||||
? 0
|
||||
: (((thisPeriodCount as number) - (lastPeriodCount as number)) / (lastPeriodCount as number)) * 100;
|
||||
|
||||
@@ -154,7 +166,7 @@ export default class StatisticsWidget extends DashboardWidget {
|
||||
{this.loadingLifetime ? <LoadingIndicator display="inline" /> : abbreviateNumber(totalCount as number)}
|
||||
</div>
|
||||
<div className="StatisticsWidget-period" title={thisPeriodCount}>
|
||||
{this.loadingTimed ? <LoadingIndicator display="inline" /> : abbreviateNumber(thisPeriodCount as number)}
|
||||
{loadingSelectedEntity ? <LoadingIndicator display="inline" /> : abbreviateNumber(thisPeriodCount as number)}
|
||||
{periodChange !== 0 && (
|
||||
<>
|
||||
{' '}
|
||||
@@ -170,13 +182,21 @@ export default class StatisticsWidget extends DashboardWidget {
|
||||
})}
|
||||
</div>
|
||||
|
||||
{this.loadingTimed ? (
|
||||
<div className="StatisticsWidget-chart">
|
||||
<LoadingIndicator size="large" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="StatisticsWidget-chart" oncreate={this.drawChart.bind(this)} onupdate={this.drawChart.bind(this)} />
|
||||
)}
|
||||
<>
|
||||
{loadingSelectedEntity ? (
|
||||
<div key="loading" className="StatisticsWidget-chart" data-loading="true">
|
||||
<LoadingIndicator size="large" />
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
key="loaded"
|
||||
className="StatisticsWidget-chart"
|
||||
data-loading="false"
|
||||
oncreate={this.drawChart.bind(this)}
|
||||
onupdate={this.drawChart.bind(this)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -186,7 +206,6 @@ export default class StatisticsWidget extends DashboardWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
const offset = this.timedData.timezoneOffset;
|
||||
const period = this.periods![this.selectedPeriod!];
|
||||
const periodLength = period.end - period.start;
|
||||
const labels = [];
|
||||
@@ -197,19 +216,18 @@ export default class StatisticsWidget extends DashboardWidget {
|
||||
let label;
|
||||
|
||||
if (period.step < 86400) {
|
||||
label = dayjs.unix(i + offset).format('h A');
|
||||
label = dayjs.unix(i).format('h A');
|
||||
} else {
|
||||
label = dayjs.unix(i + offset).format('D MMM');
|
||||
label = dayjs.unix(i).format('D MMM');
|
||||
|
||||
if (period.step > 86400) {
|
||||
label += ' - ' + dayjs.unix(i + offset + period.step - 1).format('D MMM');
|
||||
label += ' - ' + dayjs.unix(i + period.step - 1).format('D MMM');
|
||||
}
|
||||
}
|
||||
|
||||
labels.push(label);
|
||||
|
||||
thisPeriod.push(this.getPeriodCount(this.selectedEntity, { start: i, end: i + period.step }));
|
||||
|
||||
lastPeriod.push(this.getPeriodCount(this.selectedEntity, { start: i - periodLength, end: i - periodLength + period.step }));
|
||||
}
|
||||
|
||||
@@ -219,7 +237,9 @@ export default class StatisticsWidget extends DashboardWidget {
|
||||
datasets,
|
||||
};
|
||||
|
||||
if (!this.chart) {
|
||||
// If the dom element no longer exists, recreate the chart
|
||||
// https://stackoverflow.com/a/2620373/11091039
|
||||
if (!this.chart || !(document.compareDocumentPosition(this.chart.parent) & 16)) {
|
||||
this.chart = new Chart(vnode.dom, {
|
||||
data,
|
||||
type: 'line',
|
||||
|
Reference in New Issue
Block a user