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" },