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 f5ff432a84d..d1885cddcb8 100644 --- a/app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6 +++ b/app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6 @@ -4,21 +4,36 @@ import AsyncReport from "admin/mixins/async-report"; import Report from "admin/models/report"; import { number } from 'discourse/lib/formatter'; +function collapseWeekly(data, average) { + let aggregate = []; + let bucket, i; + let offset = data.length % 7; + for(i = offset; i < data.length; i++) { + + if (bucket && (i % 7 === offset)) { + if (average) { + bucket.y = parseFloat((bucket.y / 7.0).toFixed(2)); + } + aggregate.push(bucket); + bucket = null; + } + + bucket = bucket || { x: data[i].x, y: 0 }; + bucket.y += data[i].y; + } + return aggregate; +} + export default Ember.Component.extend(AsyncReport, { classNames: ["dashboard-mini-chart"], - classNameBindings: ["thirtyDayTrend", "oneDataPoint"], + classNameBindings: ["trend", "oneDataPoint"], isLoading: true, - thirtyDayTrend: Ember.computed.alias("report.thirtyDayTrend"), + trend: Ember.computed.alias("report.trend"), oneDataPoint: false, backgroundColor: "rgba(200,220,240,0.3)", borderColor: "#08C", average: false, - - willDestroyEelement() { - this._super(); - - this.messageBus.unsubscribe(this.get("dataSource")); - }, + total: 0, @computed("dataSourceName") dataSource(dataSourceName) { @@ -27,9 +42,9 @@ export default Ember.Component.extend(AsyncReport, { } }, - @computed("thirtyDayTrend") - trendIcon(thirtyDayTrend) { - switch (thirtyDayTrend) { + @computed("trend") + trendIcon(trend) { + switch (trend) { case "trending-up": return "angle-up"; case "trending-down": @@ -47,7 +62,7 @@ export default Ember.Component.extend(AsyncReport, { this.set("isLoading", true); let payload = { - data: { async: true } + data: { async: true, facets: ["prev_period"] } }; if (this.get("startDate")) { @@ -58,11 +73,18 @@ export default Ember.Component.extend(AsyncReport, { payload.data.end_date = this.get("endDate").format('YYYY-MM-DD[T]HH:mm:ss.SSSZZ'); } + if (this._chart) { + this._chart.destroy(); + this._chart = null; + } + + this.set("report", null); + ajax(this.get("dataSource"), payload) .then((response) => { - // if (!Ember.isEmpty(response.report.data)) { - this._setPropertiesFromReport(Report.create(response.report)); - // } + this.set('reportKey', response.report.report_key); + + this.loadReport(response.report); }) .finally(() => { if (this.get("oneDataPoint")) { @@ -77,6 +99,19 @@ export default Ember.Component.extend(AsyncReport, { }); }, + loadReport(report) { + if (report.data) { + Report.fillMissingDates(report); + + if (report.data && report.data.length > 40) { + report.data = collapseWeekly(report.data, this.get("average")); + } + + const model = Report.create(report); + this._setPropertiesFromReport(model); + } + }, + renderReport() { if (!this.element || this.isDestroying || this.isDestroyed) { return; } if (this.get("oneDataPoint")) return; @@ -96,6 +131,9 @@ export default Ember.Component.extend(AsyncReport, { }] }; + if (this._chart) { + this._chart.destroy(); + } this._chart = new window.Chart(context, this._buildChartConfig(data)); }); }, @@ -105,7 +143,6 @@ export default Ember.Component.extend(AsyncReport, { this.get("startDate").isSame(this.get("endDate"), "day"); report.set("average", this.get("average")); - this.setProperties({ oneDataPoint, report }); }, diff --git a/app/assets/javascripts/admin/components/dashboard-table.js.es6 b/app/assets/javascripts/admin/components/dashboard-table.js.es6 index b6748367079..8d6ba5eb141 100644 --- a/app/assets/javascripts/admin/components/dashboard-table.js.es6 +++ b/app/assets/javascripts/admin/components/dashboard-table.js.es6 @@ -13,9 +13,8 @@ export default Ember.Component.extend(AsyncReport, { 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]) ]; + return [ x[0], number(x[1]), x[2] ]; }); }, @@ -25,6 +24,10 @@ export default Ember.Component.extend(AsyncReport, { return Ember.makeArray(report.labels); }, + loadReport(report_json) { + this._setPropertiesFromReport(Report.create(report_json)); + }, + fetchReport() { this.set("isLoading", true); @@ -40,7 +43,8 @@ export default Ember.Component.extend(AsyncReport, { ajax(this.get("dataSource"), payload) .then((response) => { - this._setPropertiesFromReport(Report.create(response.report)); + this.set('reportKey', response.report.report_key); + this.loadReport(response.report); }).finally(() => { if (!Ember.isEmpty(this.get("report.data"))) { this.set("isLoading", false); 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 9fc8a021b64..af8a0d26dc8 100644 --- a/app/assets/javascripts/admin/controllers/admin-dashboard-next.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-dashboard-next.js.es6 @@ -37,30 +37,34 @@ export default Ember.Controller.extend({ @computed("period") startDate(period) { + let fullDay = moment().utc().subtract(1, "day"); + switch (period) { case "yearly": - return moment().subtract(1, "year").startOf("day"); + return fullDay.subtract(1, "year").startOf("day"); break; case "quarterly": - return moment().subtract(3, "month").startOf("day"); + return fullDay.subtract(3, "month").startOf("day"); break; case "weekly": - return moment().subtract(1, "week").startOf("day"); + return fullDay.subtract(1, "week").startOf("day"); break; case "monthly": - return moment().subtract(1, "month").startOf("day"); - break; - case "daily": - return moment().startOf("day"); + return fullDay.subtract(1, "month").startOf("day"); break; default: return null; } }, - @computed("period") - endDate(period) { - return period === "all" ? null : moment().endOf("day"); + @computed() + lastWeek() { + return moment().utc().endOf("day").subtract(1, "week"); + }, + + @computed() + endDate() { + return moment().utc().subtract(1, "day").endOf("day"); }, @computed("updated_at") diff --git a/app/assets/javascripts/admin/mixins/async-report.js.es6 b/app/assets/javascripts/admin/mixins/async-report.js.es6 index 5206f651862..31c9c501895 100644 --- a/app/assets/javascripts/admin/mixins/async-report.js.es6 +++ b/app/assets/javascripts/admin/mixins/async-report.js.es6 @@ -1,5 +1,4 @@ import computed from 'ember-addons/ember-computed-decorators'; -import Report from "admin/models/report"; export default Ember.Mixin.create({ classNameBindings: ["isLoading"], @@ -9,23 +8,27 @@ export default Ember.Mixin.create({ 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(); + this._channel = this.get("dataSource"); + this._callback = (report) => { + if (report.report_key = this.get("reportKey")) { + Em.run.next(() => { + if (report.report_key = this.get("reportKey")) { + this.loadReport(report); + console.log(report); + this.set("isLoading", false); + this.renderReport(); + } + }); } - }); + }; + // in case we did not subscribe in time ensure we always grab the + // last thing on the channel + this.messageBus.subscribe(this._channel, this._callback, -2); + }, + + willDestroyElement() { + this._super(); + this.messageBus.unsubscribe(this._channel, this._callback); }, didInsertElement() { @@ -38,12 +41,13 @@ export default Ember.Mixin.create({ didUpdateAttrs() { this._super(); - this.fetchReport(); }, renderReport() {}, + loadReport() {}, + @computed("dataSourceName") dataSource(dataSourceName) { return `/admin/reports/${dataSourceName}`; diff --git a/app/assets/javascripts/admin/models/report.js.es6 b/app/assets/javascripts/admin/models/report.js.es6 index 1113f2e5d1a..a035aa3a04b 100644 --- a/app/assets/javascripts/admin/models/report.js.es6 +++ b/app/assets/javascripts/admin/models/report.js.es6 @@ -90,6 +90,34 @@ const Report = Discourse.Model.extend({ } }, + @computed('data') + currentTotal(data){ + return _.reduce(data, (cur, pair) => cur + pair.y, 0); + }, + + @computed('data', 'currentTotal') + currentAverage(data, total) { + return parseFloat((total / parseFloat(data.length)).toFixed(1)); + }, + + @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"; + } + }, + @computed('prev30Days', 'lastThirtyDaysCount') thirtyDayTrend(prev30Days, lastThirtyDaysCount) { const currentPeriod = lastThirtyDaysCount; @@ -138,6 +166,20 @@ const Report = Discourse.Model.extend({ } }, + @computed('prev_period', 'currentTotal', 'currentAverage') + trendTitle(prev, currentTotal, currentAverage) { + let current = this.get('average') ? currentAverage : currentTotal; + let percent = this.percentChangeString(current, prev); + + if (this.get('average')) { + prev = prev.toFixed(1); + current += '%'; + prev += '%'; + } + + return I18n.t('admin.dashboard.reports.trend_title', {percent: percent, prev: prev, current: current}); + }, + changeTitle(val1, val2, prevPeriodString) { const percentChange = this.percentChangeString(val1, val2); var title = ""; @@ -176,6 +218,14 @@ const Report = Discourse.Model.extend({ Report.reopenClass({ + fillMissingDates(report) { + if (report.data.length > 0) { + const startDateFormatted = moment.utc(report.start_date).format('YYYY-MM-DD'); + const endDateFormatted = moment.utc(report.end_date).format('YYYY-MM-DD'); + report.data = fillMissingDates(report.data, startDateFormatted, endDateFormatted); + } + }, + find(type, startDate, endDate, categoryId, groupId) { return ajax("/admin/reports/" + type, { data: { @@ -186,11 +236,7 @@ Report.reopenClass({ } }).then(json => { // Add zero values for missing dates - if (json.report.data.length > 0) { - const startDateFormatted = moment(json.report.start_date).utc().format('YYYY-MM-DD'); - const endDateFormatted = moment(json.report.end_date).utc().format('YYYY-MM-DD'); - json.report.data = fillMissingDates(json.report.data, startDateFormatted, endDateFormatted); - } + Report.filleMissingDates(json.report); const model = Report.create({ type: type }); model.setProperties(json.report); 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 a0b22b0c8c6..ad087a64366 100644 --- a/app/assets/javascripts/admin/templates/components/dashboard-mini-chart.hbs +++ b/app/assets/javascripts/admin/templates/components/dashboard-mini-chart.hbs @@ -9,9 +9,15 @@
- - {{number report.lastThirtyDaysCount}} - + {{#if average}} + + {{report.currentAverage}}% + + {{else}} + + {{number report.currentTotal noTitle="true"}} + + {{/if}} {{#if trendIcon}} {{d-icon trendIcon}} diff --git a/app/assets/javascripts/admin/templates/dashboard_next.hbs b/app/assets/javascripts/admin/templates/dashboard_next.hbs index 1ee2768bc0c..b662dcd230f 100644 --- a/app/assets/javascripts/admin/templates/dashboard_next.hbs +++ b/app/assets/javascripts/admin/templates/dashboard_next.hbs @@ -19,7 +19,7 @@ endDate=endDate}} {{dashboard-mini-chart - dataSourceName="new_contributors" + dataSourceName="posts" startDate=startDate endDate=endDate}} @@ -35,9 +35,10 @@ endDate=endDate}} {{dashboard-mini-chart - dataSourceName="inactive_users" + dataSourceName="new_contributors" startDate=startDate endDate=endDate}} +
@@ -118,7 +119,7 @@
{{dashboard-table-trending-search dataSourceName="trending_search" - startDate=startDate + startDate=lastWeek endDate=endDate}}
diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index 17e52f6159b..2fae3c38d2b 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -22,11 +22,17 @@ class Admin::ReportsController < Admin::AdminController group_id = nil end + facets = nil + if Array === params[:facets] + facets = params[:facets].map { |s| s.to_s.to_sym } + end + report = Report.find(report_type, start_date: start_date, end_date: end_date, category_id: category_id, group_id: group_id, + facets: facets, async: params[:async]) raise Discourse::NotFound if report.blank? diff --git a/app/jobs/regular/retrieve_report.rb b/app/jobs/regular/retrieve_report.rb index 15a37377cb0..ed24b124db7 100644 --- a/app/jobs/regular/retrieve_report.rb +++ b/app/jobs/regular/retrieve_report.rb @@ -13,12 +13,14 @@ module Jobs report.end_date = args["end_date"].to_date if args["end_date"] report.category_id = args["category_id"] if args["category_id"] report.group_id = args["group_id"] if args["group_id"] + report.facets = args["facets"].map(&:to_sym) if args["facets"] Report.send("report_#{type}", report) - Discourse.cache.write(Report.cache_key(report), report.as_json, force: true, expires_in: 30.minutes) + json = report.as_json + Discourse.cache.write(Report.cache_key(report), json, force: true, expires_in: 30.minutes) - MessageBus.publish("/admin/reports/#{type}", report.as_json, user_ids: User.staff.pluck(:id)) + MessageBus.publish("/admin/reports/#{type}", json, user_ids: User.staff.pluck(:id)) end end end diff --git a/app/models/report.rb b/app/models/report.rb index 856bcbd093d..84dd221f72f 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -3,7 +3,8 @@ require_dependency 'topic_subtype' class Report attr_accessor :type, :data, :total, :prev30Days, :start_date, - :end_date, :category_id, :group_id, :labels, :async + :end_date, :category_id, :group_id, :labels, :async, + :prev_period, :facets def self.default_days 30 @@ -16,7 +17,15 @@ class Report end def self.cache_key(report) - "reports:#{report.type}:#{report.start_date.to_date.strftime("%Y%m%d")}:#{report.end_date.to_date.strftime("%Y%m%d")}" + (+"reports:") << + [ + report.type, + report.category_id, + report.start_date.to_date.strftime("%Y%m%d"), + report.end_date.to_date.strftime("%Y%m%d"), + report.group_id, + report.facets + ].map(&:to_s).join(':') end def self.clear_cache @@ -33,14 +42,18 @@ class Report yaxis: I18n.t("reports.#{type}.yaxis"), description: I18n.t("reports.#{type}.description"), data: data, - total: total, start_date: start_date, end_date: end_date, category_id: category_id, group_id: group_id, prev30Days: self.prev30Days, + report_key: Report.cache_key(self), labels: labels }.tap do |json| + json[:total] = total if total + json[:prev_period] = prev_period if prev_period + json[:prev30Days] = self.prev30Days if self.prev30Days + if type == 'page_view_crawler_reqs' json[:related_report] = Report.find('web_crawlers', start_date: start_date, end_date: end_date)&.as_json end @@ -61,6 +74,7 @@ class Report report.category_id = opts[:category_id] if opts[:category_id] report.group_id = opts[:group_id] if opts[:group_id] report.async = opts[:async] || false + report.facets = opts[:facets] || [:total, :prev30Days] report_method = :"report_#{type}" if respond_to?(report_method) @@ -152,10 +166,19 @@ class Report data = User.real.count_by_first_post(report.start_date, report.end_date) - prev30DaysData = User.real.count_by_first_post(report.start_date - 30.days, report.start_date) - report.prev30Days = prev30DaysData.sum { |k, v| v } + if report.facets.include?(:prev30Days) + prev30DaysData = User.real.count_by_first_post(report.start_date - 30.days, report.start_date) + report.prev30Days = prev30DaysData.sum { |k, v| v } + end - report.total = User.real.count_by_first_post + if report.facets.include?(:total) + report.total = User.real.count_by_first_post + end + + if report.facets.include?(:prev_period) + prev_period_data = User.real.count_by_first_post(report.start_date - (report.end_date - report.start_date), report.start_date) + report.prev_period = prev_period_data.sum { |k, v| v } + end data.each do |key, value| report.data << { x: key, y: value } @@ -166,11 +189,20 @@ class Report report.data = [] data = UserAction.count_daily_engaged_users(report.start_date, report.end_date) - prev30DaysData = UserAction.count_daily_engaged_users(report.start_date - 30.days, report.start_date) - report.total = UserAction.count_daily_engaged_users + if report.facets.include?(:prev30Days) + prev30DaysData = UserAction.count_daily_engaged_users(report.start_date - 30.days, report.start_date) + report.prev30Days = prev30DaysData.sum { |k, v| v } + end - report.prev30Days = prev30DaysData.sum { |k, v| v } + if report.facets.include?(:total) + report.total = UserAction.count_daily_engaged_users + end + + if report.facets.include?(:prev_period) + prev_data = UserAction.count_daily_engaged_users(report.start_date - (report.end_date - report.start_date), report.start_date) + report.prev_period = prev_data.sum { |k, v| v } + end data.each do |key, value| report.data << { x: key, y: value } @@ -186,7 +218,15 @@ class Report if data_point["mau"] == 0 0 else - ((data_point["dau"].to_f / data_point["mau"].to_f) * 100).ceil + ((data_point["dau"].to_f / data_point["mau"].to_f) * 100).ceil(2) + end + } + + dau_avg = Proc.new { |start_date, end_date| + data_points = UserVisit.count_by_active_users(start_date, end_date) + if !data_points.empty? + sum = data_points.sum { |data_point| compute_dau_by_mau.call(data_point) } + (sum.to_f / data_points.count.to_f).ceil(2) end } @@ -194,10 +234,12 @@ class Report report.data << { x: data_point["date"], y: compute_dau_by_mau.call(data_point) } end - prev_data_points = UserVisit.count_by_active_users(report.start_date - 30.days, report.start_date) - if !prev_data_points.empty? - sum = prev_data_points.sum { |data_point| compute_dau_by_mau.call(data_point) } - report.prev30Days = sum / prev_data_points.count + if report.facets.include?(:prev_period) + report.prev_period = dau_avg.call(report.start_date - (report.end_date - report.start_date), report.start_date) + end + + if report.facets.include?(:prev30Days) + report.prev30Days = dau_avg.call(report.start_date - 30.days, report.start_date) end end @@ -260,8 +302,23 @@ class Report end def self.add_counts(report, subject_class, query_column = 'created_at') - report.total = subject_class.count - report.prev30Days = subject_class.where("#{query_column} >= ? and #{query_column} < ?", report.start_date - 30.days, report.start_date).count + if report.facets.include?(:prev_period) + report.prev_period = subject_class + .where("#{query_column} >= ? and #{query_column} < ?", + (report.start_date - (report.end_date - report.start_date)), + report.start_date).count + end + + if report.facets.include?(:total) + report.total = subject_class.count + end + + if report.facets.include?(:prev30Days) + report.prev30Days = subject_class + .where("#{query_column} >= ? and #{query_column} < ?", + report.start_date - 30.days, + report.start_date).count + end end def self.report_users_by_trust_level(report) @@ -359,24 +416,39 @@ class Report def self.report_trending_search(report) report.data = [] - trends = SearchLog.select("term, - COUNT(*) AS searches, - SUM(CASE + select_sql = <<~SQL + term, + COUNT(*) AS searches, + SUM(CASE WHEN search_result_id IS NOT NULL THEN 1 ELSE 0 END) AS click_through, - COUNT(DISTINCT ip_address) AS unique") + COUNT(DISTINCT ip_address) AS unique_searches + SQL + + trends = SearchLog.select(select_sql) .where('created_at > ? AND created_at <= ?', report.start_date, report.end_date) .group(:term) - .order('COUNT(DISTINCT ip_address) DESC, COUNT(*) DESC') + .order('unique_searches DESC, click_through ASC, term ASC') .limit(20).to_a - report.labels = [:term, :searches, :unique].map { |key| + report.labels = [:term, :searches, :click_through].map { |key| I18n.t("reports.trending_search.labels.#{key}") } trends.each do |trend| - report.data << [trend.term, trend.searches, trend.unique] + ctr = + if trend.click_through == 0 + 0 + else + trend.click_through.to_f / trend.searches.to_f + end + + report.data << [ + trend.term, + trend.unique_searches, + (ctr * 100).ceil(1).to_s + "%" + ] end end end diff --git a/app/models/topic.rb b/app/models/topic.rb index 6b01d9e4535..e986d4ebdb2 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -459,7 +459,8 @@ class Topic < ActiveRecord::Base end def self.listable_count_per_day(start_date, end_date, category_id = nil) - result = listable_topics.smart_group_by_date("topics.created_at", start_date, end_date) + result = listable_topics.where("topics.created_at >= ? AND topics.created_at <= ?", start_date, end_date) + result = result.group('date(topics.created_at)').order('date(topics.created_at)') result = result.where(category_id: category_id) if category_id result.count end diff --git a/app/models/user.rb b/app/models/user.rb index e549cf2f165..c541bf26caf 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -880,7 +880,9 @@ class User < ActiveRecord::Base result = self if start_date && end_date - result = result.smart_group_by_date("users.created_at", start_date, end_date) + result = result.group("date(users.created_at)") + result = result.where("users.created_at >= ? AND users.created_at <= ?", start_date, end_date) + result = result.order('date(users.created_at)') end if group_id @@ -895,7 +897,9 @@ class User < ActiveRecord::Base result = joins('INNER JOIN user_stats AS us ON us.user_id = users.id') if start_date && end_date - result = result.smart_group_by_date("us.first_post_created_at", start_date, end_date) + result = result.group("date(us.first_post_created_at)") + result = result.where("us.first_post_created_at > ? AND us.first_post_created_at < ?", start_date, end_date) + result = result.order("date(us.first_post_created_at)") end result.count diff --git a/app/models/user_action.rb b/app/models/user_action.rb index 22b27b8cb1c..baa540c252f 100644 --- a/app/models/user_action.rb +++ b/app/models/user_action.rb @@ -127,7 +127,9 @@ SQL .where(action_type: [LIKE, NEW_TOPIC, REPLY, NEW_PRIVATE_MESSAGE]) if start_date && end_date - result = result.smart_group_by_date(:created_at, start_date, end_date) + result = result.group('date(created_at)') + result = result.where('created_at > ? AND created_at < ?', start_date, end_date) + result = result.order('date(created_at)') end result.count diff --git a/app/models/user_visit.rb b/app/models/user_visit.rb index be4bd917c1b..c4e377cf4c5 100644 --- a/app/models/user_visit.rb +++ b/app/models/user_visit.rb @@ -1,6 +1,4 @@ class UserVisit < ActiveRecord::Base - include DateGroupable - def self.counts_by_day_query(start_date, end_date, group_id = nil) result = where('visited_at >= ? and visited_at <= ?', start_date.to_date, end_date.to_date) @@ -13,16 +11,14 @@ class UserVisit < ActiveRecord::Base end def self.count_by_active_users(start_date, end_date) - aggregation_unit = aggregation_unit_for_period(start_date, end_date) - sql = <= :start_date::DATE AND user_visits.visited_at < :end_date::DATE + GROUP BY date_trunc('day', user_visits.visited_at)::DATE + ORDER BY date_trunc('day', user_visits.visited_at)::DATE ) SELECT date, dau, @@ -33,7 +29,7 @@ class UserVisit < ActiveRecord::Base FROM dau SQL - UserVisit.exec_sql(sql).to_a + UserVisit.exec_sql(sql, start_date: start_date, end_date: end_date).to_a end # A count of visits in a date range by day diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 9bb8fc6b079..525ed566fcc 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2748,6 +2748,7 @@ en: activity_metrics: Activity Metrics reports: + trend_title: "%{percent} change. Currently %{current}, was %{prev} in previous period." today: "Today" yesterday: "Yesterday" last_7_days: "Last 7 Days" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 299e8e7b7d3..127bcb635b0 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -880,6 +880,7 @@ en: title: "Posts" xaxis: "Day" yaxis: "Number of new posts" + description: "New posts created during this period" likes: title: "Likes" xaxis: "Day" @@ -914,7 +915,7 @@ en: labels: term: Term searches: Searches - unique: Unique + click_through: Click Through Rate emails: title: "Emails Sent" xaxis: "Day" diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb index 45a0772e57a..ba67a0a7329 100644 --- a/spec/models/report_spec.rb +++ b/spec/models/report_spec.rb @@ -30,6 +30,7 @@ describe Report do describe "topics" do before do + Report.clear_cache freeze_time DateTime.parse('2017-03-01 12:00') ((0..32).to_a + [60, 61, 62, 63]).each do |i| @@ -37,11 +38,21 @@ describe Report do end end - subject(:json) { Report.find("topics").as_json } - it "counts the correct records" do + json = Report.find("topics").as_json expect(json[:data].size).to eq(31) expect(json[:prev30Days]).to eq(3) + + # lets make sure we can ask for the correct options for the report + json = Report.find("topics", + start_date: 5.days.ago.beginning_of_day, + end_date: 1.day.ago.end_of_day, + facets: [:prev_period] + ).as_json + + expect(json[:prev_period]).to eq(5) + expect(json[:data].length).to eq(5) + expect(json[:prev30Days]).to eq(nil) end end end @@ -321,7 +332,9 @@ describe Report do context "with different searches" do before do SearchLog.log(term: 'ruby', search_type: :header, ip_address: '127.0.0.1') - SearchLog.log(term: 'ruby', search_type: :header, ip_address: '127.0.0.1', user_id: Fabricate(:user).id) + + SearchLog.create!(term: 'ruby', search_result_id: 1, search_type: 1, ip_address: '127.0.0.1', user_id: Fabricate(:user).id) + SearchLog.log(term: 'ruby', search_type: :header, ip_address: '127.0.0.2') SearchLog.log(term: 'php', search_type: :header, ip_address: '127.0.0.1') end @@ -332,12 +345,11 @@ describe Report do it "returns a report with data" do expect(report.data[0][0]).to eq("ruby") - expect(report.data[0][1]).to eq(3) - expect(report.data[0][2]).to eq(2) + expect(report.data[0][1]).to eq(2) + expect(report.data[0][2]).to eq('33.4%') expect(report.data[1][0]).to eq("php") expect(report.data[1][1]).to eq(1) - expect(report.data[1][2]).to eq(1) end end end @@ -373,7 +385,7 @@ describe Report do it "returns a report with data" do expect(report.data.first[:y]).to eq(100) - expect(report.data.last[:y]).to eq(34) + expect(report.data.last[:y]).to eq(33.34) expect(report.prev30Days).to eq(75) end end diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb index aed6e653ddc..72eb939feec 100644 --- a/spec/models/topic_spec.rb +++ b/spec/models/topic_spec.rb @@ -1746,7 +1746,7 @@ describe Topic do describe '#listable_count_per_day' do before(:each) do - freeze_time + freeze_time DateTime.parse('2017-03-01 12:00') Fabricate(:topic) Fabricate(:topic, created_at: 1.day.ago) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index fe2299cb7bf..e1471394e48 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -94,7 +94,7 @@ describe User do describe '#count_by_signup_date' do before(:each) do User.destroy_all - freeze_time + freeze_time DateTime.parse('2017-02-01 12:00') Fabricate(:user) Fabricate(:user, created_at: 1.day.ago) Fabricate(:user, created_at: 1.day.ago) diff --git a/test/javascripts/fixtures/posts.js.es6 b/test/javascripts/fixtures/posts.js.es6 new file mode 100644 index 00000000000..904ba874324 --- /dev/null +++ b/test/javascripts/fixtures/posts.js.es6 @@ -0,0 +1,18 @@ +export default { + "/admin/reports/posts": { + "report": { + "type": "topics", + "title": "Topics", + "xaxis": "Day", + "yaxis": "Number of new posts", + "data": null, + "total": null, + "start_date": "2018-03-26T00:00:00.000Z", + "end_date": "2018-04-25T23:59:59.999Z", + "category_id": null, + "group_id": null, + "prev30Days": 0, + "labels": null + } + } +}; diff --git a/test/javascripts/models/report-test.js.es6 b/test/javascripts/models/report-test.js.es6 index 1e4f73d89ea..e9661eadb6b 100644 --- a/test/javascripts/models/report-test.js.es6 +++ b/test/javascripts/models/report-test.js.es6 @@ -60,4 +60,4 @@ QUnit.test("thirtyDayCountTitle", assert => { assert.ok(title.indexOf('+50%') !== -1); assert.ok(title.match(/Was 10/)); -}); \ No newline at end of file +});