mirror of
https://github.com/discourse/discourse.git
synced 2025-05-30 00:48:05 +08:00
new dashboard quality pass (code, tests and UI)
This commit is contained in:
@ -1,22 +1,24 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import round from "discourse/lib/round";
|
||||
import { fillMissingDates } from 'discourse/lib/utilities';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { fillMissingDates } from "discourse/lib/utilities";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import { number } from 'discourse/lib/formatter';
|
||||
|
||||
const Report = Discourse.Model.extend({
|
||||
average: false,
|
||||
percent: false,
|
||||
higher_is_better: true,
|
||||
|
||||
@computed("type", "start_date", "end_date")
|
||||
reportUrl(type, start_date, end_date) {
|
||||
start_date = moment(start_date).locale('en').format("YYYY-MM-DD");
|
||||
end_date = moment(end_date).locale('en').format("YYYY-MM-DD");
|
||||
start_date = moment(start_date).locale("en").format("YYYY-MM-DD");
|
||||
end_date = moment(end_date).locale("en").format("YYYY-MM-DD");
|
||||
return Discourse.getURL(`/admin/reports/${type}?start_date=${start_date}&end_date=${end_date}`);
|
||||
},
|
||||
|
||||
valueAt(numDaysAgo) {
|
||||
if (this.data) {
|
||||
const wantedDate = moment().subtract(numDaysAgo, "days").locale('en').format("YYYY-MM-DD");
|
||||
const wantedDate = moment().subtract(numDaysAgo, "days").locale("en").format("YYYY-MM-DD");
|
||||
const item = this.data.find(d => d.x === wantedDate);
|
||||
if (item) {
|
||||
return item.y;
|
||||
@ -29,7 +31,7 @@ const Report = Discourse.Model.extend({
|
||||
if (this.data) {
|
||||
const earliestDate = moment().subtract(endDaysAgo, "days").startOf("day");
|
||||
const latestDate = moment().subtract(startDaysAgo, "days").startOf("day");
|
||||
var d, sum = 0, count = 0;
|
||||
let d, sum = 0, count = 0;
|
||||
_.each(this.data, datum => {
|
||||
d = moment(datum.x);
|
||||
if (d >= earliestDate && d <= latestDate) {
|
||||
@ -46,6 +48,7 @@ const Report = Discourse.Model.extend({
|
||||
yesterdayCount: function() { return this.valueAt(1); }.property("data", "average"),
|
||||
sevenDaysAgoCount: function() { return this.valueAt(7); }.property("data", "average"),
|
||||
thirtyDaysAgoCount: function() { return this.valueAt(30); }.property("data", "average"),
|
||||
|
||||
lastSevenDaysCount: function() {
|
||||
return this.averageCount(7, this.valueFor(1, 7));
|
||||
}.property("data", "average"),
|
||||
@ -57,50 +60,22 @@ const Report = Discourse.Model.extend({
|
||||
return this.get("average") ? value / count : value;
|
||||
},
|
||||
|
||||
@computed('yesterdayCount')
|
||||
@computed("yesterdayCount")
|
||||
yesterdayTrend(yesterdayCount) {
|
||||
const yesterdayVal = yesterdayCount;
|
||||
const twoDaysAgoVal = this.valueAt(2);
|
||||
const change = ((yesterdayVal - twoDaysAgoVal) / yesterdayVal) * 100;
|
||||
|
||||
if (change > 50) {
|
||||
return "high-trending-up";
|
||||
} else if (change > 0) {
|
||||
return "trending-up";
|
||||
} else if (change === 0) {
|
||||
return "no-change";
|
||||
} else if (change < -50) {
|
||||
return "high-trending-down";
|
||||
} else if (change < 0) {
|
||||
return "trending-down";
|
||||
}
|
||||
return this._computeTrend(this.valueAt(2), yesterdayCount);
|
||||
},
|
||||
|
||||
@computed('lastSevenDaysCount')
|
||||
sevenDayTrend(lastSevenDaysCount) {
|
||||
const currentPeriod = lastSevenDaysCount;
|
||||
const prevPeriod = this.valueFor(8, 14);
|
||||
const change = ((currentPeriod - prevPeriod) / prevPeriod) * 100;
|
||||
|
||||
if (change > 50) {
|
||||
return "high-trending-up";
|
||||
} else if (change > 0) {
|
||||
return "trending-up";
|
||||
} else if (change === 0) {
|
||||
return "no-change";
|
||||
} else if (change < -50) {
|
||||
return "high-trending-down";
|
||||
} else if (change < 0) {
|
||||
return "trending-down";
|
||||
}
|
||||
@computed("lastSevenDaysCount")
|
||||
sevenDaysTrend(lastSevenDaysCount) {
|
||||
return this._computeTrend(this.valueFor(8, 14), lastSevenDaysCount);
|
||||
},
|
||||
|
||||
@computed('data')
|
||||
@computed("data")
|
||||
currentTotal(data){
|
||||
return _.reduce(data, (cur, pair) => cur + pair.y, 0);
|
||||
},
|
||||
|
||||
@computed('data', 'currentTotal')
|
||||
@computed("data", "currentTotal")
|
||||
currentAverage(data, total) {
|
||||
return Ember.makeArray(data).length === 0 ? 0 : parseFloat((total / parseFloat(data.length)).toFixed(1));
|
||||
},
|
||||
@ -121,43 +96,18 @@ const Report = Discourse.Model.extend({
|
||||
}
|
||||
},
|
||||
|
||||
@computed('prev_period', 'currentTotal', 'currentAverage')
|
||||
@computed("prev_period", "currentTotal", "currentAverage")
|
||||
trend(prev, currentTotal, currentAverage) {
|
||||
const total = this.get('average') ? currentAverage : currentTotal;
|
||||
const change = ((total - prev) / total) * 100;
|
||||
|
||||
if (change > 50) {
|
||||
return "high-trending-up";
|
||||
} else if (change > 0) {
|
||||
return "trending-up";
|
||||
} else if (change === 0) {
|
||||
return "no-change";
|
||||
} else if (change < -50) {
|
||||
return "high-trending-down";
|
||||
} else if (change < 0) {
|
||||
return "trending-down";
|
||||
}
|
||||
const total = this.get("average") ? currentAverage : currentTotal;
|
||||
return this._computeTrend(prev, total);
|
||||
},
|
||||
|
||||
@computed('prev30Days', 'lastThirtyDaysCount')
|
||||
thirtyDayTrend(prev30Days, lastThirtyDaysCount) {
|
||||
const currentPeriod = lastThirtyDaysCount;
|
||||
const change = ((currentPeriod - prev30Days) / currentPeriod) * 100;
|
||||
|
||||
if (change > 50) {
|
||||
return "high-trending-up";
|
||||
} else if (change > 0) {
|
||||
return "trending-up";
|
||||
} else if (change === 0) {
|
||||
return "no-change";
|
||||
} else if (change < -50) {
|
||||
return "high-trending-down";
|
||||
} else if (change < 0) {
|
||||
return "trending-down";
|
||||
}
|
||||
@computed("prev30Days", "lastThirtyDaysCount")
|
||||
thirtyDaysTrend(prev30Days, lastThirtyDaysCount) {
|
||||
return this._computeTrend(prev30Days, lastThirtyDaysCount);
|
||||
},
|
||||
|
||||
@computed('type')
|
||||
@computed("type")
|
||||
icon(type) {
|
||||
if (type.indexOf("message") > -1) {
|
||||
return "envelope";
|
||||
@ -170,7 +120,7 @@ const Report = Discourse.Model.extend({
|
||||
}
|
||||
},
|
||||
|
||||
@computed('type')
|
||||
@computed("type")
|
||||
method(type) {
|
||||
if (type === "time_to_first_response") {
|
||||
return "average";
|
||||
@ -180,75 +130,98 @@ const Report = Discourse.Model.extend({
|
||||
},
|
||||
|
||||
percentChangeString(val1, val2) {
|
||||
const val = ((val1 - val2) / val2) * 100;
|
||||
if (isNaN(val) || !isFinite(val)) {
|
||||
const change = this._computeChange(val1, val2);
|
||||
|
||||
if (isNaN(change) || !isFinite(change)) {
|
||||
return null;
|
||||
} else if (val > 0) {
|
||||
return "+" + val.toFixed(0) + "%";
|
||||
} else if (change > 0) {
|
||||
return "+" + change.toFixed(0) + "%";
|
||||
} else {
|
||||
return val.toFixed(0) + "%";
|
||||
return change.toFixed(0) + "%";
|
||||
}
|
||||
},
|
||||
|
||||
@computed('prev_period', 'currentTotal', 'currentAverage')
|
||||
@computed("prev_period", "currentTotal", "currentAverage")
|
||||
trendTitle(prev, currentTotal, currentAverage) {
|
||||
let current = this.get('average') ? currentAverage : currentTotal;
|
||||
let percent = this.percentChangeString(current, prev);
|
||||
let current = this.get("average") ? currentAverage : currentTotal;
|
||||
let percent = this.percentChangeString(prev, current);
|
||||
|
||||
if (this.get('average')) {
|
||||
if (this.get("average")) {
|
||||
prev = prev ? prev.toFixed(1) : "0";
|
||||
if (this.get('percent')) {
|
||||
current += '%';
|
||||
prev += '%';
|
||||
if (this.get("percent")) {
|
||||
current += "%";
|
||||
prev += "%";
|
||||
}
|
||||
} else {
|
||||
prev = number(prev);
|
||||
current = number(current);
|
||||
}
|
||||
|
||||
return I18n.t('admin.dashboard.reports.trend_title', {percent: percent, prev: prev, current: current});
|
||||
return I18n.t("admin.dashboard.reports.trend_title", {percent, prev, current});
|
||||
},
|
||||
|
||||
changeTitle(val1, val2, prevPeriodString) {
|
||||
const percentChange = this.percentChangeString(val1, val2);
|
||||
var title = "";
|
||||
if (percentChange) { title += percentChange + " change. "; }
|
||||
title += "Was " + val2 + " " + prevPeriodString + ".";
|
||||
changeTitle(valAtT1, valAtT2, prevPeriodString) {
|
||||
const change = this.percentChangeString(valAtT1, valAtT2);
|
||||
let title = "";
|
||||
if (change) { title += `${change} change. `; }
|
||||
title += `Was ${number(valAtT1)} ${prevPeriodString}.`;
|
||||
return title;
|
||||
},
|
||||
|
||||
@computed('yesterdayCount')
|
||||
@computed("yesterdayCount")
|
||||
yesterdayCountTitle(yesterdayCount) {
|
||||
return this.changeTitle(yesterdayCount, this.valueAt(2), "two days ago");
|
||||
return this.changeTitle(this.valueAt(2), yesterdayCount, "two days ago");
|
||||
},
|
||||
|
||||
@computed('lastSevenDaysCount')
|
||||
sevenDayCountTitle(lastSevenDaysCount) {
|
||||
return this.changeTitle(lastSevenDaysCount, this.valueFor(8, 14), "two weeks ago");
|
||||
@computed("lastSevenDaysCount")
|
||||
sevenDaysCountTitle(lastSevenDaysCount) {
|
||||
return this.changeTitle(this.valueFor(8, 14), lastSevenDaysCount, "two weeks ago");
|
||||
},
|
||||
|
||||
@computed('prev30Days', 'lastThirtyDaysCount')
|
||||
thirtyDayCountTitle(prev30Days, lastThirtyDaysCount) {
|
||||
return this.changeTitle(lastThirtyDaysCount, prev30Days, "in the previous 30 day period");
|
||||
@computed("prev30Days", "lastThirtyDaysCount")
|
||||
thirtyDaysCountTitle(prev30Days, lastThirtyDaysCount) {
|
||||
return this.changeTitle(prev30Days, lastThirtyDaysCount, "in the previous 30 day period");
|
||||
},
|
||||
|
||||
@computed('data')
|
||||
@computed("data")
|
||||
sortedData(data) {
|
||||
return this.get('xAxisIsDate') ? data.toArray().reverse() : data.toArray();
|
||||
return this.get("xAxisIsDate") ? data.toArray().reverse() : data.toArray();
|
||||
},
|
||||
|
||||
@computed('data')
|
||||
@computed("data")
|
||||
xAxisIsDate() {
|
||||
if (!this.data[0]) return false;
|
||||
return this.data && this.data[0].x.match(/\d{4}-\d{1,2}-\d{1,2}/);
|
||||
}
|
||||
},
|
||||
|
||||
_computeChange(valAtT1, valAtT2) {
|
||||
return ((valAtT2 - valAtT1) / valAtT1) * 100;
|
||||
},
|
||||
|
||||
_computeTrend(valAtT1, valAtT2) {
|
||||
const change = this._computeChange(valAtT1, valAtT2);
|
||||
const higherIsBetter = this.get("higher_is_better");
|
||||
|
||||
if (change > 50) {
|
||||
return higherIsBetter ? "high-trending-up" : "high-trending-down";
|
||||
} else if (change > 0) {
|
||||
return higherIsBetter ? "trending-up" : "trending-down";
|
||||
} else if (change === 0) {
|
||||
return "no-change";
|
||||
} else if (change < -50) {
|
||||
return higherIsBetter ? "high-trending-down" : "high-trending-up";
|
||||
} else if (change < 0) {
|
||||
return higherIsBetter ? "trending-down" : "trending-up";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Report.reopenClass({
|
||||
|
||||
fillMissingDates(report) {
|
||||
if (_.isArray(report.data)) {
|
||||
|
||||
const startDateFormatted = moment.utc(report.start_date).locale('en').format('YYYY-MM-DD');
|
||||
const endDateFormatted = moment.utc(report.end_date).locale('en').format('YYYY-MM-DD');
|
||||
const startDateFormatted = moment.utc(report.start_date).locale("en").format("YYYY-MM-DD");
|
||||
const endDateFormatted = moment.utc(report.end_date).locale("en").format("YYYY-MM-DD");
|
||||
report.data = fillMissingDates(report.data, startDateFormatted, endDateFormatted);
|
||||
}
|
||||
},
|
||||
@ -272,7 +245,7 @@ Report.reopenClass({
|
||||
// TODO: fillMissingDates if xaxis is date
|
||||
const related = Report.create({ type: json.report.related_report.type });
|
||||
related.setProperties(json.report.related_report);
|
||||
model.set('relatedReport', related);
|
||||
model.set("relatedReport", related);
|
||||
}
|
||||
|
||||
return model;
|
||||
|
Reference in New Issue
Block a user