diff --git a/app/assets/javascripts/admin/components/dashboard-inline-table.js.es6 b/app/assets/javascripts/admin/components/dashboard-inline-table.js.es6
index dd8582461bf..22443906acb 100644
--- a/app/assets/javascripts/admin/components/dashboard-inline-table.js.es6
+++ b/app/assets/javascripts/admin/components/dashboard-inline-table.js.es6
@@ -1,65 +1,23 @@
import { ajax } from 'discourse/lib/ajax';
-import computed from 'ember-addons/ember-computed-decorators';
+import Report from "admin/models/report";
+import AsyncReport from "admin/mixins/async-report";
-export default Ember.Component.extend({
+export default Ember.Component.extend(AsyncReport, {
classNames: ["dashboard-table", "dashboard-inline-table", "fixed"],
-
- classNameBindings: ["isLoading"],
-
- total: null,
- labels: null,
- title: null,
- chartData: null,
- isLoading: false,
+ isLoading: true,
help: null,
helpPage: null,
- model: null,
-
- didInsertElement() {
- this._super();
-
- if (this.get("dataSourceName")){
- this._fetchReport();
- } else if (this.get("model")) {
- this._setPropertiesFromModel(this.get("model"));
- }
- },
-
- didUpdateAttrs() {
- this._super();
-
- if (this.get("model")) {
- this._setPropertiesFromModel(this.get("model"));
- }
- },
-
- @computed("dataSourceName")
- dataSource(dataSourceName) {
- return `/admin/reports/${dataSourceName}`;
- },
-
- _fetchReport() {
- if (this.get("isLoading")) return;
+ fetchReport() {
this.set("isLoading", true);
ajax(this.get("dataSource"))
.then((response) => {
- this._setPropertiesFromModel(response.report);
+ this._setPropertiesFromReport(Report.create(response.report));
}).finally(() => {
- this.set("isLoading", false);
+ if (!Ember.isEmpty(this.get("report.data"))) {
+ this.set("isLoading", false);
+ };
});
- },
-
- _setPropertiesFromModel(model) {
- const data = model.data.sort((a, b) => a.x >= b.x);
-
- this.setProperties({
- labels: data.map(r => r.x),
- dataset: data.map(r => r.y),
- total: model.total,
- title: model.title,
- chartData: data
- });
}
});
diff --git a/app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6 b/app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6
index 3b56917f997..f5ff432a84d 100644
--- a/app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6
+++ b/app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6
@@ -1,43 +1,23 @@
import { ajax } from "discourse/lib/ajax";
import computed from "ember-addons/ember-computed-decorators";
-import loadScript from "discourse/lib/load-script";
+import AsyncReport from "admin/mixins/async-report";
import Report from "admin/models/report";
+import { number } from 'discourse/lib/formatter';
-export default Ember.Component.extend({
+export default Ember.Component.extend(AsyncReport, {
classNames: ["dashboard-mini-chart"],
-
- classNameBindings: ["trend", "oneDataPoint", "isLoading"],
-
- isLoading: false,
- total: null,
- trend: null,
- title: null,
+ classNameBindings: ["thirtyDayTrend", "oneDataPoint"],
+ isLoading: true,
+ thirtyDayTrend: Ember.computed.alias("report.thirtyDayTrend"),
oneDataPoint: false,
backgroundColor: "rgba(200,220,240,0.3)",
borderColor: "#08C",
+ average: false,
- didInsertElement() {
+ willDestroyEelement() {
this._super();
- if (this.get("model")) {
- loadScript("/javascripts/Chart.min.js").then(() => {
- this._setPropertiesFromModel(this.get("model"));
- this._drawChart();
- });
- }
- },
-
- didUpdateAttrs() {
- this._super();
-
- loadScript("/javascripts/Chart.min.js").then(() => {
- if (this.get("model") && !this.get("values")) {
- this._setPropertiesFromModel(this.get("model"));
- this._drawChart();
- } else if (this.get("dataSource")) {
- this._fetchReport();
- }
- });
+ this.messageBus.unsubscribe(this.get("dataSource"));
},
@computed("dataSourceName")
@@ -47,9 +27,9 @@ export default Ember.Component.extend({
}
},
- @computed("trend")
- trendIcon(trend) {
- switch (trend) {
+ @computed("thirtyDayTrend")
+ trendIcon(thirtyDayTrend) {
+ switch (thirtyDayTrend) {
case "trending-up":
return "angle-up";
case "trending-down":
@@ -63,79 +43,73 @@ export default Ember.Component.extend({
}
},
- _fetchReport() {
- if (this.get("isLoading")) return;
-
+ fetchReport() {
this.set("isLoading", true);
let payload = {
- data: {}
+ data: { async: true }
};
if (this.get("startDate")) {
- payload.data.start_date = this.get("startDate").toISOString();
+ payload.data.start_date = this.get("startDate").format('YYYY-MM-DD[T]HH:mm:ss.SSSZZ');
}
if (this.get("endDate")) {
- payload.data.end_date = this.get("endDate").toISOString();
+ payload.data.end_date = this.get("endDate").format('YYYY-MM-DD[T]HH:mm:ss.SSSZZ');
}
ajax(this.get("dataSource"), payload)
.then((response) => {
- this._setPropertiesFromModel(Report.create(response.report));
+ // if (!Ember.isEmpty(response.report.data)) {
+ this._setPropertiesFromReport(Report.create(response.report));
+ // }
})
.finally(() => {
- this.set("isLoading", false);
+ if (this.get("oneDataPoint")) {
+ this.set("isLoading", false);
+ return;
+ }
- Ember.run.schedule("afterRender", () => {
- if (!this.get("oneDataPoint")) {
- this._drawChart();
- }
- });
+ if (!Ember.isEmpty(this.get("report.data"))) {
+ this.set("isLoading", false);
+ this.renderReport();
+ }
});
},
- _drawChart() {
- const $chartCanvas = this.$(".chart-canvas");
- if (!$chartCanvas.length) return;
+ renderReport() {
+ if (!this.element || this.isDestroying || this.isDestroyed) { return; }
+ if (this.get("oneDataPoint")) return;
- const context = $chartCanvas[0].getContext("2d");
+ Ember.run.schedule("afterRender", () => {
+ const $chartCanvas = this.$(".chart-canvas");
- const data = {
- labels: this.get("labels"),
- datasets: [{
- data: Ember.makeArray(this.get("values")),
- backgroundColor: this.get("backgroundColor"),
- borderColor: this.get("borderColor")
- }]
- };
+ if (!$chartCanvas.length) return;
+ const context = $chartCanvas[0].getContext("2d");
- this._chart = new window.Chart(context, this._buildChartConfig(data));
- },
+ const data = {
+ labels: this.get("labels"),
+ datasets: [{
+ data: Ember.makeArray(this.get("values")),
+ backgroundColor: this.get("backgroundColor"),
+ borderColor: this.get("borderColor")
+ }]
+ };
- _setPropertiesFromModel(report) {
- const oneDataPoint = (this.get("startDate") && this.get("endDate")) &&
- this.get("startDate").isSame(this.get("endDate"), "day");
-
- this.setProperties({
- oneDataPoint,
- labels: report.get("data").map(r => r.x),
- values: report.get("data").map(r => r.y),
- total: report.get("total"),
- description: report.get("description"),
- title: report.get("title"),
- trend: report.get("sevenDayTrend"),
- prev30Days: report.get("prev30Days"),
+ this._chart = new window.Chart(context, this._buildChartConfig(data));
});
},
+ _setPropertiesFromReport(report) {
+ const oneDataPoint = (this.get("startDate") && this.get("endDate")) &&
+ this.get("startDate").isSame(this.get("endDate"), "day");
+
+ report.set("average", this.get("average"));
+
+ this.setProperties({ oneDataPoint, report });
+ },
+
_buildChartConfig(data) {
- const values = data.datasets[0].data;
- const max = Math.max(...values);
- const min = Math.min(...values);
-
- const stepSize = Math.max(...[Math.ceil((max - min) / 5) * 5, 20]);
-
return {
type: "line",
data,
@@ -144,7 +118,6 @@ export default Ember.Component.extend({
display: false
},
responsive: true,
- maintainAspectRatio: false,
layout: {
padding: {
left: 0,
@@ -156,11 +129,7 @@ export default Ember.Component.extend({
scales: {
yAxes: [{
display: true,
- ticks: {
- suggestedMin: 0,
- stepSize,
- suggestedMax: max + stepSize
- }
+ ticks: { callback: (label) => number(label) }
}],
xAxes: [{
display: true,
diff --git a/app/assets/javascripts/admin/components/dashboard-table-trending-search.js.es6 b/app/assets/javascripts/admin/components/dashboard-table-trending-search.js.es6
index aac53bb9c0c..f6523f6841b 100644
--- a/app/assets/javascripts/admin/components/dashboard-table-trending-search.js.es6
+++ b/app/assets/javascripts/admin/components/dashboard-table-trending-search.js.es6
@@ -1,17 +1,8 @@
import DashboardTable from "admin/components/dashboard-table";
-import { number } from 'discourse/lib/formatter';
+import AsyncReport from "admin/mixins/async-report";
-export default DashboardTable.extend({
+export default DashboardTable.extend(AsyncReport, {
layoutName: "admin/templates/components/dashboard-table",
- classNames: ["dashboard-table", "dashboard-table-trending-search"],
-
- transformModel(model) {
- return {
- labels: model.labels,
- values: model.data.map(data => {
- return [data[0], number(data[1]), number(data[2])];
- })
- };
- },
+ classNames: ["dashboard-table", "dashboard-table-trending-search"]
});
diff --git a/app/assets/javascripts/admin/components/dashboard-table.js.es6 b/app/assets/javascripts/admin/components/dashboard-table.js.es6
index 2bf5929443d..b6748367079 100644
--- a/app/assets/javascripts/admin/components/dashboard-table.js.es6
+++ b/app/assets/javascripts/admin/components/dashboard-table.js.es6
@@ -1,83 +1,50 @@
-import { ajax } from 'discourse/lib/ajax';
-import computed from 'ember-addons/ember-computed-decorators';
+import { ajax } from "discourse/lib/ajax";
+import Report from "admin/models/report";
+import AsyncReport from "admin/mixins/async-report";
+import computed from "ember-addons/ember-computed-decorators";
+import { number } from 'discourse/lib/formatter';
-export default Ember.Component.extend({
+export default Ember.Component.extend(AsyncReport, {
classNames: ["dashboard-table"],
-
- classNameBindings: ["isLoading"],
-
- total: null,
- labels: null,
- title: null,
- chartData: null,
- isLoading: false,
help: null,
helpPage: null,
- model: null,
- transformModel(model) {
- const data = model.data.sort((a, b) => a.x >= b.x);
-
- return {
- labels: model.labels,
- values: data
- };
+ @computed("report")
+ values(report) {
+ if (!report) return;
+ return Ember.makeArray(report.data)
+ .sort((a, b) => a.x >= b.x)
+ .map(x => {
+ return [ x[0], number(x[1]), number(x[2]) ];
+ });
},
- didInsertElement() {
- this._super();
- this._initializeTable();
+ @computed("report")
+ labels(report) {
+ if (!report) return;
+ return Ember.makeArray(report.labels);
},
- didUpdateAttrs() {
- this._super();
- this._initializeTable();
- },
-
- @computed("dataSourceName")
- dataSource(dataSourceName) {
- return `/admin/reports/${dataSourceName}`;
- },
-
- _initializeTable() {
- if (this.get("model") && !this.get("values")) {
- this._setPropertiesFromModel(this.get("model"));
- } else if (this.get("dataSource")) {
- this._fetchReport();
- }
- },
-
- _fetchReport() {
- if (this.get("isLoading")) return;
-
+ fetchReport() {
this.set("isLoading", true);
- let payload = {data: {}};
+ let payload = { data: { async: true } };
if (this.get("startDate")) {
- payload.data.start_date = this.get("startDate").toISOString();
+ payload.data.start_date = this.get("startDate").format("YYYY-MM-DD[T]HH:mm:ss.SSSZZ");
}
if (this.get("endDate")) {
- payload.data.end_date = this.get("endDate").toISOString();
+ payload.data.end_date = this.get("endDate").format("YYYY-MM-DD[T]HH:mm:ss.SSSZZ");
}
ajax(this.get("dataSource"), payload)
.then((response) => {
- this._setPropertiesFromModel(response.report);
+ this._setPropertiesFromReport(Report.create(response.report));
}).finally(() => {
- this.set("isLoading", false);
+ if (!Ember.isEmpty(this.get("report.data"))) {
+ this.set("isLoading", false);
+ };
});
- },
-
- _setPropertiesFromModel(model) {
- const { labels, values } = this.transformModel(model);
-
- this.setProperties({
- labels,
- values,
- total: model.total,
- title: model.title
- });
}
});
diff --git a/app/assets/javascripts/admin/controllers/admin-dashboard-next.js.es6 b/app/assets/javascripts/admin/controllers/admin-dashboard-next.js.es6
index 0c56cfe971a..2d7fc621839 100644
--- a/app/assets/javascripts/admin/controllers/admin-dashboard-next.js.es6
+++ b/app/assets/javascripts/admin/controllers/admin-dashboard-next.js.es6
@@ -1,14 +1,14 @@
import DiscourseURL from "discourse/lib/url";
import computed from "ember-addons/ember-computed-decorators";
-import AdminDashboardNext from 'admin/models/admin-dashboard-next';
+import AdminDashboardNext from "admin/models/admin-dashboard-next";
+import Report from "admin/models/report";
export default Ember.Controller.extend({
queryParams: ["period"],
period: "all",
isLoading: false,
dashboardFetchedAt: null,
- exceptionController: Ember.inject.controller('exception'),
-
+ exceptionController: Ember.inject.controller("exception"),
diskSpace: Ember.computed.alias("model.attributes.disk_space"),
fetchDashboard() {
@@ -18,8 +18,11 @@ export default Ember.Controller.extend({
this.set("isLoading", true);
AdminDashboardNext.find().then(adminDashboardNextModel => {
- this.set("dashboardFetchedAt", new Date());
- this.set("model", 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");
diff --git a/app/assets/javascripts/admin/mixins/async-report.js.es6 b/app/assets/javascripts/admin/mixins/async-report.js.es6
new file mode 100644
index 00000000000..5206f651862
--- /dev/null
+++ b/app/assets/javascripts/admin/mixins/async-report.js.es6
@@ -0,0 +1,68 @@
+import computed from 'ember-addons/ember-computed-decorators';
+import Report from "admin/models/report";
+
+export default Ember.Mixin.create({
+ classNameBindings: ["isLoading"],
+
+ report: null,
+
+ init() {
+ this._super();
+
+ this.messageBus.subscribe(this.get("dataSource"), report => {
+ const formatDate = (date) => moment(date).format("YYYYMMDD");
+
+ // this check is done to avoid loading a chart after period has changed
+ if (
+ (this.get("startDate") && formatDate(report.start_date) === formatDate(this.get("startDate"))) &&
+ (this.get("endDate") && formatDate(report.end_date) === formatDate(this.get("endDate")))
+ ) {
+ this._setPropertiesFromReport(Report.create(report));
+ this.set("isLoading", false);
+ this.renderReport();
+ } else {
+ this._setPropertiesFromReport(Report.create(report));
+ this.set("isLoading", false);
+ this.renderReport();
+ }
+ });
+ },
+
+ didInsertElement() {
+ this._super();
+
+ Ember.run.later(this, function() {
+ this.fetchReport();
+ }, 500);
+ },
+
+ didUpdateAttrs() {
+ this._super();
+
+ this.fetchReport();
+ },
+
+ renderReport() {},
+
+ @computed("dataSourceName")
+ dataSource(dataSourceName) {
+ return `/admin/reports/${dataSourceName}`;
+ },
+
+ @computed("report")
+ labels(report) {
+ if (!report) return;
+ return Ember.makeArray(report.data).map(r => r.x);
+ },
+
+ @computed("report")
+ values(report) {
+ if (!report) return;
+ return Ember.makeArray(report.data).map(r => r.y);
+ },
+
+ _setPropertiesFromReport(report) {
+ if (!this.element || this.isDestroying || this.isDestroyed) { return; }
+ this.setProperties({ report });
+ }
+});
diff --git a/app/assets/javascripts/admin/models/admin-dashboard-next.js.es6 b/app/assets/javascripts/admin/models/admin-dashboard-next.js.es6
index 880c2d5488f..4747529fe7e 100644
--- a/app/assets/javascripts/admin/models/admin-dashboard-next.js.es6
+++ b/app/assets/javascripts/admin/models/admin-dashboard-next.js.es6
@@ -1,9 +1,6 @@
import { ajax } from "discourse/lib/ajax";
-import Report from "admin/models/report";
-const ATTRIBUTES = [ "disk_space", "updated_at", "last_backup_taken_at"];
-
-const REPORTS = [ "global_reports", "user_reports" ];
+const ATTRIBUTES = [ "disk_space", "updated_at", "last_backup_taken_at" ];
const AdminDashboardNext = Discourse.Model.extend({});
@@ -19,12 +16,7 @@ AdminDashboardNext.reopenClass({
return ajax("/admin/dashboard-next.json").then(function(json) {
var model = AdminDashboardNext.create();
- const reports = {};
- REPORTS.forEach(name => json[name].forEach(r => {
- if (!reports[name]) reports[name] = {};
- reports[name][r.type] = Report.create(r);
- }));
- model.set("reports", reports);
+ model.set("reports", json.reports);
const attributes = {};
ATTRIBUTES.forEach(a => attributes[a] = json[a]);
diff --git a/app/assets/javascripts/admin/models/report.js.es6 b/app/assets/javascripts/admin/models/report.js.es6
index fac048a62a4..1113f2e5d1a 100644
--- a/app/assets/javascripts/admin/models/report.js.es6
+++ b/app/assets/javascripts/admin/models/report.js.es6
@@ -5,6 +5,8 @@ import { fillMissingDates } from 'discourse/lib/utilities';
import computed from 'ember-addons/ember-computed-decorators';
const Report = Discourse.Model.extend({
+ average: false,
+
reportUrl: fmt("type", "/admin/reports/%@"),
valueAt(numDaysAgo) {
@@ -35,30 +37,43 @@ const Report = Discourse.Model.extend({
}
},
- todayCount: function() { return this.valueAt(0); }.property("data"),
- yesterdayCount: function() { return this.valueAt(1); }.property("data"),
- sevenDaysAgoCount: function() { return this.valueAt(7); }.property("data"),
- thirtyDaysAgoCount: function() { return this.valueAt(30); }.property("data"),
+ todayCount: function() { return this.valueAt(0); }.property("data", "average"),
+ 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"),
+ lastThirtyDaysCount: function() {
+ return this.averageCount(30, this.valueFor(1, 30));
+ }.property("data", "average"),
- lastSevenDaysCount: function() { return this.valueFor(1, 7); }.property("data"),
- lastThirtyDaysCount: function() { return this.valueFor(1, 30); }.property("data"),
+ averageCount(count, value) {
+ return this.get("average") ? value / count : value;
+ },
- @computed('data')
- yesterdayTrend() {
- const yesterdayVal = this.valueAt(1);
+ @computed('yesterdayCount')
+ yesterdayTrend(yesterdayCount) {
+ const yesterdayVal = yesterdayCount;
const twoDaysAgoVal = this.valueAt(2);
- if (yesterdayVal > twoDaysAgoVal) {
+ const change = ((yesterdayVal - twoDaysAgoVal) / yesterdayVal) * 100;
+
+ if (change > 50) {
+ return "high-trending-up";
+ } else if (change > 0) {
return "trending-up";
- } else if (yesterdayVal < twoDaysAgoVal) {
- return "trending-down";
- } else {
+ } else if (change === 0) {
return "no-change";
+ } else if (change < -50) {
+ return "high-trending-down";
+ } else if (change < 0) {
+ return "trending-down";
}
},
- @computed('data')
- sevenDayTrend() {
- const currentPeriod = this.valueFor(1, 7);
+ @computed('lastSevenDaysCount')
+ sevenDayTrend(lastSevenDaysCount) {
+ const currentPeriod = lastSevenDaysCount;
const prevPeriod = this.valueFor(8, 14);
const change = ((currentPeriod - prevPeriod) / prevPeriod) * 100;
@@ -75,17 +90,22 @@ const Report = Discourse.Model.extend({
}
},
- @computed('prev30Days', 'data')
- thirtyDayTrend(prev30Days) {
- if (prev30Days) {
- const currentPeriod = this.valueFor(1, 30);
- if (currentPeriod > this.get("prev30Days")) {
- return "trending-up";
- } else if (currentPeriod < prev30Days) {
- return "trending-down";
- }
+ @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";
}
- return "no-change";
},
@computed('type')
@@ -126,19 +146,19 @@ const Report = Discourse.Model.extend({
return title;
},
- @computed('data')
- yesterdayCountTitle() {
- return this.changeTitle(this.valueAt(1), this.valueAt(2), "two days ago");
+ @computed('yesterdayCount')
+ yesterdayCountTitle(yesterdayCount) {
+ return this.changeTitle(yesterdayCount, this.valueAt(2), "two days ago");
},
- @computed('data')
- sevenDayCountTitle() {
- return this.changeTitle(this.valueFor(1, 7), this.valueFor(8, 14), "two weeks ago");
+ @computed('lastSevenDaysCount')
+ sevenDayCountTitle(lastSevenDaysCount) {
+ return this.changeTitle(lastSevenDaysCount, this.valueFor(8, 14), "two weeks ago");
},
- @computed('prev30Days', 'data')
- thirtyDayCountTitle(prev30Days) {
- return this.changeTitle(this.valueFor(1, 30), prev30Days, "in the previous 30 day period");
+ @computed('prev30Days', 'lastThirtyDaysCount')
+ thirtyDayCountTitle(prev30Days, lastThirtyDaysCount) {
+ return this.changeTitle(lastThirtyDaysCount, prev30Days, "in the previous 30 day period");
},
@computed('data')
diff --git a/app/assets/javascripts/admin/routes/admin-dashboard-next.js.es6 b/app/assets/javascripts/admin/routes/admin-dashboard-next.js.es6
index 30ca9b033c0..c877f07bf50 100644
--- a/app/assets/javascripts/admin/routes/admin-dashboard-next.js.es6
+++ b/app/assets/javascripts/admin/routes/admin-dashboard-next.js.es6
@@ -1,5 +1,9 @@
+import loadScript from "discourse/lib/load-script";
+
export default Discourse.Route.extend({
activate() {
- this.controllerFor('admin-dashboard-next').fetchDashboard();
+ loadScript("/javascripts/Chart.min.js").then(() => {
+ this.controllerFor('admin-dashboard-next').fetchDashboard();
+ });
}
});
diff --git a/app/assets/javascripts/admin/templates/components/dashboard-inline-table.hbs b/app/assets/javascripts/admin/templates/components/dashboard-inline-table.hbs
index b7b0bdc61eb..a5c22f86c68 100644
--- a/app/assets/javascripts/admin/templates/components/dashboard-inline-table.hbs
+++ b/app/assets/javascripts/admin/templates/components/dashboard-inline-table.hbs
@@ -1,6 +1,6 @@
-{{#conditional-loading-spinner condition=isLoading}}
+{{#conditional-loading-section isLoading=isLoading title=report.title}}
-
{{title}}
+
{{report.title}}
{{#if help}}
{{i18n help}}
@@ -18,11 +18,11 @@
- {{#each dataset as |data|}}
- {{number data}} |
+ {{#each values as |value|}}
+ {{number value}} |
{{/each}}
-{{/conditional-loading-spinner}}
+{{/conditional-loading-section}}
diff --git a/app/assets/javascripts/admin/templates/components/dashboard-mini-chart.hbs b/app/assets/javascripts/admin/templates/components/dashboard-mini-chart.hbs
index 20fb90063d6..a0b22b0c8c6 100644
--- a/app/assets/javascripts/admin/templates/components/dashboard-mini-chart.hbs
+++ b/app/assets/javascripts/admin/templates/components/dashboard-mini-chart.hbs
@@ -1,28 +1,31 @@
-{{#conditional-loading-spinner condition=isLoading}}
+{{#conditional-loading-section isLoading=isLoading title=report.title}}
-
{{title}}
+
+ {{report.title}}
- {{#if description}}
-
+ {{#if report.description}}
{{d-icon "question-circle"}}
+ {{/if}}
+
+
+
+
+ {{number report.lastThirtyDaysCount}}
- {{/if}}
+
+ {{#if trendIcon}}
+ {{d-icon trendIcon}}
+ {{/if}}
+
{{#if oneDataPoint}}
- {{number chartData.lastObject.y}}
+ {{number values.lastObject.y}}
{{else}}
-
- {{number prev30Days}}
-
- {{#if trendIcon}}
- {{d-icon trendIcon}}
- {{/if}}
-
{{/if}}
-{{/conditional-loading-spinner}}
+{{/conditional-loading-section}}
diff --git a/app/assets/javascripts/admin/templates/components/dashboard-table.hbs b/app/assets/javascripts/admin/templates/components/dashboard-table.hbs
index cb4070e40ea..d658f4bb019 100644
--- a/app/assets/javascripts/admin/templates/components/dashboard-table.hbs
+++ b/app/assets/javascripts/admin/templates/components/dashboard-table.hbs
@@ -1,6 +1,6 @@
-{{#conditional-loading-spinner condition=isLoading}}
+{{#conditional-loading-section isLoading=isLoading title=report.title}}
-
{{title}}
+
{{report.title}}
{{#if help}}
{{i18n help}}
@@ -28,4 +28,4 @@
-{{/conditional-loading-spinner}}
+{{/conditional-loading-section}}
diff --git a/app/assets/javascripts/admin/templates/dashboard_next.hbs b/app/assets/javascripts/admin/templates/dashboard_next.hbs
index 3d0cca00c3d..ec9fd3b93b7 100644
--- a/app/assets/javascripts/admin/templates/dashboard_next.hbs
+++ b/app/assets/javascripts/admin/templates/dashboard_next.hbs
@@ -1,4 +1,4 @@
-{{plugin-outlet name="admin-dashboard-top"}}
+ {{plugin-outlet name="admin-dashboard-top"}}