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"}} - + {{number report.lastSevenDaysCount}} {{d-icon "caret-up" class="up"}} {{d-icon "caret-down" class="down"}} - + {{number report.lastThirtyDaysCount}} {{d-icon "caret-up" class="up"}} {{d-icon "caret-down" class="down"}} diff --git a/app/assets/javascripts/admin/templates/reports.hbs b/app/assets/javascripts/admin/templates/reports.hbs index eab27824252..26b5bfb4fe6 100644 --- a/app/assets/javascripts/admin/templates/reports.hbs +++ b/app/assets/javascripts/admin/templates/reports.hbs @@ -4,41 +4,49 @@

{{model.description}}

{{/if}} -
- {{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 viewingTable}} + {{i18n 'admin.dashboard.reports.view_table'}} + {{else}} + {{i18n 'admin.dashboard.reports.view_table'}} + {{/if}} + | + {{#if viewingGraph}} + {{i18n 'admin.dashboard.reports.view_graph'}} + {{else}} + {{i18n 'admin.dashboard.reports.view_graph'}} + {{/if}} +
+ + {{#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"}} +
- -
- {{#if viewingTable}} - {{i18n 'admin.dashboard.reports.view_table'}} - {{else}} - {{i18n 'admin.dashboard.reports.view_table'}} - {{/if}} - | - {{#if viewingGraph}} - {{i18n 'admin.dashboard.reports.view_graph'}} - {{else}} - {{i18n 'admin.dashboard.reports.view_graph'}} - {{/if}} -
- -{{#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); +});