mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-30 10:58:31 -05:00
FEATURE: 2 new reports: time to first response, topics with no response
FIX: relativeAgeMediumSpan was off by 1 REFACTOR: extracted decimalAdjust & round functions from the poll plugin
This commit is contained in:
parent
0bfabed2d5
commit
b25a16ee3e
10 changed files with 101 additions and 10 deletions
|
@ -1,9 +1,11 @@
|
||||||
Discourse.Report = Discourse.Model.extend({
|
import round from "discourse/lib/round";
|
||||||
|
|
||||||
|
const Report = Discourse.Model.extend({
|
||||||
reportUrl: function() {
|
reportUrl: function() {
|
||||||
return("/admin/reports/" + this.get('type'));
|
return("/admin/reports/" + this.get('type'));
|
||||||
}.property('type'),
|
}.property('type'),
|
||||||
|
|
||||||
valueAt: function(numDaysAgo) {
|
valueAt(numDaysAgo) {
|
||||||
if (this.data) {
|
if (this.data) {
|
||||||
var wantedDate = moment().subtract(numDaysAgo, 'days').format('YYYY-MM-DD');
|
var wantedDate = moment().subtract(numDaysAgo, 'days').format('YYYY-MM-DD');
|
||||||
var item = this.data.find( function(d) { return d.x === wantedDate; } );
|
var item = this.data.find( function(d) { return d.x === wantedDate; } );
|
||||||
|
@ -14,7 +16,7 @@ Discourse.Report = Discourse.Model.extend({
|
||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
sumDays: function(startDaysAgo, endDaysAgo) {
|
sumDays(startDaysAgo, endDaysAgo) {
|
||||||
if (this.data) {
|
if (this.data) {
|
||||||
var earliestDate = moment().subtract(endDaysAgo, 'days').startOf('day');
|
var earliestDate = moment().subtract(endDaysAgo, 'days').startOf('day');
|
||||||
var latestDate = moment().subtract(startDaysAgo, 'days').startOf('day');
|
var latestDate = moment().subtract(startDaysAgo, 'days').startOf('day');
|
||||||
|
@ -25,7 +27,7 @@ Discourse.Report = Discourse.Model.extend({
|
||||||
sum += datum.y;
|
sum += datum.y;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return sum;
|
return round(sum, -2);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -100,7 +102,7 @@ Discourse.Report = Discourse.Model.extend({
|
||||||
}
|
}
|
||||||
}.property('type'),
|
}.property('type'),
|
||||||
|
|
||||||
percentChangeString: function(val1, val2) {
|
percentChangeString(val1, val2) {
|
||||||
var val = ((val1 - val2) / val2) * 100;
|
var val = ((val1 - val2) / val2) * 100;
|
||||||
if( isNaN(val) || !isFinite(val) ) {
|
if( isNaN(val) || !isFinite(val) ) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -111,7 +113,7 @@ Discourse.Report = Discourse.Model.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
changeTitle: function(val1, val2, prevPeriodString) {
|
changeTitle(val1, val2, prevPeriodString) {
|
||||||
var title = '';
|
var title = '';
|
||||||
var percentChange = this.percentChangeString(val1, val2);
|
var percentChange = this.percentChangeString(val1, val2);
|
||||||
if( percentChange ) {
|
if( percentChange ) {
|
||||||
|
@ -139,7 +141,7 @@ Discourse.Report = Discourse.Model.extend({
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Discourse.Report.reopenClass({
|
Report.reopenClass({
|
||||||
find: function(type, startDate, endDate) {
|
find: function(type, startDate, endDate) {
|
||||||
|
|
||||||
return Discourse.ajax("/admin/reports/" + type, {data: {
|
return Discourse.ajax("/admin/reports/" + type, {data: {
|
||||||
|
@ -162,3 +164,5 @@ Discourse.Report.reopenClass({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default Report;
|
|
@ -58,6 +58,8 @@
|
||||||
{{admin-report-counts report=signups}}
|
{{admin-report-counts report=signups}}
|
||||||
{{admin-report-counts report=topics}}
|
{{admin-report-counts report=topics}}
|
||||||
{{admin-report-counts report=posts}}
|
{{admin-report-counts report=posts}}
|
||||||
|
{{admin-report-counts report=time_to_first_response}}
|
||||||
|
{{admin-report-counts report=topics_with_no_response}}
|
||||||
{{admin-report-counts report=likes}}
|
{{admin-report-counts report=likes}}
|
||||||
{{admin-report-counts report=flags}}
|
{{admin-report-counts report=flags}}
|
||||||
{{admin-report-counts report=bookmarks}}
|
{{admin-report-counts report=bookmarks}}
|
||||||
|
|
|
@ -188,7 +188,7 @@ relativeAgeMediumSpan = function(distance, leaveAgo) {
|
||||||
};
|
};
|
||||||
|
|
||||||
switch(true){
|
switch(true){
|
||||||
case(distanceInMinutes >= 1 && distanceInMinutes <= 56):
|
case(distanceInMinutes >= 1 && distanceInMinutes <= 55):
|
||||||
formatted = t("x_minutes", {count: distanceInMinutes});
|
formatted = t("x_minutes", {count: distanceInMinutes});
|
||||||
break;
|
break;
|
||||||
case(distanceInMinutes >= 56 && distanceInMinutes <= 89):
|
case(distanceInMinutes >= 56 && distanceInMinutes <= 89):
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import decimalAdjust from "discourse/plugins/poll/lib/decimal-adjust";
|
import decimalAdjust from "discourse/lib/decimal-adjust";
|
||||||
|
|
||||||
export default function(value, exp) {
|
export default function(value, exp) {
|
||||||
return decimalAdjust("round", value, exp);
|
return decimalAdjust("round", value, exp);
|
|
@ -7,6 +7,8 @@ class AdminDashboardData
|
||||||
'signups',
|
'signups',
|
||||||
'topics',
|
'topics',
|
||||||
'posts',
|
'posts',
|
||||||
|
'time_to_first_response',
|
||||||
|
'topics_with_no_response',
|
||||||
'flags',
|
'flags',
|
||||||
'users_by_trust_level',
|
'users_by_trust_level',
|
||||||
'likes',
|
'likes',
|
||||||
|
|
|
@ -93,6 +93,21 @@ class Report
|
||||||
add_counts report, Post.public_posts, 'posts.created_at'
|
add_counts report, Post.public_posts, 'posts.created_at'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.report_time_to_first_response(report)
|
||||||
|
report.data = []
|
||||||
|
Topic.time_to_first_response_per_day(report.start_date, report.end_date).each do |r|
|
||||||
|
report.data << { x: Date.parse(r["date"]), y: r["hours"].to_f.round(2) }
|
||||||
|
end
|
||||||
|
report.total = Topic.time_to_first_response_total
|
||||||
|
report.prev30Days = Topic.time_to_first_response_total(report.start_date - 30.days, report.start_date)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.report_topics_with_no_response(report)
|
||||||
|
basic_report_about report, Topic, :with_no_response_per_day, report.start_date, report.end_date
|
||||||
|
report.total = Topic.with_no_response_total
|
||||||
|
report.prev30Days = Topic.with_no_response_total(report.start_date - 30.days, report.start_date)
|
||||||
|
end
|
||||||
|
|
||||||
def self.report_emails(report)
|
def self.report_emails(report)
|
||||||
report_about report, EmailLog
|
report_about report, EmailLog
|
||||||
end
|
end
|
||||||
|
|
|
@ -848,6 +848,66 @@ class Topic < ActiveRecord::Base
|
||||||
SiteSetting.embeddable_hosts.present? && SiteSetting.embed_truncate? && has_topic_embed?
|
SiteSetting.embeddable_hosts.present? && SiteSetting.embed_truncate? && has_topic_embed?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
TIME_TO_FIRST_RESPONSE_SQL ||= <<-SQL
|
||||||
|
SELECT AVG(t.hours)::float AS "hours", t.created_at AS "date"
|
||||||
|
FROM (
|
||||||
|
SELECT t.id, t.created_at::date AS created_at, EXTRACT(EPOCH FROM MIN(p.created_at) - t.created_at)::float / 3600.0 AS "hours"
|
||||||
|
FROM topics t
|
||||||
|
LEFT JOIN posts p ON p.topic_id = t.id
|
||||||
|
/*where*/
|
||||||
|
GROUP BY t.id
|
||||||
|
) t
|
||||||
|
GROUP BY t.created_at
|
||||||
|
ORDER BY t.created_at
|
||||||
|
SQL
|
||||||
|
|
||||||
|
TIME_TO_FIRST_RESPONSE_TOTAL_SQL ||= <<-SQL
|
||||||
|
SELECT AVG(t.hours)::float AS "hours"
|
||||||
|
FROM (
|
||||||
|
SELECT t.id, EXTRACT(EPOCH FROM MIN(p.created_at) - t.created_at)::float / 3600.0 AS "hours"
|
||||||
|
FROM topics t
|
||||||
|
LEFT JOIN posts p ON p.topic_id = t.id
|
||||||
|
/*where*/
|
||||||
|
GROUP BY t.id
|
||||||
|
) t
|
||||||
|
SQL
|
||||||
|
|
||||||
|
def self.time_to_first_response(sql, start_date=nil, end_date=nil)
|
||||||
|
builder = SqlBuilder.new(sql)
|
||||||
|
builder.where("t.created_at >= :start_date", start_date: start_date) if start_date
|
||||||
|
builder.where("t.created_at <= :end_date", end_date: end_date) if end_date
|
||||||
|
builder.where("t.archetype <> '#{Archetype.private_message}'")
|
||||||
|
builder.where("t.deleted_at IS NULL")
|
||||||
|
builder.where("p.deleted_at IS NULL")
|
||||||
|
builder.where("p.post_number > 1")
|
||||||
|
builder.where("EXTRACT(EPOCH FROM p.created_at - t.created_at) > 0")
|
||||||
|
builder.exec
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.time_to_first_response_per_day(start_date, end_date)
|
||||||
|
time_to_first_response(TIME_TO_FIRST_RESPONSE_SQL, start_date, end_date)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.time_to_first_response_total(start_date=nil, end_date=nil)
|
||||||
|
result = time_to_first_response(TIME_TO_FIRST_RESPONSE_TOTAL_SQL, start_date, end_date)
|
||||||
|
result.first["hours"].to_f.round(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.with_no_response_per_day(start_date, end_date)
|
||||||
|
listable_topics.where(highest_post_number: 1)
|
||||||
|
.where("created_at BETWEEN ? AND ?", start_date, end_date)
|
||||||
|
.group("created_at::date")
|
||||||
|
.order("created_at::date")
|
||||||
|
.count
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.with_no_response_total(start_date=nil, end_date=nil)
|
||||||
|
total = listable_topics.where(highest_post_number: 1)
|
||||||
|
total = total.where("created_at >= ?", start_date) if start_date
|
||||||
|
total = total.where("created_at <= ?", end_date) if end_date
|
||||||
|
total.count
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def update_category_topic_count_by(num)
|
def update_category_topic_count_by(num)
|
||||||
|
|
|
@ -675,6 +675,14 @@ en:
|
||||||
title: "Total"
|
title: "Total"
|
||||||
xaxis: "Day"
|
xaxis: "Day"
|
||||||
yaxis: "Total requests"
|
yaxis: "Total requests"
|
||||||
|
time_to_first_response:
|
||||||
|
title: "Time to first response"
|
||||||
|
xaxis: "Day"
|
||||||
|
yaxis: "Average time (hours)"
|
||||||
|
topics_with_no_response:
|
||||||
|
title: "Topics with no response"
|
||||||
|
xaxis: "Day"
|
||||||
|
yaxis: "Total"
|
||||||
|
|
||||||
dashboard:
|
dashboard:
|
||||||
rails_env_warning: "Your server is running in %{env} mode."
|
rails_env_warning: "Your server is running in %{env} mode."
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import round from "discourse/plugins/poll/lib/round";
|
import round from "discourse/lib/round";
|
||||||
|
|
||||||
export default Em.Component.extend({
|
export default Em.Component.extend({
|
||||||
tagName: "span",
|
tagName: "span",
|
||||||
|
|
Loading…
Reference in a new issue