mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 02:11:08 -06: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
@ -1,9 +1,11 @@
|
||||
Discourse.Report = Discourse.Model.extend({
|
||||
import round from "discourse/lib/round";
|
||||
|
||||
const Report = Discourse.Model.extend({
|
||||
reportUrl: function() {
|
||||
return("/admin/reports/" + this.get('type'));
|
||||
}.property('type'),
|
||||
|
||||
valueAt: function(numDaysAgo) {
|
||||
valueAt(numDaysAgo) {
|
||||
if (this.data) {
|
||||
var wantedDate = moment().subtract(numDaysAgo, 'days').format('YYYY-MM-DD');
|
||||
var item = this.data.find( function(d) { return d.x === wantedDate; } );
|
||||
@ -14,7 +16,7 @@ Discourse.Report = Discourse.Model.extend({
|
||||
return 0;
|
||||
},
|
||||
|
||||
sumDays: function(startDaysAgo, endDaysAgo) {
|
||||
sumDays(startDaysAgo, endDaysAgo) {
|
||||
if (this.data) {
|
||||
var earliestDate = moment().subtract(endDaysAgo, 'days').startOf('day');
|
||||
var latestDate = moment().subtract(startDaysAgo, 'days').startOf('day');
|
||||
@ -25,7 +27,7 @@ Discourse.Report = Discourse.Model.extend({
|
||||
sum += datum.y;
|
||||
}
|
||||
});
|
||||
return sum;
|
||||
return round(sum, -2);
|
||||
}
|
||||
},
|
||||
|
||||
@ -100,7 +102,7 @@ Discourse.Report = Discourse.Model.extend({
|
||||
}
|
||||
}.property('type'),
|
||||
|
||||
percentChangeString: function(val1, val2) {
|
||||
percentChangeString(val1, val2) {
|
||||
var val = ((val1 - val2) / val2) * 100;
|
||||
if( isNaN(val) || !isFinite(val) ) {
|
||||
return null;
|
||||
@ -111,7 +113,7 @@ Discourse.Report = Discourse.Model.extend({
|
||||
}
|
||||
},
|
||||
|
||||
changeTitle: function(val1, val2, prevPeriodString) {
|
||||
changeTitle(val1, val2, prevPeriodString) {
|
||||
var title = '';
|
||||
var percentChange = this.percentChangeString(val1, val2);
|
||||
if( percentChange ) {
|
||||
@ -139,7 +141,7 @@ Discourse.Report = Discourse.Model.extend({
|
||||
|
||||
});
|
||||
|
||||
Discourse.Report.reopenClass({
|
||||
Report.reopenClass({
|
||||
find: function(type, startDate, endDate) {
|
||||
|
||||
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=topics}}
|
||||
{{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=flags}}
|
||||
{{admin-report-counts report=bookmarks}}
|
||||
|
@ -188,7 +188,7 @@ relativeAgeMediumSpan = function(distance, leaveAgo) {
|
||||
};
|
||||
|
||||
switch(true){
|
||||
case(distanceInMinutes >= 1 && distanceInMinutes <= 56):
|
||||
case(distanceInMinutes >= 1 && distanceInMinutes <= 55):
|
||||
formatted = t("x_minutes", {count: distanceInMinutes});
|
||||
break;
|
||||
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) {
|
||||
return decimalAdjust("round", value, exp);
|
@ -7,6 +7,8 @@ class AdminDashboardData
|
||||
'signups',
|
||||
'topics',
|
||||
'posts',
|
||||
'time_to_first_response',
|
||||
'topics_with_no_response',
|
||||
'flags',
|
||||
'users_by_trust_level',
|
||||
'likes',
|
||||
|
@ -93,6 +93,21 @@ class Report
|
||||
add_counts report, Post.public_posts, 'posts.created_at'
|
||||
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)
|
||||
report_about report, EmailLog
|
||||
end
|
||||
|
@ -848,6 +848,66 @@ class Topic < ActiveRecord::Base
|
||||
SiteSetting.embeddable_hosts.present? && SiteSetting.embed_truncate? && has_topic_embed?
|
||||
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
|
||||
|
||||
def update_category_topic_count_by(num)
|
||||
|
@ -675,6 +675,14 @@ en:
|
||||
title: "Total"
|
||||
xaxis: "Day"
|
||||
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:
|
||||
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({
|
||||
tagName: "span",
|
||||
|
Loading…
Reference in New Issue
Block a user