new dashboard quality pass (code, tests and UI)

This commit is contained in:
Joffrey JAFFEUX 2018-05-17 22:44:33 +02:00 committed by GitHub
parent 0639b902dc
commit af548c23c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 394 additions and 234 deletions

View File

@ -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
}
}]

View File

@ -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'),

View File

@ -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"));

View File

@ -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;

View File

@ -11,11 +11,11 @@
{{number report.yesterdayCount}} {{d-icon "caret-up" class="up"}} {{d-icon "caret-down" class="down"}}
</td>
<td class="value {{report.sevenDayTrend}}" title={{report.sevenDayCountTitle}}>
<td class="value {{report.sevenDaysTrend}}" title={{report.sevenDaysCountTitle}}>
{{number report.lastSevenDaysCount}} {{d-icon "caret-up" class="up"}} {{d-icon "caret-down" class="down"}}
</td>
<td class="value {{report.thirtyDayTrend}}" title={{report.thirtyDayCountTitle}}>
<td class="value {{report.thirtyDaysTrend}}" title={{report.thirtyDaysCountTitle}}>
{{number report.lastThirtyDaysCount}} {{d-icon "caret-up" class="up"}} {{d-icon "caret-down" class="down"}}
</td>

View File

@ -4,41 +4,49 @@
<p>{{model.description}}</p>
{{/if}}
<div class="admin-reports-filter">
{{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"}}
<div class="report-container">
<div class="visualization">
{{#conditional-loading-spinner condition=refreshing}}
<div class='view-options'>
{{#if viewingTable}}
{{i18n 'admin.dashboard.reports.view_table'}}
{{else}}
<a href {{action "viewAsTable"}}>{{i18n 'admin.dashboard.reports.view_table'}}</a>
{{/if}}
|
{{#if viewingGraph}}
{{i18n 'admin.dashboard.reports.view_graph'}}
{{else}}
<a href {{action "viewAsGraph"}}>{{i18n 'admin.dashboard.reports.view_graph'}}</a>
{{/if}}
</div>
{{#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}}
</div>
<div class="filters">
<span>
{{i18n 'admin.dashboard.reports.start_date'}} {{date-picker-past value=startDate defaultDate=startDate}}
</span>
<span>
{{i18n 'admin.dashboard.reports.end_date'}} {{date-picker-past value=endDate defaultDate=endDate}}
</span>
{{#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"}}
</div>
</div>
<div class='view-options'>
{{#if viewingTable}}
{{i18n 'admin.dashboard.reports.view_table'}}
{{else}}
<a href {{action "viewAsTable"}}>{{i18n 'admin.dashboard.reports.view_table'}}</a>
{{/if}}
|
{{#if viewingGraph}}
{{i18n 'admin.dashboard.reports.view_graph'}}
{{else}}
<a href {{action "viewAsGraph"}}>{{i18n 'admin.dashboard.reports.view_graph'}}</a>
{{/if}}
</div>
{{#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}}

View File

@ -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%); }

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -1,5 +1,4 @@
.conditional-loading-section {
&.is-loading {
padding: 2em;
margin: 1em;

View File

@ -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) }

View File

@ -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");
});
});

View File

@ -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);
});