FEATURE: part 2 of dashboard improvements
- moderation tab - sorting/pagination - improved third party reports support - trending charts - better perf - many fixes - refactoring - new reports Co-Authored-By: Simon Cossar <scossar@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import { setting } from "discourse/lib/computed";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import AdminDashboardNext from "admin/models/admin-dashboard-next";
|
||||
import Report from "admin/models/report";
|
||||
import PeriodComputationMixin from "admin/mixins/period-computation";
|
||||
|
||||
export default Ember.Controller.extend(PeriodComputationMixin, {
|
||||
isLoading: false,
|
||||
dashboardFetchedAt: null,
|
||||
exceptionController: Ember.inject.controller("exception"),
|
||||
diskSpace: Ember.computed.alias("model.attributes.disk_space"),
|
||||
logSearchQueriesEnabled: setting("log_search_queries"),
|
||||
lastBackupTakenAt: Ember.computed.alias(
|
||||
"model.attributes.last_backup_taken_at"
|
||||
),
|
||||
shouldDisplayDurability: Ember.computed.and("lastBackupTakenAt", "diskSpace"),
|
||||
|
||||
@computed
|
||||
topReferredTopicsTopions() {
|
||||
return { table: { total: false, limit: 8 } };
|
||||
},
|
||||
|
||||
@computed
|
||||
trendingSearchOptions() {
|
||||
return { table: { total: false, limit: 8 } };
|
||||
},
|
||||
|
||||
@computed("reports.[]")
|
||||
topReferredTopicsReport(reports) {
|
||||
return reports.find(x => x.type === "top_referred_topics");
|
||||
},
|
||||
|
||||
@computed("reports.[]")
|
||||
trendingSearchReport(reports) {
|
||||
return reports.find(x => x.type === "trending_search");
|
||||
},
|
||||
|
||||
@computed("reports.[]")
|
||||
usersByTypeReport(reports) {
|
||||
return reports.find(x => x.type === "users_by_type");
|
||||
},
|
||||
|
||||
@computed("reports.[]")
|
||||
usersByTrustLevelReport(reports) {
|
||||
return reports.find(x => x.type === "users_by_trust_level");
|
||||
},
|
||||
|
||||
@computed("reports.[]")
|
||||
activityMetricsReports(reports) {
|
||||
return reports.filter(report => {
|
||||
return [
|
||||
"page_view_total_reqs",
|
||||
"visits",
|
||||
"time_to_first_response",
|
||||
"likes",
|
||||
"flags",
|
||||
"user_to_user_private_messages_with_replies"
|
||||
].includes(report.type);
|
||||
});
|
||||
},
|
||||
|
||||
fetchDashboard() {
|
||||
if (this.get("isLoading")) return;
|
||||
|
||||
if (
|
||||
!this.get("dashboardFetchedAt") ||
|
||||
moment()
|
||||
.subtract(30, "minutes")
|
||||
.toDate() > this.get("dashboardFetchedAt")
|
||||
) {
|
||||
this.set("isLoading", true);
|
||||
|
||||
AdminDashboardNext.fetchGeneral()
|
||||
.then(adminDashboardNextModel => {
|
||||
this.setProperties({
|
||||
dashboardFetchedAt: new Date(),
|
||||
model: adminDashboardNextModel,
|
||||
reports: adminDashboardNextModel.reports.map(x => Report.create(x))
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
this.get("exceptionController").set("thrown", e.jqXHR);
|
||||
this.replaceRoute("exception");
|
||||
})
|
||||
.finally(() => this.set("isLoading", false));
|
||||
}
|
||||
},
|
||||
|
||||
@computed("model.attributes.updated_at")
|
||||
updatedTimestamp(updatedAt) {
|
||||
return moment(updatedAt).format("LLL");
|
||||
},
|
||||
|
||||
@computed("lastBackupTakenAt")
|
||||
backupTimestamp(lastBackupTakenAt) {
|
||||
return moment(lastBackupTakenAt).format("LLL");
|
||||
},
|
||||
|
||||
_reportsForPeriodURL(period) {
|
||||
return Discourse.getURL(`/admin/dashboard/general?period=${period}`);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import Report from "admin/models/report";
|
||||
import AdminDashboardNext from "admin/models/admin-dashboard-next";
|
||||
import PeriodComputationMixin from "admin/mixins/period-computation";
|
||||
|
||||
export default Ember.Controller.extend(PeriodComputationMixin, {
|
||||
isLoading: false,
|
||||
dashboardFetchedAt: null,
|
||||
exceptionController: Ember.inject.controller("exception"),
|
||||
|
||||
@computed
|
||||
flagsStatusOptions() {
|
||||
return {
|
||||
table: {
|
||||
total: false,
|
||||
perPage: 10
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@computed("reports.[]")
|
||||
flagsStatusReport(reports) {
|
||||
return reports.find(x => x.type === "flags_status");
|
||||
},
|
||||
|
||||
@computed("reports.[]")
|
||||
postEditsReport(reports) {
|
||||
return reports.find(x => x.type === "post_edits");
|
||||
},
|
||||
|
||||
fetchDashboard() {
|
||||
if (this.get("isLoading")) return;
|
||||
|
||||
if (
|
||||
!this.get("dashboardFetchedAt") ||
|
||||
moment()
|
||||
.subtract(30, "minutes")
|
||||
.toDate() > this.get("dashboardFetchedAt")
|
||||
) {
|
||||
this.set("isLoading", true);
|
||||
|
||||
AdminDashboardNext.fetchModeration()
|
||||
.then(model => {
|
||||
const reports = model.reports.map(x => Report.create(x));
|
||||
this.setProperties({
|
||||
dashboardFetchedAt: new Date(),
|
||||
model,
|
||||
reports
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
this.get("exceptionController").set("thrown", e.jqXHR);
|
||||
this.replaceRoute("exception");
|
||||
})
|
||||
.finally(() => {
|
||||
this.set("isLoading", false);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_reportsForPeriodURL(period) {
|
||||
return Discourse.getURL(`/admin/dashboard/moderation?period=${period}`);
|
||||
}
|
||||
});
|
||||
@@ -1,34 +1,38 @@
|
||||
import { setting } from "discourse/lib/computed";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import AdminDashboardNext from "admin/models/admin-dashboard-next";
|
||||
import Report from "admin/models/report";
|
||||
import VersionCheck from "admin/models/version-check";
|
||||
|
||||
const PROBLEMS_CHECK_MINUTES = 1;
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
queryParams: ["period"],
|
||||
period: "monthly",
|
||||
isLoading: false,
|
||||
dashboardFetchedAt: null,
|
||||
exceptionController: Ember.inject.controller("exception"),
|
||||
showVersionChecks: setting("version_checks"),
|
||||
diskSpace: Ember.computed.alias("model.attributes.disk_space"),
|
||||
lastBackupTakenAt: Ember.computed.alias(
|
||||
"model.attributes.last_backup_taken_at"
|
||||
),
|
||||
logSearchQueriesEnabled: setting("log_search_queries"),
|
||||
availablePeriods: ["yearly", "quarterly", "monthly", "weekly"],
|
||||
shouldDisplayDurability: Ember.computed.and("lastBackupTakenAt", "diskSpace"),
|
||||
|
||||
@computed("problems.length")
|
||||
foundProblems(problemsLength) {
|
||||
return this.currentUser.get("admin") && (problemsLength || 0) > 0;
|
||||
},
|
||||
|
||||
fetchProblems() {
|
||||
if (this.get("isLoadingProblems")) return;
|
||||
|
||||
if (
|
||||
!this.get("problemsFetchedAt") ||
|
||||
moment()
|
||||
.subtract(PROBLEMS_CHECK_MINUTES, "minutes")
|
||||
.toDate() > this.get("problemsFetchedAt")
|
||||
) {
|
||||
this._loadProblems();
|
||||
}
|
||||
},
|
||||
|
||||
fetchDashboard() {
|
||||
if (this.get("isLoading")) return;
|
||||
const versionChecks = this.siteSettings.version_checks;
|
||||
|
||||
if (this.get("isLoading") || !versionChecks) return;
|
||||
|
||||
if (
|
||||
!this.get("dashboardFetchedAt") ||
|
||||
@@ -38,22 +42,17 @@ export default Ember.Controller.extend({
|
||||
) {
|
||||
this.set("isLoading", true);
|
||||
|
||||
const versionChecks = this.siteSettings.version_checks;
|
||||
AdminDashboardNext.fetch()
|
||||
.then(model => {
|
||||
let properties = {
|
||||
dashboardFetchedAt: new Date()
|
||||
};
|
||||
|
||||
AdminDashboardNext.find()
|
||||
.then(adminDashboardNextModel => {
|
||||
if (versionChecks) {
|
||||
this.set(
|
||||
"versionCheck",
|
||||
VersionCheck.create(adminDashboardNextModel.version_check)
|
||||
);
|
||||
properties.versionCheck = VersionCheck.create(model.version_check);
|
||||
}
|
||||
|
||||
this.setProperties({
|
||||
dashboardFetchedAt: new Date(),
|
||||
model: adminDashboardNextModel,
|
||||
reports: adminDashboardNextModel.reports.map(x => Report.create(x))
|
||||
});
|
||||
this.setProperties(properties);
|
||||
})
|
||||
.catch(e => {
|
||||
this.get("exceptionController").set("thrown", e.jqXHR);
|
||||
@@ -63,27 +62,17 @@ export default Ember.Controller.extend({
|
||||
this.set("isLoading", false);
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!this.get("problemsFetchedAt") ||
|
||||
moment()
|
||||
.subtract(PROBLEMS_CHECK_MINUTES, "minutes")
|
||||
.toDate() > this.get("problemsFetchedAt")
|
||||
) {
|
||||
this.loadProblems();
|
||||
}
|
||||
},
|
||||
|
||||
loadProblems() {
|
||||
this.set("loadingProblems", true);
|
||||
this.set("problemsFetchedAt", new Date());
|
||||
_loadProblems() {
|
||||
this.setProperties({
|
||||
loadingProblems: true,
|
||||
problemsFetchedAt: new Date()
|
||||
});
|
||||
|
||||
AdminDashboardNext.fetchProblems()
|
||||
.then(d => {
|
||||
this.set("problems", d.problems);
|
||||
})
|
||||
.finally(() => {
|
||||
this.set("loadingProblems", false);
|
||||
});
|
||||
.then(model => this.set("problems", model.problems))
|
||||
.finally(() => this.set("loadingProblems", false));
|
||||
},
|
||||
|
||||
@computed("problemsFetchedAt")
|
||||
@@ -93,69 +82,9 @@ export default Ember.Controller.extend({
|
||||
.format("LLL");
|
||||
},
|
||||
|
||||
@computed("period")
|
||||
startDate(period) {
|
||||
let fullDay = moment()
|
||||
.locale("en")
|
||||
.utc()
|
||||
.subtract(1, "day");
|
||||
|
||||
switch (period) {
|
||||
case "yearly":
|
||||
return fullDay.subtract(1, "year").startOf("day");
|
||||
break;
|
||||
case "quarterly":
|
||||
return fullDay.subtract(3, "month").startOf("day");
|
||||
break;
|
||||
case "weekly":
|
||||
return fullDay.subtract(1, "week").startOf("day");
|
||||
break;
|
||||
case "monthly":
|
||||
return fullDay.subtract(1, "month").startOf("day");
|
||||
break;
|
||||
default:
|
||||
return fullDay.subtract(1, "month").startOf("day");
|
||||
}
|
||||
},
|
||||
|
||||
@computed()
|
||||
lastWeek() {
|
||||
return moment()
|
||||
.locale("en")
|
||||
.utc()
|
||||
.endOf("day")
|
||||
.subtract(1, "week");
|
||||
},
|
||||
|
||||
@computed()
|
||||
endDate() {
|
||||
return moment()
|
||||
.locale("en")
|
||||
.utc()
|
||||
.subtract(1, "day")
|
||||
.endOf("day");
|
||||
},
|
||||
|
||||
@computed("model.attributes.updated_at")
|
||||
updatedTimestamp(updatedAt) {
|
||||
return moment(updatedAt).format("LLL");
|
||||
},
|
||||
|
||||
@computed("lastBackupTakenAt")
|
||||
backupTimestamp(lastBackupTakenAt) {
|
||||
return moment(lastBackupTakenAt).format("LLL");
|
||||
},
|
||||
|
||||
actions: {
|
||||
changePeriod(period) {
|
||||
DiscourseURL.routeTo(this._reportsForPeriodURL(period));
|
||||
},
|
||||
refreshProblems() {
|
||||
this.loadProblems();
|
||||
this._loadProblems();
|
||||
}
|
||||
},
|
||||
|
||||
_reportsForPeriodURL(period) {
|
||||
return Discourse.getURL(`/admin?period=${period}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,108 +1,36 @@
|
||||
import { exportEntity } from "discourse/lib/export-csv";
|
||||
import { outputExportResult } from "discourse/lib/export-result";
|
||||
import Report from "admin/models/report";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
queryParams: ["mode", "start_date", "end_date", "category_id", "group_id"],
|
||||
viewMode: "graph",
|
||||
viewingTable: Em.computed.equal("viewMode", "table"),
|
||||
viewingGraph: Em.computed.equal("viewMode", "graph"),
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
queryParams: ["start_date", "end_date", "category_id", "group_id"],
|
||||
categoryId: null,
|
||||
groupId: null,
|
||||
refreshing: false,
|
||||
|
||||
@computed()
|
||||
categoryOptions() {
|
||||
const arr = [{ name: I18n.t("category.all"), value: "all" }];
|
||||
return arr.concat(
|
||||
Discourse.Site.currentProp("sortedCategories").map(i => {
|
||||
return { name: i.get("name"), value: i.get("id") };
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
@computed()
|
||||
groupOptions() {
|
||||
const arr = [
|
||||
{ name: I18n.t("admin.dashboard.reports.groups"), value: "all" }
|
||||
];
|
||||
return arr.concat(
|
||||
this.site.groups.map(i => {
|
||||
return { name: i["name"], value: i["id"] };
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
@computed("model.type")
|
||||
showCategoryOptions(modelType) {
|
||||
return [
|
||||
"topics",
|
||||
"posts",
|
||||
"time_to_first_response_total",
|
||||
"topics_with_no_response",
|
||||
"flags",
|
||||
"likes",
|
||||
"bookmarks"
|
||||
].includes(modelType);
|
||||
},
|
||||
reportOptions(type) {
|
||||
let options = { table: { perPage: 50, limit: 50 } };
|
||||
|
||||
@computed("model.type")
|
||||
showGroupOptions(modelType) {
|
||||
return (
|
||||
modelType === "visits" ||
|
||||
modelType === "signups" ||
|
||||
modelType === "profile_views"
|
||||
);
|
||||
if (type === "top_referred_topics") {
|
||||
options.table.limit = 10;
|
||||
}
|
||||
|
||||
return options;
|
||||
},
|
||||
|
||||
actions: {
|
||||
refreshReport() {
|
||||
var q;
|
||||
this.set("refreshing", true);
|
||||
|
||||
this.setProperties({
|
||||
start_date: this.get("startDate"),
|
||||
end_date: this.get("endDate"),
|
||||
category_id: this.get("categoryId")
|
||||
});
|
||||
|
||||
if (this.get("groupId")) {
|
||||
this.set("group_id", this.get("groupId"));
|
||||
}
|
||||
|
||||
q = Report.find(
|
||||
this.get("model.type"),
|
||||
this.get("startDate"),
|
||||
this.get("endDate"),
|
||||
this.get("categoryId"),
|
||||
this.get("groupId")
|
||||
);
|
||||
q.then(m => this.set("model", m)).finally(() =>
|
||||
this.set("refreshing", false)
|
||||
);
|
||||
onSelectStartDate(startDate) {
|
||||
this.set("start_date", startDate);
|
||||
},
|
||||
|
||||
viewAsTable() {
|
||||
this.set("viewMode", "table");
|
||||
onSelectCategory(categoryId) {
|
||||
this.set("category_id", categoryId);
|
||||
},
|
||||
|
||||
viewAsGraph() {
|
||||
this.set("viewMode", "graph");
|
||||
onSelectGroup(groupId) {
|
||||
this.set("group_id", groupId);
|
||||
},
|
||||
|
||||
exportCsv() {
|
||||
exportEntity("report", {
|
||||
name: this.get("model.type"),
|
||||
start_date: this.get("startDate"),
|
||||
end_date: this.get("endDate"),
|
||||
category_id:
|
||||
this.get("categoryId") === "all" ? undefined : this.get("categoryId"),
|
||||
group_id:
|
||||
this.get("groupId") === "all" ? undefined : this.get("groupId")
|
||||
}).then(outputExportResult);
|
||||
onSelectEndDate(endDate) {
|
||||
this.set("end_date", endDate);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user