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