diff --git a/app/assets/javascripts/admin/components/admin-graph.js.es6 b/app/assets/javascripts/admin/components/admin-graph.js.es6
index c99c7b186b5..724e9673742 100644
--- a/app/assets/javascripts/admin/components/admin-graph.js.es6
+++ b/app/assets/javascripts/admin/components/admin-graph.js.es6
@@ -1,4 +1,5 @@
import loadScript from 'discourse/lib/load-script';
+import { number } from 'discourse/lib/formatter';
export default Ember.Component.extend({
tagName: 'canvas',
@@ -22,10 +23,16 @@ export default Ember.Component.extend({
data: data,
options: {
responsive: true,
+ tooltips: {
+ callbacks: {
+ title: (context) => moment(context[0].xLabel, "YYYY-MM-DD").format("LL")
+ }
+ },
scales: {
yAxes: [{
display: true,
ticks: {
+ callback: (label) => number(label),
suggestedMin: 0
}
}]
diff --git a/app/assets/javascripts/admin/controllers/admin-reports.js.es6 b/app/assets/javascripts/admin/controllers/admin-reports.js.es6
index 83fde41c7e9..1b02d5575ac 100644
--- a/app/assets/javascripts/admin/controllers/admin-reports.js.es6
+++ b/app/assets/javascripts/admin/controllers/admin-reports.js.es6
@@ -4,6 +4,7 @@ import Report from 'admin/models/report';
import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Controller.extend({
+ classNames: ["admin-reports"],
queryParams: ["mode", "start_date", "end_date", "category_id", "group_id"],
viewMode: 'graph',
viewingTable: Em.computed.equal('viewMode', 'table'),
diff --git a/app/assets/javascripts/admin/mixins/async-report.js.es6 b/app/assets/javascripts/admin/mixins/async-report.js.es6
index 2c22ab15c21..df225f42f61 100644
--- a/app/assets/javascripts/admin/mixins/async-report.js.es6
+++ b/app/assets/javascripts/admin/mixins/async-report.js.es6
@@ -1,7 +1,7 @@
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Mixin.create({
- classNameBindings: ["isLoading"],
+ classNameBindings: ["isLoading", "dataSourceNames"],
reports: null,
isLoading: false,
dataSourceNames: "",
@@ -25,7 +25,6 @@ export default Ember.Mixin.create({
// the array contains only unique values
reports = reports.uniqBy("report_key");
-
const sort = (r) => {
if (r.length > 1) {
return dataSourceNames
@@ -40,7 +39,6 @@ export default Ember.Mixin.create({
return sort(reports);
}
-
return sort(reports.filter(report => {
return report.report_key.includes(startDate.format("YYYYMMDD")) &&
report.report_key.includes(endDate.format("YYYYMMDD"));
diff --git a/app/assets/javascripts/admin/models/report.js.es6 b/app/assets/javascripts/admin/models/report.js.es6
index 5441be004a3..f8078e85ab5 100644
--- a/app/assets/javascripts/admin/models/report.js.es6
+++ b/app/assets/javascripts/admin/models/report.js.es6
@@ -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;
diff --git a/app/assets/javascripts/admin/templates/components/admin-report-counts.hbs b/app/assets/javascripts/admin/templates/components/admin-report-counts.hbs
index 793573e2509..2dc0600e443 100644
--- a/app/assets/javascripts/admin/templates/components/admin-report-counts.hbs
+++ b/app/assets/javascripts/admin/templates/components/admin-report-counts.hbs
@@ -11,11 +11,11 @@
{{number report.yesterdayCount}} {{d-icon "caret-up" class="up"}} {{d-icon "caret-down" class="down"}}
-
- {{i18n 'admin.dashboard.reports.start_date'}} {{date-picker-past value=startDate defaultDate=startDate}}
- {{i18n 'admin.dashboard.reports.end_date'}} {{date-picker-past value=endDate defaultDate=endDate}}
- {{#if showCategoryOptions}}
- {{combo-box filterable=true valueAttribute="value" content=categoryOptions value=categoryId}}
- {{/if}}
- {{#if showGroupOptions}}
- {{combo-box filterable=true valueAttribute="value" content=groupOptions value=groupId}}
- {{/if}}
- {{d-button action="refreshReport" class="btn-primary" label="admin.dashboard.reports.refresh_report" icon="refresh"}}
- {{d-button action="exportCsv" label="admin.export_csv.button_text" icon="download"}}
+
+
+ {{#conditional-loading-spinner condition=refreshing}}
+
+
+ {{#if viewingGraph}}
+ {{admin-graph model=model}}
+ {{else}}
+ {{admin-table-report model=model}}
+ {{/if}}
+
+ {{#if model.relatedReport}}
+ {{admin-table-report model=model.relatedReport}}
+ {{/if}}
+ {{/conditional-loading-spinner}}
+
+
+
+
+ {{i18n 'admin.dashboard.reports.start_date'}} {{date-picker-past value=startDate defaultDate=startDate}}
+
+
+ {{i18n 'admin.dashboard.reports.end_date'}} {{date-picker-past value=endDate defaultDate=endDate}}
+
+ {{#if showCategoryOptions}}
+ {{combo-box filterable=true valueAttribute="value" content=categoryOptions value=categoryId}}
+ {{/if}}
+ {{#if showGroupOptions}}
+ {{combo-box filterable=true valueAttribute="value" content=groupOptions value=groupId}}
+ {{/if}}
+ {{d-button action="refreshReport" class="btn-primary" label="admin.dashboard.reports.refresh_report" icon="refresh"}}
+ {{d-button action="exportCsv" label="admin.export_csv.button_text" icon="download"}}
+
-
-
-
-{{#conditional-loading-spinner condition=refreshing}}
- {{#if viewingGraph}}
- {{admin-graph model=model}}
- {{else}}
- {{admin-table-report model=model}}
- {{/if}}
-
- {{#if model.relatedReport}}
- {{admin-table-report model=model.relatedReport}}
- {{/if}}
-{{/conditional-loading-spinner}}
diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss
index d473536d09b..5557f7c795e 100644
--- a/app/assets/stylesheets/common/admin/admin_base.scss
+++ b/app/assets/stylesheets/common/admin/admin_base.scss
@@ -6,6 +6,7 @@
@import "common/admin/customize";
@import "common/admin/flagging";
@import "common/admin/dashboard_next";
+@import "common/admin/admin_reports";
@import "common/admin/moderation_history";
@import "common/admin/suspend";
@@ -1968,6 +1969,12 @@ table#user-badges {
margin-right: 20px;
}
+.admin-reports, .dashboard-next {
+ &.admin-contents {
+ margin: 0;
+ }
+}
+
.cbox0 { background: blend-primary-secondary(0%); }
.cbox10 { background: blend-primary-secondary(10%); }
.cbox20 { background: blend-primary-secondary(20%); }
diff --git a/app/assets/stylesheets/common/admin/admin_reports.scss b/app/assets/stylesheets/common/admin/admin_reports.scss
new file mode 100644
index 00000000000..9a064a85c02
--- /dev/null
+++ b/app/assets/stylesheets/common/admin/admin_reports.scss
@@ -0,0 +1,53 @@
+.admin-reports {
+ h3 {
+ border-bottom: 1px solid $primary-low;
+ margin-bottom: .5em;
+ padding-bottom: .5em;
+ }
+
+ .report-container {
+ display: flex;
+
+ .loading-container {
+ width: 100%;
+ }
+
+ .visualization {
+ display: flex;
+ flex: 4;
+ }
+
+ .filters {
+ display: flex;
+ flex: 1;
+ align-items: center;
+ flex-direction: column;
+ margin-left: 2em;
+
+ .date-picker {
+ margin: 0;
+ width: 195px;
+ }
+
+ .combo-box, .date-picker-wrapper, .btn {
+ width: 100%;
+ margin-bottom: 1em;
+ }
+ }
+
+ @include small-width {
+ flex-direction: column;
+ min-width: 100%;
+
+ .visualization {
+ order: 2;
+ }
+
+ .filters {
+ order: 1;
+ margin: 0;
+ align-items: flex-start;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/common/admin/dashboard_next.scss b/app/assets/stylesheets/common/admin/dashboard_next.scss
index 3fe538aa7dc..57cce7659e5 100644
--- a/app/assets/stylesheets/common/admin/dashboard_next.scss
+++ b/app/assets/stylesheets/common/admin/dashboard_next.scss
@@ -1,8 +1,4 @@
.dashboard-next {
- &.admin-contents {
- margin: 0;
- }
-
.section-top {
margin-bottom: 1em;
}
@@ -19,6 +15,14 @@
min-width: calc(50% - .5em);
max-width: 100%;
+ &:last-child, {
+ margin-left: 1em;
+ }
+
+ &:first-child {
+ margin-right: 1em;
+ }
+
@include small-width {
min-width: 100%;
@@ -32,16 +36,9 @@
}
}
- .section-column:last-child, {
- margin-left: 1em;
- }
-
- .section-column:first-child {
- margin-right: 1em;
- }
-
@include small-width {
- .section-column:last-child, .section-column:first-child {
+ .section-column:last-child,
+ .section-column:first-child {
margin: 0;
}
}
@@ -107,16 +104,17 @@
.durability, .last-dashboard-update {
flex: 1 1 50%;
box-sizing: border-box;
- margin: 20px 0;
- padding: 0 20px;
+ margin: 1em 0;
+ padding: 0 1em;
}
.durability {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
+
.backups, .uploads {
- flex: 1 1 100%;
+ flex: 1 1 100%;
}
.uploads p:last-of-type {
@@ -146,7 +144,7 @@
border-left: 1px solid $primary-low;
text-align: center;
display: flex;
- justify-content: center;
+ justify-content: center;
div {
align-self: center;
h4 {
@@ -156,7 +154,17 @@
}
}
+ .top-referred-topics, .trending-search {
+ th:first-of-type {
+ text-align: left;
+ }
+ }
+ .top-referred-topics {
+ .dashboard-table table {
+ table-layout: auto;
+ }
+ }
.community-health {
.period-chooser .period-chooser-header {
@@ -171,7 +179,6 @@
}
}
-
.dashboard-mini-chart {
.status {
display: flex;
@@ -239,7 +246,7 @@
width: 100%;
}
- .d-icon-question-circle {
+ .tooltip {
cursor: pointer;
}
@@ -255,41 +262,6 @@
}
}
}
-
- &.high-trending-up, &.trending-up {
- .chart-trend, .data-point {
- color: $success;
- }
- }
-
- &.high-trending-down, &.trending-down {
- .chart-trend, .data-point {
- color: $danger;
- }
- }
-}
-
-.dashboard-table.activity-metrics {
- table {
- @media screen and (min-width: 400px) {
- table-layout: auto;
- }
- tr th {
- text-align: right;
- }
- }
-}
-
-.top-referred-topics, .trending-search {
- th:first-of-type {
- text-align: left;
- }
-}
-
-.top-referred-topics {
- .dashboard-table table {
- table-layout: auto;
- }
}
.dashboard-table {
@@ -351,6 +323,7 @@
text-align: center;
padding: 8px;
}
+
td.left {
text-align: left;
}
@@ -396,3 +369,14 @@
}
}
}
+
+.dashboard-table.activity-metrics {
+ table {
+ @media screen and (min-width: 400px) {
+ table-layout: auto;
+ }
+ tr th {
+ text-align: right;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/common/components/conditional-loading-section.scss b/app/assets/stylesheets/common/components/conditional-loading-section.scss
index e081dcde463..cb6c052b18d 100644
--- a/app/assets/stylesheets/common/components/conditional-loading-section.scss
+++ b/app/assets/stylesheets/common/components/conditional-loading-section.scss
@@ -1,5 +1,4 @@
.conditional-loading-section {
-
&.is-loading {
padding: 2em;
margin: 1em;
diff --git a/app/models/report.rb b/app/models/report.rb
index 744275b155e..d0ebb790e93 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -4,7 +4,8 @@ class Report
attr_accessor :type, :data, :total, :prev30Days, :start_date,
:end_date, :category_id, :group_id, :labels, :async,
- :prev_period, :facets, :limit, :processing, :average, :percent
+ :prev_period, :facets, :limit, :processing, :average, :percent,
+ :higher_is_better
def self.default_days
30
@@ -14,6 +15,9 @@ class Report
@type = type
@start_date ||= Report.default_days.days.ago.beginning_of_day
@end_date ||= Time.zone.now.end_of_day
+ @average = false
+ @percent = false
+ @higher_is_better = true
end
def self.cache_key(report)
@@ -54,7 +58,8 @@ class Report
labels: labels,
processing: self.processing,
average: self.average,
- percent: self.percent
+ percent: self.percent,
+ higher_is_better: self.higher_is_better
}.tap do |json|
json[:total] = total if total
json[:prev_period] = prev_period if prev_period
@@ -83,8 +88,9 @@ class Report
report.facets = opts[:facets] || [:total, :prev30Days]
report.limit = opts[:limit] if opts[:limit]
report.processing = false
- report.average = opts[:average] || false
- report.percent = opts[:percent] || false
+ report.average = opts[:average] if opts[:average]
+ report.percent = opts[:percent] if opts[:percent]
+ report.higher_is_better = opts[:higher_is_better] if opts[:higher_is_better]
report
end
@@ -278,6 +284,7 @@ class Report
end
def self.report_time_to_first_response(report)
+ report.higher_is_better = false
report.data = []
Topic.time_to_first_response_per_day(report.start_date, report.end_date, category_id: report.category_id).each do |r|
report.data << { x: Date.parse(r["date"]), y: r["hours"].to_f.round(2) }
diff --git a/test/javascripts/acceptance/dashboard-next-test.js.es6 b/test/javascripts/acceptance/dashboard-next-test.js.es6
index 12eed20c243..46d50d0087a 100644
--- a/test/javascripts/acceptance/dashboard-next-test.js.es6
+++ b/test/javascripts/acceptance/dashboard-next-test.js.es6
@@ -12,5 +12,11 @@ QUnit.test("Visit dashboard next page", assert => {
andThen(() => {
assert.ok($('.dashboard-next').length, "has dashboard-next class");
+
+ assert.ok($('.dashboard-mini-chart.signups').length, "has a signups chart");
+ assert.ok($('.dashboard-mini-chart.posts').length, "has a posts chart");
+ assert.ok($('.dashboard-mini-chart.dau_by_mau').length, "has a dau_by_mau chart");
+ assert.ok($('.dashboard-mini-chart.daily_engaged_users').length, "has a daily_engaged_users chart");
+ assert.ok($('.dashboard-mini-chart.new_contributors').length, "has a new_contributors chart");
});
});
diff --git a/test/javascripts/models/report-test.js.es6 b/test/javascripts/models/report-test.js.es6
index e9661eadb6b..a2544836e09 100644
--- a/test/javascripts/models/report-test.js.es6
+++ b/test/javascripts/models/report-test.js.es6
@@ -1,63 +1,180 @@
-import Report from 'admin/models/report';
+import Report from "admin/models/report";
QUnit.module("Report");
function reportWithData(data) {
return Report.create({
- type: 'topics',
- data: _.map(data, function(val, index) {
- return { x: moment().subtract(index, "days").format('YYYY-MM-DD'), y: val };
+ type: "topics",
+ data: _.map(data, (val, index) => {
+ return { x: moment().subtract(index, "days").format("YYYY-MM-DD"), y: val };
})
});
}
QUnit.test("counts", assert => {
- var report = reportWithData([5, 4, 3, 2, 1, 100, 99, 98, 1000]);
+ const report = reportWithData([5, 4, 3, 2, 1, 100, 99, 98, 1000]);
- assert.equal(report.get('todayCount'), 5);
- assert.equal(report.get('yesterdayCount'), 4);
+ assert.equal(report.get("todayCount"), 5);
+ assert.equal(report.get("yesterdayCount"), 4);
assert.equal(report.valueFor(2, 4), 6, "adds the values for the given range of days, inclusive");
- assert.equal(report.get('lastSevenDaysCount'), 307, "sums 7 days excluding today");
+ assert.equal(report.get("lastSevenDaysCount"), 307, "sums 7 days excluding today");
report.set("method", "average");
assert.equal(report.valueFor(2, 4), 2, "averages the values for the given range of days");
});
QUnit.test("percentChangeString", assert => {
- var report = reportWithData([]);
+ const report = reportWithData([]);
- assert.equal(report.percentChangeString(8, 5), "+60%", "value increased");
- assert.equal(report.percentChangeString(2, 8), "-75%", "value decreased");
+ assert.equal(report.percentChangeString(5, 8), "+60%", "value increased");
+ assert.equal(report.percentChangeString(8, 2), "-75%", "value decreased");
assert.equal(report.percentChangeString(8, 8), "0%", "value unchanged");
- assert.blank(report.percentChangeString(8, 0), "returns blank when previous value was 0");
- assert.equal(report.percentChangeString(0, 8), "-100%", "yesterday was 0");
+ assert.blank(report.percentChangeString(0, 8), "returns blank when previous value was 0");
+ assert.equal(report.percentChangeString(8, 0), "-100%", "yesterday was 0");
assert.blank(report.percentChangeString(0, 0), "returns blank when both were 0");
});
QUnit.test("yesterdayCountTitle with valid values", assert => {
- var title = reportWithData([6,8,5,2,1]).get('yesterdayCountTitle');
- assert.ok(title.indexOf('+60%') !== -1);
+ const title = reportWithData([6,8,5,2,1]).get("yesterdayCountTitle");
+ assert.ok(title.indexOf("+60%") !== -1);
assert.ok(title.match(/Was 5/));
});
QUnit.test("yesterdayCountTitle when two days ago was 0", assert => {
- var title = reportWithData([6,8,0,2,1]).get('yesterdayCountTitle');
- assert.equal(title.indexOf('%'), -1);
+ const title = reportWithData([6,8,0,2,1]).get("yesterdayCountTitle");
+ assert.equal(title.indexOf("%"), -1);
assert.ok(title.match(/Was 0/));
});
-QUnit.test("sevenDayCountTitle", assert => {
- var title = reportWithData([100,1,1,1,1,1,1,1,2,2,2,2,2,2,2,100,100]).get('sevenDayCountTitle');
+QUnit.test("sevenDaysCountTitle", assert => {
+ const title = reportWithData([100,1,1,1,1,1,1,1,2,2,2,2,2,2,2,100,100]).get("sevenDaysCountTitle");
assert.ok(title.match(/-50%/));
assert.ok(title.match(/Was 14/));
});
-QUnit.test("thirtyDayCountTitle", assert => {
- var report = reportWithData([5,5,5,5]);
- report.set('prev30Days', 10);
- var title = report.get('thirtyDayCountTitle');
+QUnit.test("thirtyDaysCountTitle", assert => {
+ const report = reportWithData([5,5,5,5]);
+ report.set("prev30Days", 10);
+ const title = report.get("thirtyDaysCountTitle");
- assert.ok(title.indexOf('+50%') !== -1);
+ assert.ok(title.indexOf("+50%") !== -1);
assert.ok(title.match(/Was 10/));
});
+
+QUnit.test("sevenDaysTrend", assert => {
+ let report;
+ let trend;
+
+ report = reportWithData([0, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1]);
+ trend = report.get("sevenDaysTrend");
+ assert.ok(trend === "no-change");
+
+ report = reportWithData([0, 1,1,1,1,1,1,1, 0,0,0,0,0,0,0]);
+ trend = report.get("sevenDaysTrend");
+ assert.ok(trend === "high-trending-up");
+
+ report = reportWithData([0, 1,1,1,1,1,1,1, 1,1,1,1,1,1,0]);
+ trend = report.get("sevenDaysTrend");
+ assert.ok(trend === "trending-up");
+
+ report = reportWithData([0, 0,0,0,0,0,0,0, 1,1,1,1,1,1,1]);
+ trend = report.get("sevenDaysTrend");
+ assert.ok(trend === "high-trending-down");
+
+ report = reportWithData([0, 1,1,1,1,1,1,0, 1,1,1,1,1,1,1]);
+ trend = report.get("sevenDaysTrend");
+ assert.ok(trend === "trending-down");;
+});
+
+QUnit.test("yesterdayTrend", assert => {
+ let report;
+ let trend;
+
+ report = reportWithData([0, 1, 1]);
+ trend = report.get("yesterdayTrend");
+ assert.ok(trend === "no-change");
+
+ report = reportWithData([0, 1, 0]);
+ trend = report.get("yesterdayTrend");
+ assert.ok(trend === "high-trending-up");
+
+ report = reportWithData([0, 1.1, 1]);
+ trend = report.get("yesterdayTrend");
+ assert.ok(trend === "trending-up");
+
+ report = reportWithData([0, 0, 1]);
+ trend = report.get("yesterdayTrend");
+ assert.ok(trend === "high-trending-down");
+
+ report = reportWithData([0, 1, 1.1]);
+ trend = report.get("yesterdayTrend");
+ assert.ok(trend === "trending-down");;
+});
+
+QUnit.test("thirtyDaysTrend", assert => {
+ let report;
+ let trend;
+
+ report = reportWithData([0, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]);
+ report.set("prev30Days", 30);
+ trend = report.get("thirtyDaysTrend");
+ assert.ok(trend === "no-change");
+
+ report = reportWithData([0, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]);
+ report.set("prev30Days", 0);
+ trend = report.get("thirtyDaysTrend");
+ assert.ok(trend === "high-trending-up");
+
+ report = reportWithData([0, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]);
+ report.set("prev30Days", 25);
+ trend = report.get("thirtyDaysTrend");
+ assert.ok(trend === "trending-up");
+
+ report = reportWithData([0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
+ report.set("prev30Days", 60);
+ trend = report.get("thirtyDaysTrend");
+ assert.ok(trend === "high-trending-down");
+
+ report = reportWithData([0, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0]);
+ report.set("prev30Days", 35);
+ trend = report.get("thirtyDaysTrend");
+ assert.ok(trend === "trending-down");;
+});
+
+QUnit.test("higher is better false", assert => {
+ let report;
+ let trend;
+
+ report = reportWithData([0, 1, 0]);
+ report.set("higher_is_better", false);
+ trend = report.get("yesterdayTrend");
+ assert.ok(trend === "high-trending-down");
+
+ report = reportWithData([0, 1.1, 1]);
+ report.set("higher_is_better", false);
+ trend = report.get("yesterdayTrend");
+ assert.ok(trend === "trending-down");
+
+ report = reportWithData([0, 0, 1]);
+ report.set("higher_is_better", false);
+ trend = report.get("yesterdayTrend");
+ assert.ok(trend === "high-trending-up");
+
+ report = reportWithData([0, 1, 1.1]);
+ report.set("higher_is_better", false);
+ trend = report.get("yesterdayTrend");
+ assert.ok(trend === "trending-up");;
+});
+
+QUnit.test("average", assert => {
+ let report;
+
+ report = reportWithData([5, 5, 5, 5, 5, 5, 5, 5]);
+
+ report.set("average", true);
+ assert.ok(report.get("lastSevenDaysCount") === 5);
+
+ report.set("average", false);
+ assert.ok(report.get("lastSevenDaysCount") === 35);
+});