diff --git a/app/assets/javascripts/admin/components/admin-report.js b/app/assets/javascripts/admin/components/admin-report.js
index a385b71730a..178bef98f28 100644
--- a/app/assets/javascripts/admin/components/admin-report.js
+++ b/app/assets/javascripts/admin/components/admin-report.js
@@ -7,7 +7,6 @@ import Component from "@ember/component";
import ReportLoader from "discourse/lib/reports-loader";
import { exportEntity } from "discourse/lib/export-csv";
import { outputExportResult } from "discourse/lib/export-result";
-import { isNumeric } from "discourse/lib/utilities";
import Report, { SCHEMA_VERSION } from "admin/models/report";
import ENV from "discourse-common/config/environment";
@@ -167,10 +166,9 @@ export default Component.extend({
ENV.environment === "test" ? "end" : endDate.replace(/-/g, ""),
"[:prev_period]",
this.get("reportOptions.table.limit"),
+ // Convert all filter values to strings to ensure unique serialization
customFilters
- ? JSON.stringify(customFilters, (key, value) =>
- isNumeric(value) ? value.toString() : value
- )
+ ? JSON.stringify(customFilters, (k, v) => (k ? `${v}` : v))
: null,
SCHEMA_VERSION
]
diff --git a/app/assets/javascripts/admin/components/report-filters/bool.js b/app/assets/javascripts/admin/components/report-filters/bool.js
new file mode 100644
index 00000000000..777e15cfe48
--- /dev/null
+++ b/app/assets/javascripts/admin/components/report-filters/bool.js
@@ -0,0 +1,16 @@
+import { action } from "@ember/object";
+import FilterComponent from "admin/components/report-filters/filter";
+
+export default FilterComponent.extend({
+ checked: false,
+
+ didReceiveAttrs() {
+ this._super(...arguments);
+ this.set("checked", !!this.filter.default);
+ },
+
+ @action
+ onChange() {
+ this.applyFilter(this.filter.id, !this.checked || undefined);
+ }
+});
diff --git a/app/assets/javascripts/admin/components/report-filters/category.js b/app/assets/javascripts/admin/components/report-filters/category.js
index fab7753da4c..5a5ab52098f 100644
--- a/app/assets/javascripts/admin/components/report-filters/category.js
+++ b/app/assets/javascripts/admin/components/report-filters/category.js
@@ -1,16 +1,12 @@
+import { action } from "@ember/object";
import { readOnly } from "@ember/object/computed";
import FilterComponent from "admin/components/report-filters/filter";
export default FilterComponent.extend({
- classNames: ["category-filter"],
-
- layoutName: "admin/templates/components/report-filters/category",
-
category: readOnly("filter.default"),
- actions: {
- onChange(categoryId) {
- this.applyFilter(this.get("filter.id"), categoryId || undefined);
- }
+ @action
+ onChange(categoryId) {
+ this.applyFilter(this.filter.id, categoryId || undefined);
}
});
diff --git a/app/assets/javascripts/admin/components/report-filters/file-extension.js b/app/assets/javascripts/admin/components/report-filters/file-extension.js
deleted file mode 100644
index d8eb1b5b062..00000000000
--- a/app/assets/javascripts/admin/components/report-filters/file-extension.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import FilterComponent from "admin/components/report-filters/filter";
-
-export default FilterComponent.extend({
- classNames: ["file-extension-filter"],
-
- layoutName: "admin/templates/components/report-filters/file-extension"
-});
diff --git a/app/assets/javascripts/admin/components/report-filters/filter.js b/app/assets/javascripts/admin/components/report-filters/filter.js
index f61b2d496ae..0c4e0063463 100644
--- a/app/assets/javascripts/admin/components/report-filters/filter.js
+++ b/app/assets/javascripts/admin/components/report-filters/filter.js
@@ -1,8 +1,9 @@
import Component from "@ember/component";
+import { action } from "@ember/object";
+
export default Component.extend({
- actions: {
- onChange(value) {
- this.applyFilter(this.get("filter.id"), value);
- }
+ @action
+ onChange(value) {
+ this.applyFilter(this.filter.id, value);
}
});
diff --git a/app/assets/javascripts/admin/components/report-filters/group.js b/app/assets/javascripts/admin/components/report-filters/group.js
index a6511e75f63..e841800030f 100644
--- a/app/assets/javascripts/admin/components/report-filters/group.js
+++ b/app/assets/javascripts/admin/components/report-filters/group.js
@@ -1,20 +1,18 @@
+import { computed } from "@ember/object";
import FilterComponent from "admin/components/report-filters/filter";
-import discourseComputed from "discourse-common/utils/decorators";
export default FilterComponent.extend({
classNames: ["group-filter"],
- layoutName: "admin/templates/components/report-filters/group",
-
- @discourseComputed()
- groupOptions() {
+ @computed
+ get groupOptions() {
return (this.site.groups || []).map(group => {
return { name: group["name"], value: group["id"] };
});
},
- @discourseComputed("filter.default")
- groupId(filterDefault) {
- return filterDefault ? parseInt(filterDefault, 10) : null;
+ @computed("filter.default")
+ get groupId() {
+ return this.filter.default ? parseInt(this.filter.default, 10) : null;
}
});
diff --git a/app/assets/javascripts/admin/components/report-filters/list.js b/app/assets/javascripts/admin/components/report-filters/list.js
new file mode 100644
index 00000000000..654269ecd00
--- /dev/null
+++ b/app/assets/javascripts/admin/components/report-filters/list.js
@@ -0,0 +1,3 @@
+import FilterComponent from "admin/components/report-filters/filter";
+
+export default FilterComponent.extend();
diff --git a/app/assets/javascripts/admin/routes/admin-dashboard-reports.js b/app/assets/javascripts/admin/routes/admin-dashboard-reports.js
index 0de5bfe5057..b1eff77998c 100644
--- a/app/assets/javascripts/admin/routes/admin-dashboard-reports.js
+++ b/app/assets/javascripts/admin/routes/admin-dashboard-reports.js
@@ -3,7 +3,7 @@ import { ajax } from "discourse/lib/ajax";
export default DiscourseRoute.extend({
model() {
- return ajax("/admin/reports").then(json => json);
+ return ajax("/admin/reports");
},
setupController(controller, model) {
diff --git a/app/assets/javascripts/admin/templates/components/admin-report.hbs b/app/assets/javascripts/admin/templates/components/admin-report.hbs
index 2aa3143a47e..ab0c1a63980 100644
--- a/app/assets/javascripts/admin/templates/components/admin-report.hbs
+++ b/app/assets/javascripts/admin/templates/components/admin-report.hbs
@@ -165,7 +165,8 @@
{{component
- (concat "report-filters/" filter.id)
+ (concat "report-filters/" filter.type)
+ model=model
filter=filter
applyFilter=(action "applyFilter")}}
diff --git a/app/assets/javascripts/admin/templates/components/report-filters/bool.hbs b/app/assets/javascripts/admin/templates/components/report-filters/bool.hbs
new file mode 100644
index 00000000000..4d96755ff9c
--- /dev/null
+++ b/app/assets/javascripts/admin/templates/components/report-filters/bool.hbs
@@ -0,0 +1,5 @@
+{{input
+ type="checkbox"
+ checked=checked
+ click=(action "onChange")
+}}
\ No newline at end of file
diff --git a/app/assets/javascripts/admin/templates/components/report-filters/category.hbs b/app/assets/javascripts/admin/templates/components/report-filters/category.hbs
index 31110bcbbdc..bdf354e188b 100644
--- a/app/assets/javascripts/admin/templates/components/report-filters/category.hbs
+++ b/app/assets/javascripts/admin/templates/components/report-filters/category.hbs
@@ -1,7 +1,5 @@
{{search-advanced-category-chooser
value=category
onChange=(action "onChange")
- options=(hash
- filterable=true
- )
+ options=(hash filterable=true)
}}
diff --git a/app/assets/javascripts/admin/templates/components/report-filters/file-extension.hbs b/app/assets/javascripts/admin/templates/components/report-filters/list.hbs
similarity index 100%
rename from app/assets/javascripts/admin/templates/components/report-filters/file-extension.hbs
rename to app/assets/javascripts/admin/templates/components/report-filters/list.hbs
diff --git a/app/models/incoming_links_report.rb b/app/models/incoming_links_report.rb
index d791a65903e..e17f893a939 100644
--- a/app/models/incoming_links_report.rb
+++ b/app/models/incoming_links_report.rb
@@ -2,13 +2,14 @@
class IncomingLinksReport
- attr_accessor :type, :data, :y_titles, :start_date, :end_date, :limit, :category_id
+ attr_accessor :type, :data, :y_titles, :start_date, :end_date, :limit, :category_id, :include_subcategories
def initialize(type)
@type = type
@y_titles = {}
@data = nil
@category_id = nil
+ @include_subcategories = false
end
def as_json(_options = nil)
@@ -34,6 +35,7 @@ class IncomingLinksReport
report.end_date = _opts[:end_date] || Time.now.end_of_day
report.limit = _opts[:limit].to_i if _opts[:limit]
report.category_id = _opts[:category_id] if _opts[:category_id]
+ report.include_subcategories = _opts[:include_subcategories] if _opts[:include_subcategories]
public_send(report_method, report)
report
@@ -44,8 +46,8 @@ class IncomingLinksReport
report.y_titles[:num_clicks] = I18n.t("reports.#{report.type}.num_clicks")
report.y_titles[:num_topics] = I18n.t("reports.#{report.type}.num_topics")
- num_clicks = link_count_per_user(start_date: report.start_date, end_date: report.end_date, category_id: report.category_id)
- num_topics = topic_count_per_user(start_date: report.start_date, end_date: report.end_date, category_id: report.category_id)
+ num_clicks = link_count_per_user(start_date: report.start_date, end_date: report.end_date, category_id: report.category_id, include_subcategories: report.include_subcategories)
+ num_topics = topic_count_per_user(start_date: report.start_date, end_date: report.end_date, category_id: report.category_id, include_subcategories: report.include_subcategories)
user_id_lookup = User
.where(username: num_clicks.keys)
.select(:id, :username, :uploaded_avatar_id)
@@ -70,19 +72,19 @@ class IncomingLinksReport
report.data = report.data.sort_by { |x| x[:num_clicks] }.reverse[0, 10]
end
- def self.per_user(start_date:, end_date:, category_id:)
- public_incoming_links(category_id: category_id)
+ def self.per_user(start_date:, end_date:, category_id:, include_subcategories:)
+ public_incoming_links(category_id: category_id, include_subcategories: include_subcategories)
.where('incoming_links.created_at > ? AND incoming_links.created_at < ? AND incoming_links.user_id IS NOT NULL', start_date, end_date)
.joins(:user)
.group('users.username')
end
- def self.link_count_per_user(start_date:, end_date:, category_id:)
- per_user(start_date: start_date, end_date: end_date, category_id: category_id).count
+ def self.link_count_per_user(start_date:, end_date:, category_id:, include_subcategories:)
+ per_user(start_date: start_date, end_date: end_date, category_id: category_id, include_subcategories: include_subcategories).count
end
- def self.topic_count_per_user(start_date:, end_date:, category_id:)
- per_user(start_date: start_date, end_date: end_date, category_id: category_id).joins(:post).count("DISTINCT posts.topic_id")
+ def self.topic_count_per_user(start_date:, end_date:, category_id:, include_subcategories:)
+ per_user(start_date: start_date, end_date: end_date, category_id: category_id, include_subcategories: include_subcategories).joins(:post).count("DISTINCT posts.topic_id")
end
# Return top 10 domains that brought traffic to the site within the last 30 days
@@ -91,8 +93,8 @@ class IncomingLinksReport
report.y_titles[:num_topics] = I18n.t("reports.#{report.type}.num_topics")
report.y_titles[:num_users] = I18n.t("reports.#{report.type}.num_users")
- num_clicks = link_count_per_domain(start_date: report.start_date, end_date: report.end_date, category_id: report.category_id)
- num_topics = topic_count_per_domain(num_clicks.keys, category_id: report.category_id)
+ num_clicks = link_count_per_domain(start_date: report.start_date, end_date: report.end_date, category_id: report.category_id, include_subcategories: report.include_subcategories)
+ num_topics = topic_count_per_domain(num_clicks.keys, category_id: report.category_id, include_subcategories: report.include_subcategories)
report.data = []
num_clicks.each_key do |domain|
report.data << { domain: domain, num_clicks: num_clicks[domain], num_topics: num_topics[domain] }
@@ -100,8 +102,8 @@ class IncomingLinksReport
report.data = report.data.sort_by { |x| x[:num_clicks] }.reverse[0, 10]
end
- def self.link_count_per_domain(limit: 10, start_date:, end_date:, category_id:)
- public_incoming_links(category_id: category_id)
+ def self.link_count_per_domain(limit: 10, start_date:, end_date:, category_id:, include_subcategories:)
+ public_incoming_links(category_id: category_id, include_subcategories: include_subcategories)
.where('incoming_links.created_at > ? AND incoming_links.created_at < ?', start_date, end_date)
.joins(incoming_referer: :incoming_domain)
.group('incoming_domains.name')
@@ -111,7 +113,7 @@ class IncomingLinksReport
end
def self.per_domain(domains, options = {})
- public_incoming_links(category_id: options[:category_id])
+ public_incoming_links(category_id: options[:category_id], include_subcategories: options[:include_subcategories])
.joins(incoming_referer: :incoming_domain)
.where('incoming_links.created_at > ? AND incoming_domains.name IN (?)', 30.days.ago, domains)
.group('incoming_domains.name')
@@ -124,11 +126,13 @@ class IncomingLinksReport
def self.report_top_referred_topics(report)
report.y_titles[:num_clicks] = I18n.t("reports.#{report.type}.labels.num_clicks")
- num_clicks = link_count_per_topic(start_date: report.start_date, end_date: report.end_date, category_id: report.category_id)
+ num_clicks = link_count_per_topic(start_date: report.start_date, end_date: report.end_date, category_id: report.category_id, include_subcategories: report.include_subcategories)
num_clicks = num_clicks.to_a.sort_by { |x| x[1] }.last(report.limit || 10).reverse
report.data = []
topics = Topic.select('id, slug, title').where('id in (?)', num_clicks.map { |z| z[0] })
- topics = topics.in_category_and_subcategories(report.category_id) if report.category_id
+ if report.category_id
+ topics = topics.where(category_id: report.include_subcategories ? Category.subcategory_ids(report.category_id) : report.category_id)
+ end
num_clicks.each do |topic_id, num_clicks_element|
topic = topics.find { |t| t.id == topic_id }
if topic
@@ -138,17 +142,26 @@ class IncomingLinksReport
report.data
end
- def self.link_count_per_topic(start_date:, end_date:, category_id:)
- public_incoming_links(category_id: category_id)
+ def self.link_count_per_topic(start_date:, end_date:, category_id:, include_subcategories:)
+ public_incoming_links(category_id: category_id, include_subcategories: include_subcategories)
.where('incoming_links.created_at > ? AND incoming_links.created_at < ? AND topic_id IS NOT NULL', start_date, end_date)
.group('topic_id')
.count
end
- def self.public_incoming_links(category_id: nil)
- IncomingLink
+ def self.public_incoming_links(category_id: nil, include_subcategories: nil)
+ links = IncomingLink
.joins(post: :topic)
.where("topics.archetype = ?", Archetype.default)
- .merge(Topic.in_category_and_subcategories(category_id))
+
+ if category_id
+ if include_subcategories
+ links = links.where("topics.category_id IN (?)", Category.subcategory_ids(category_id))
+ else
+ links = links.where("topics.category_id = ?", category_id)
+ end
+ end
+
+ links
end
end
diff --git a/app/models/post.rb b/app/models/post.rb
index 86dafde6bce..acc6c540f2e 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -765,10 +765,19 @@ class Post < ActiveRecord::Base
DiscourseEvent.trigger(:after_trigger_post_process, self)
end
- def self.public_posts_count_per_day(start_date, end_date, category_id = nil)
- result = public_posts.where('posts.created_at >= ? AND posts.created_at <= ?', start_date, end_date)
+ def self.public_posts_count_per_day(start_date, end_date, category_id = nil, include_subcategories = false)
+ result = public_posts
+ .where('posts.created_at >= ? AND posts.created_at <= ?', start_date, end_date)
.where(post_type: Post.types[:regular])
- result = result.where('topics.category_id IN (?)', Category.subcategory_ids(category_id.to_i)) if category_id
+
+ if category_id
+ if include_subcategories
+ result = result.where('topics.category_id IN (?)', Category.subcategory_ids(category_id))
+ else
+ result = result.where('topics.category_id = ?', category_id)
+ end
+ end
+
result
.group('date(posts.created_at)')
.order('date(posts.created_at)')
diff --git a/app/models/post_action.rb b/app/models/post_action.rb
index 077ef253c18..53c482d0d3c 100644
--- a/app/models/post_action.rb
+++ b/app/models/post_action.rb
@@ -67,7 +67,13 @@ class PostAction < ActiveRecord::Base
result = unscoped.where(post_action_type_id: post_action_type)
result = result.where('post_actions.created_at >= ?', opts[:start_date] || (opts[:since_days_ago] || 30).days.ago)
result = result.where('post_actions.created_at <= ?', opts[:end_date]) if opts[:end_date]
- result = result.joins(post: :topic).merge(Topic.in_category_and_subcategories(opts[:category_id])) if opts[:category_id]
+ if opts[:category_id]
+ if opts[:include_subcategories]
+ result = result.joins(post: :topic).where('topics.category_id IN (?)', Category.subcategory_ids(opts[:category_id]))
+ else
+ result = result.joins(post: :topic).where('topics.category_id = ?', opts[:category_id])
+ end
+ end
result.group('date(post_actions.created_at)')
.order('date(post_actions.created_at)')
.count
diff --git a/app/models/report.rb b/app/models/report.rb
index 13f7d02830b..b66e36c7721 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -50,8 +50,8 @@ class Report
end
def self.cache_key(report)
- (+"reports:") <<
[
+ "reports",
report.type,
report.start_date.to_date.strftime("%Y%m%d"),
report.end_date.to_date.strftime("%Y%m%d"),
@@ -63,14 +63,30 @@ class Report
end
def add_filter(name, options = {})
- default_filter = { allow_any: false, choices: [], default: nil }
- available_filters[name] = default_filter.merge(options)
+ if options[:type].blank?
+ options[:type] = name
+ Discourse.deprecate("#{name} filter should define a `:type` option. Temporarily setting type to #{name}.")
+ end
+
+ available_filters[name] = options
end
def remove_filter(name)
available_filters.delete(name)
end
+ def add_category_filter
+ category_id = filters[:category].to_i if filters[:category].present?
+ add_filter('category', type: 'category', default: category_id)
+ return if category_id.blank?
+
+ include_subcategories = filters[:'include-subcategories']
+ include_subcategories = !!ActiveRecord::Type::Boolean.new.cast(include_subcategories)
+ add_filter('include-subcategories', type: 'bool', default: include_subcategories)
+
+ [category_id, include_subcategories]
+ end
+
def self.clear_cache(type = nil)
pattern = type ? "reports:#{type}:*" : "reports:*"
@@ -285,15 +301,22 @@ class Report
end
def self.post_action_report(report, post_action_type)
- category_filter = report.filters.dig(:category)
- report.add_filter('category', default: category_filter)
+ category_id, include_subcategories = report.add_category_filter
report.data = []
- PostAction.count_per_day_for_type(post_action_type, category_id: category_filter, start_date: report.start_date, end_date: report.end_date).each do |date, count|
+ PostAction.count_per_day_for_type(post_action_type, category_id: category_id, include_subcategories: include_subcategories, start_date: report.start_date, end_date: report.end_date).each do |date, count|
report.data << { x: date, y: count }
end
+
countable = PostAction.unscoped.where(post_action_type_id: post_action_type)
- countable = countable.joins(post: :topic).merge(Topic.in_category_and_subcategories(category_filter)) if category_filter
+ if category_id
+ if include_subcategories
+ countable = countable.joins(post: :topic).where('topics.category_id IN (?)', Category.subcategory_ids(category_id))
+ else
+ countable = countable.joins(post: :topic).where('topics.category_id = ?', category_id)
+ end
+ end
+
add_counts report, countable, 'post_actions.created_at'
end
diff --git a/app/models/reports/flags.rb b/app/models/reports/flags.rb
index 682fa4f4dc3..da80c8e92a6 100644
--- a/app/models/reports/flags.rb
+++ b/app/models/reports/flags.rb
@@ -1,8 +1,7 @@
# frozen_string_literal: true
Report.add_report('flags') do |report|
- category_filter = report.filters.dig(:category)
- report.add_filter('category', default: category_filter)
+ category_id, include_subcategories = report.add_category_filter
report.icon = 'flag'
report.higher_is_better = false
@@ -13,13 +12,18 @@ Report.add_report('flags') do |report|
:count_by_date,
report.start_date,
report.end_date,
- category_filter
+ category_id,
+ include_subcategories
)
countable = ReviewableFlaggedPost.scores_with_topics
- if category_filter
- countable.merge!(Topic.in_category_and_subcategories(category_filter))
+ if category_id
+ if include_subcategories
+ countable = countable.where('topics.category_id IN (?)', Category.subcategory_ids(category_id))
+ else
+ countable = countable.where('topics.category_id = ?', category_id)
+ end
end
add_counts report, countable, 'reviewable_scores.created_at'
diff --git a/app/models/reports/post_edits.rb b/app/models/reports/post_edits.rb
index 90925204301..b8ece00d0cd 100644
--- a/app/models/reports/post_edits.rb
+++ b/app/models/reports/post_edits.rb
@@ -1,8 +1,7 @@
# frozen_string_literal: true
Report.add_report('post_edits') do |report|
- category_filter = report.filters.dig(:category)
- report.add_filter('category', default: category_filter)
+ category_id, include_subcategories = report.add_category_filter
report.modes = [:table]
@@ -77,13 +76,13 @@ Report.add_report('post_edits') do |report|
/*limit*/
SQL
- if category_filter
+ if category_id
builder.join "topics t ON t.id = p.topic_id"
- builder.where("t.category_id = :category_id
- OR t.category_id IN (
- SELECT id FROM categories
- WHERE categories.parent_category_id = :category_id
- )", category_id: category_filter)
+ if include_subcategories
+ builder.where("t.category_id IN (?)", Category.subcategory_ids(category_id))
+ else
+ builder.where("t.category_id = ?", category_id)
+ end
end
builder.where("editor.id > 0 AND editor.id != author.id")
diff --git a/app/models/reports/posts.rb b/app/models/reports/posts.rb
index 03175996874..8461d79dfce 100644
--- a/app/models/reports/posts.rb
+++ b/app/models/reports/posts.rb
@@ -3,14 +3,18 @@
Report.add_report('posts') do |report|
report.modes = [:table, :chart]
- category_filter = report.filters.dig(:category)
- report.add_filter('category', default: category_filter)
+ category_id, include_subcategories = report.add_category_filter
- basic_report_about report, Post, :public_posts_count_per_day, report.start_date, report.end_date, category_filter
+ basic_report_about report, Post, :public_posts_count_per_day, report.start_date, report.end_date, category_id, include_subcategories
countable = Post.public_posts.where(post_type: Post.types[:regular])
- if category_filter
- countable = countable.joins(:topic).merge(Topic.in_category_and_subcategories(category_filter))
+ if category_id
+ if include_subcategories
+ countable = countable.joins(:topic).where('topics.category_id IN (?)', Category.subcategory_ids(category_id))
+ else
+ countable = countable.joins(:topic).where('topics.category_id = ?', category_id)
+ end
end
+
add_counts report, countable, 'posts.created_at'
end
diff --git a/app/models/reports/profile_views.rb b/app/models/reports/profile_views.rb
index 564ff198d0d..ec349cf1ad6 100644
--- a/app/models/reports/profile_views.rb
+++ b/app/models/reports/profile_views.rb
@@ -2,7 +2,7 @@
Report.add_report('profile_views') do |report|
group_filter = report.filters.dig(:group)
- report.add_filter('group', default: group_filter)
+ report.add_filter('group', type: 'group', default: group_filter)
start_date = report.start_date
end_date = report.end_date
diff --git a/app/models/reports/signups.rb b/app/models/reports/signups.rb
index 79dcc041478..3df08e2b870 100644
--- a/app/models/reports/signups.rb
+++ b/app/models/reports/signups.rb
@@ -4,7 +4,7 @@ Report.add_report('signups') do |report|
report.icon = 'user-plus'
group_filter = report.filters.dig(:group)
- report.add_filter('group', default: group_filter)
+ report.add_filter('group', type: 'group', default: group_filter)
if group_filter
basic_report_about report, User.real, :count_by_signup_date, report.start_date, report.end_date, group_filter
diff --git a/app/models/reports/time_to_first_response.rb b/app/models/reports/time_to_first_response.rb
index 7f361ef3383..dd0a474318d 100644
--- a/app/models/reports/time_to_first_response.rb
+++ b/app/models/reports/time_to_first_response.rb
@@ -2,17 +2,17 @@
Report.add_report('time_to_first_response') do |report|
category_filter = report.filters.dig(:category)
- report.add_filter('category', default: category_filter)
+ category_id, include_subcategories = report.add_category_filter
report.icon = 'reply'
report.higher_is_better = false
report.data = []
- Topic.time_to_first_response_per_day(report.start_date, report.end_date, category_id: category_filter).each do |r|
+ Topic.time_to_first_response_per_day(report.start_date, report.end_date, category_id: category_id, include_subcategories: include_subcategories).each do |r|
report.data << { x: r['date'], y: r['hours'].to_f.round(2) }
end
- report.total = Topic.time_to_first_response_total(category_id: category_filter)
+ report.total = Topic.time_to_first_response_total(category_id: category_id, include_subcategories: include_subcategories)
- report.prev30Days = Topic.time_to_first_response_total(start_date: report.start_date - 30.days, end_date: report.start_date, category_id: category_filter)
+ report.prev30Days = Topic.time_to_first_response_total(start_date: report.start_date - 30.days, end_date: report.start_date, category_id: category_id, include_subcategories: include_subcategories)
end
diff --git a/app/models/reports/top_referred_topics.rb b/app/models/reports/top_referred_topics.rb
index 6b8abf84e25..7f5dad25890 100644
--- a/app/models/reports/top_referred_topics.rb
+++ b/app/models/reports/top_referred_topics.rb
@@ -1,8 +1,7 @@
# frozen_string_literal: true
Report.add_report('top_referred_topics') do |report|
- category_filter = report.filters.dig(:category)
- report.add_filter('category', default: category_filter)
+ category_id, include_subcategories = report.add_category_filter
report.modes = [:table]
@@ -26,7 +25,8 @@ Report.add_report('top_referred_topics') do |report|
end_date: report.end_date,
start_date: report.start_date,
limit: report.limit || 8,
- category_id: category_filter
+ category_id: category_id,
+ include_subcategories: include_subcategories
}
result = nil
result = IncomingLinksReport.find(:top_referred_topics, options)
diff --git a/app/models/reports/top_traffic_sources.rb b/app/models/reports/top_traffic_sources.rb
index f32130dc058..b6b7e16bf23 100644
--- a/app/models/reports/top_traffic_sources.rb
+++ b/app/models/reports/top_traffic_sources.rb
@@ -1,8 +1,7 @@
# frozen_string_literal: true
Report.add_report('top_traffic_sources') do |report|
- category_filter = report.filters.dig(:category)
- report.add_filter('category', default: category_filter)
+ category_id, include_subcategories = report.add_category_filter
report.modes = [:table]
@@ -27,7 +26,8 @@ Report.add_report('top_traffic_sources') do |report|
end_date: report.end_date,
start_date: report.start_date,
limit: report.limit || 8,
- category_id: category_filter
+ category_id: category_id,
+ include_subcategories: include_subcategories
}
result = IncomingLinksReport.find(:top_traffic_sources, options)
diff --git a/app/models/reports/top_uploads.rb b/app/models/reports/top_uploads.rb
index b8978678f39..e5a4f542cfa 100644
--- a/app/models/reports/top_uploads.rb
+++ b/app/models/reports/top_uploads.rb
@@ -5,10 +5,9 @@ Report.add_report('top_uploads') do |report|
extension_filter = report.filters.dig(:"file-extension")
report.add_filter('file-extension',
+ type: 'list',
default: extension_filter || 'any',
- choices: (
- SiteSetting.authorized_extensions.split('|') + Array(extension_filter)
- ).uniq
+ choices: (SiteSetting.authorized_extensions.split('|') + Array(extension_filter)).uniq
)
report.labels = [
diff --git a/app/models/reports/topics.rb b/app/models/reports/topics.rb
index 7fd181afa09..61acaaab093 100644
--- a/app/models/reports/topics.rb
+++ b/app/models/reports/topics.rb
@@ -1,14 +1,12 @@
# frozen_string_literal: true
Report.add_report('topics') do |report|
- category_filter = report.filters.dig(:category)
- report.add_filter('category', default: category_filter)
+ category_id, include_subcategories = report.add_category_filter
- basic_report_about report, Topic, :listable_count_per_day, report.start_date, report.end_date, category_filter
+ basic_report_about report, Topic, :listable_count_per_day, report.start_date, report.end_date, category_id, include_subcategories
countable = Topic.listable_topics
- if category_filter
- countable = countable.in_category_and_subcategories(category_filter)
- end
+ countable = countable.where(category_id: include_subcategories ? Category.subcategory_ids(category_id) : category_id) if category_id
+
add_counts report, countable, 'topics.created_at'
end
diff --git a/app/models/reports/topics_with_no_response.rb b/app/models/reports/topics_with_no_response.rb
index 1cc0f17e3e1..7cd2d15e164 100644
--- a/app/models/reports/topics_with_no_response.rb
+++ b/app/models/reports/topics_with_no_response.rb
@@ -1,15 +1,14 @@
# frozen_string_literal: true
Report.add_report('topics_with_no_response') do |report|
- category_filter = report.filters.dig(:category)
- report.add_filter('category', default: category_filter)
+ category_id, include_subcategories = report.add_category_filter
report.data = []
- Topic.with_no_response_per_day(report.start_date, report.end_date, category_filter).each do |r|
+ Topic.with_no_response_per_day(report.start_date, report.end_date, category_id, include_subcategories).each do |r|
report.data << { x: r['date'], y: r['count'].to_i }
end
- report.total = Topic.with_no_response_total(category_id: category_filter)
+ report.total = Topic.with_no_response_total(category_id: category_id, include_subcategories: include_subcategories)
- report.prev30Days = Topic.with_no_response_total(start_date: report.start_date - 30.days, end_date: report.start_date, category_id: category_filter)
+ report.prev30Days = Topic.with_no_response_total(start_date: report.start_date - 30.days, end_date: report.start_date, category_id: category_id, include_subcategories: include_subcategories)
end
diff --git a/app/models/reports/visits.rb b/app/models/reports/visits.rb
index 472c0117bc4..841f65ecf30 100644
--- a/app/models/reports/visits.rb
+++ b/app/models/reports/visits.rb
@@ -2,7 +2,7 @@
Report.add_report('visits') do |report|
group_filter = report.filters.dig(:group)
- report.add_filter('group', default: group_filter)
+ report.add_filter('group', type: 'group', default: group_filter)
report.icon = 'user'
diff --git a/app/models/reviewable.rb b/app/models/reviewable.rb
index 771b37f4ee6..0fd3f4059bd 100644
--- a/app/models/reviewable.rb
+++ b/app/models/reviewable.rb
@@ -534,10 +534,18 @@ class Reviewable < ActiveRecord::Base
ReviewableScore.joins(reviewable: :topic).where("reviewables.type = ?", name)
end
- def self.count_by_date(start_date, end_date, category_id = nil)
- scores_with_topics
- .where('reviewable_scores.created_at BETWEEN ? AND ?', start_date, end_date)
- .where("topics.category_id = COALESCE(?, topics.category_id)", category_id)
+ def self.count_by_date(start_date, end_date, category_id = nil, include_subcategories = false)
+ query = scores_with_topics.where('reviewable_scores.created_at BETWEEN ? AND ?', start_date, end_date)
+
+ if category_id
+ if include_subcategories
+ query = query.where("topics.category_id IN (?)", Category.subcategory_ids(category_id))
+ else
+ query = query.where("topics.category_id = ?", category_id)
+ end
+ end
+
+ query
.group("date(reviewable_scores.created_at)")
.order('date(reviewable_scores.created_at)')
.count
diff --git a/app/models/topic.rb b/app/models/topic.rb
index d080fbee325..f1f17443cef 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -454,10 +454,10 @@ class Topic < ActiveRecord::Base
((Time.zone.now - created_at) / 1.minute).round
end
- def self.listable_count_per_day(start_date, end_date, category_id = nil)
+ def self.listable_count_per_day(start_date, end_date, category_id = nil, include_subcategories = false)
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 = result.where(category_id: include_subcategories ? Category.subcategory_ids(category_id) : category_id) if category_id
result.count
end
@@ -1294,7 +1294,13 @@ class Topic < ActiveRecord::Base
builder = DB.build(sql)
builder.where("t.created_at >= :start_date", start_date: opts[:start_date]) if opts[:start_date]
builder.where("t.created_at < :end_date", end_date: opts[:end_date]) if opts[:end_date]
- builder.where("t.category_id IN (:subcategory_ids)", subcategory_ids: Category.subcategory_ids(opts[:category_id].to_i)) if opts[:category_id]
+ if opts[:category_id]
+ if opts[:include_subcategories]
+ builder.where("t.category_id IN (?)", Category.subcategory_ids(opts[:category_id]))
+ else
+ builder.where("t.category_id = ?", opts[:category_id])
+ end
+ end
builder.where("t.archetype <> '#{Archetype.private_message}'")
builder.where("t.deleted_at IS NULL")
builder.where("p.deleted_at IS NULL")
@@ -1329,11 +1335,17 @@ class Topic < ActiveRecord::Base
ORDER BY tt.created_at
SQL
- def self.with_no_response_per_day(start_date, end_date, category_id = nil)
+ def self.with_no_response_per_day(start_date, end_date, category_id = nil, include_subcategories = nil)
builder = DB.build(WITH_NO_RESPONSE_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.category_id IN (:subcategory_ids)", subcategory_ids: Category.subcategory_ids(category_id.to_i)) if category_id
+ if category_id
+ if include_subcategories
+ builder.where("t.category_id IN (?)", Category.subcategory_ids(category_id))
+ else
+ builder.where("t.category_id = ?", category_id)
+ end
+ end
builder.where("t.archetype <> '#{Archetype.private_message}'")
builder.where("t.deleted_at IS NULL")
builder.query_hash
@@ -1353,7 +1365,13 @@ class Topic < ActiveRecord::Base
def self.with_no_response_total(opts = {})
builder = DB.build(WITH_NO_RESPONSE_TOTAL_SQL)
- builder.where("t.category_id IN (:subcategory_ids)", subcategory_ids: Category.subcategory_ids(opts[:category_id].to_i)) if opts[:category_id]
+ if opts[:category_id]
+ if opts[:include_subcategories]
+ builder.where("t.category_id IN (?)", Category.subcategory_ids(opts[:category_id]))
+ else
+ builder.where("t.category_id = ?", opts[:category_id])
+ end
+ end
builder.where("t.archetype <> '#{Archetype.private_message}'")
builder.where("t.deleted_at IS NULL")
builder.query_single.first.to_i
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 39951e2b841..4ccd3d397ac 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -3411,6 +3411,8 @@ en:
label: Group
category:
label: Category
+ include-subcategories:
+ label: "Include Subcategories"
commits:
latest_changes: "Latest changes: please update often!"
diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb
index 12924fd82e1..7b4dc6dcc1e 100644
--- a/spec/models/report_spec.rb
+++ b/spec/models/report_spec.rb
@@ -771,7 +771,7 @@ describe Report do
include_examples 'category filtering'
context "on subcategories" do
- let(:report) { Report.find('flags', filters: { category: c0.id }) }
+ let(:report) { Report.find('flags', filters: { category: c0.id, 'include-subcategories': true }) }
include_examples 'category filtering on subcategories'
end
@@ -801,7 +801,7 @@ describe Report do
include_examples 'category filtering'
context "on subcategories" do
- let(:report) { Report.find('topics', filters: { category: c0.id }) }
+ let(:report) { Report.find('topics', filters: { category: c0.id, 'include-subcategories': true }) }
include_examples 'category filtering on subcategories'
end
@@ -892,7 +892,7 @@ describe Report do
include_examples 'category filtering'
context "on subcategories" do
- let(:report) { Report.find('posts', filters: { category: c0.id }) }
+ let(:report) { Report.find('posts', filters: { category: c0.id, 'include-subcategories': true }) }
include_examples 'category filtering on subcategories'
end
@@ -924,7 +924,7 @@ describe Report do
include_examples 'category filtering'
context "on subcategories" do
- let(:report) { Report.find('topics_with_no_response', filters: { category: c0.id }) }
+ let(:report) { Report.find('topics_with_no_response', filters: { category: c0.id, 'include-subcategories': true }) }
include_examples 'category filtering on subcategories'
end
@@ -960,7 +960,7 @@ describe Report do
include_examples 'category filtering'
context "on subcategories" do
- let(:report) { Report.find('likes', filters: { category: c0.id }) }
+ let(:report) { Report.find('likes', filters: { category: c0.id, 'include-subcategories': true }) }
include_examples 'category filtering on subcategories'
end
diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb
index e4db2d1d00d..d3815793009 100644
--- a/spec/models/topic_spec.rb
+++ b/spec/models/topic_spec.rb
@@ -2359,7 +2359,7 @@ describe Topic do
Fabricate(:post, topic: topic, user: topic.user, post_number: 1, created_at: 3.hours.ago)
Fabricate(:post, topic: topic, post_number: 2, created_at: 2.hours.ago)
- expect(Topic.time_to_first_response_total(category_id: category.id)).to eq(1)
+ expect(Topic.time_to_first_response_total(category_id: category.id, include_subcategories: true)).to eq(1)
end
it "should only count regular posts as the first response" do
diff --git a/test/javascripts/fixtures/reports_bulk.js b/test/javascripts/fixtures/reports_bulk.js
index c315989170d..a41cf345836 100644
--- a/test/javascripts/fixtures/reports_bulk.js
+++ b/test/javascripts/fixtures/reports_bulk.js
@@ -61,7 +61,7 @@ let signups = {
dates_filtering: true,
report_key: 'reports:signups:start:end:[:prev_period]:50:{"group":"88"}:4',
available_filters: [
- { id: "group", allow_any: false, choices: [], default: "88" }
+ { id: "group", type: "group", allow_any: false, choices: [], default: "88" }
],
labels: [
{ type: "date", properties: ["x"], title: "Day" },