FEATURE: initial implementation of generic filters for reports

This commit is contained in:
Joffrey JAFFEUX 2019-04-26 12:17:10 +02:00 committed by GitHub
parent 4b455e741e
commit bcca2b5d73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 361 additions and 314 deletions

View File

@ -1,7 +1,7 @@
import ReportLoader from "discourse/lib/reports-loader";
import Category from "discourse/models/category";
import { exportEntity } from "discourse/lib/export-csv";
import { outputExportResult } from "discourse/lib/export-result";
import { isNumeric } from "discourse/lib/utilities";
import { SCHEMA_VERSION, default as Report } from "admin/models/report";
import computed from "ember-addons/ember-computed-decorators";
@ -50,21 +50,15 @@ export default Ember.Component.extend({
filters: null,
startDate: null,
endDate: null,
category: null,
groupId: null,
filter: null,
showTrend: false,
showHeader: true,
showTitle: true,
showFilteringUI: false,
showCategoryOptions: Ember.computed.alias("model.category_filtering"),
showDatesOptions: Ember.computed.alias("model.dates_filtering"),
showGroupOptions: Ember.computed.alias("model.group_filtering"),
showExport: Ember.computed.not("model.onlyTable"),
showRefresh: Ember.computed.or(
"showCategoryOptions",
"showDatesOptions",
"showGroupOptions"
"model.available_filters.length"
),
shouldDisplayTrend: Ember.computed.and("showTrend", "model.prev_period"),
@ -74,19 +68,12 @@ export default Ember.Component.extend({
this._reports = [];
},
startDate: Ember.computed.alias("filters.startDate"),
endDate: Ember.computed.alias("filters.endDate"),
didReceiveAttrs() {
this._super(...arguments);
const state = this.get("filters") || {};
this.setProperties({
category: Category.findById(state.categoryId),
groupId: state.groupId,
filter: state.filter,
startDate: state.startDate,
endDate: state.endDate
});
if (this.get("report")) {
this._renderReport(
this.get("report"),
@ -125,8 +112,6 @@ export default Ember.Component.extend({
return displayedModesLength > 1;
},
categoryId: Ember.computed.alias("category.id"),
@computed("currentMode", "model.modes", "forcedModes")
displayedModes(currentMode, reportModes, forcedModes) {
const modes = forcedModes ? forcedModes.split(",") : reportModes;
@ -143,35 +128,11 @@ export default Ember.Component.extend({
});
},
@computed()
groupOptions() {
const arr = [
{ name: I18n.t("admin.dashboard.reports.groups"), value: "all" }
];
return arr.concat(
(this.site.groups || []).map(i => {
return { name: i["name"], value: i["id"] };
})
);
},
@computed("currentMode")
modeComponent(currentMode) {
return `admin-report-${currentMode}`;
},
@computed("model.filter_options")
filterOptions(options) {
if (options) {
return options.map(option => {
if (option.allowAny) {
option.choices.unshift(I18n.t("admin.dashboard.report_filter_any"));
}
return option;
});
}
},
@computed("startDate")
normalizedStartDate(startDate) {
return startDate && typeof startDate.isValid === "function"
@ -198,25 +159,25 @@ export default Ember.Component.extend({
@computed(
"dataSourceName",
"categoryId",
"groupId",
"filter",
"normalizedStartDate",
"normalizedEndDate"
"normalizedEndDate",
"filters.customFilters"
)
reportKey(dataSourceName, categoryId, groupId, filter, startDate, endDate) {
reportKey(dataSourceName, startDate, endDate, customFilters) {
if (!dataSourceName || !startDate || !endDate) return null;
let reportKey = "reports:";
reportKey += [
dataSourceName,
categoryId,
startDate.replace(/-/g, ""),
endDate.replace(/-/g, ""),
groupId,
filter,
"[:prev_period]",
this.get("reportOptions.table.limit"),
customFilters
? JSON.stringify(customFilters, (key, value) =>
isNumeric(value) ? value.toString() : value
)
: null,
SCHEMA_VERSION
]
.filter(x => x)
@ -227,49 +188,40 @@ export default Ember.Component.extend({
},
actions: {
filter(filterOptionId, value) {
let params = [];
let paramPairs = {};
let newParams = [];
applyFilter(id, value) {
let customFilters = this.get("filters.customFilters") || {};
if (this.get("filter")) {
const filter = this.get("filter").slice(1, -1);
params = filter.split("&") || [];
params.map(p => {
const pair = p.split("=");
paramPairs[pair[0]] = pair[1];
});
if (typeof value === "undefined") {
delete customFilters[id];
} else {
customFilters[id] = value;
}
paramPairs[filterOptionId] = value;
Object.keys(paramPairs).forEach(key => {
if (paramPairs[key] !== I18n.t("admin.dashboard.report_filter_any")) {
newParams.push(`${key}=${paramPairs[key]}`);
}
this.attrs.onRefresh({
type: this.get("model.type"),
startDate: this.get("startDate"),
endDate: this.get("endDate"),
filters: customFilters
});
this.set("filter", `[${newParams.join("&")}]`);
},
refreshReport() {
this.attrs.onRefresh({
categoryId: this.get("categoryId"),
groupId: this.get("groupId"),
filter: this.get("filter"),
startDate: this.get("startDate"),
endDate: this.get("endDate")
endDate: this.get("endDate"),
filters: this.get("filters.customFilters")
});
},
exportCsv() {
const customFilters = this.get("filters.customFilters");
exportEntity("report", {
name: this.get("model.type"),
start_date: this.get("startDate"),
end_date: this.get("endDate"),
category_id:
this.get("categoryId") === "all" ? undefined : this.get("categoryId"),
group_id:
this.get("groupId") === "all" ? undefined : this.get("groupId")
startDate: this.get("startDate"),
endDate: this.get("endDate"),
category_id: customFilters.category,
group_id: customFilters.group
}).then(outputExportResult);
},
@ -383,22 +335,14 @@ export default Ember.Component.extend({
.toISOString();
}
if (this.get("groupId") && this.get("groupId") !== "all") {
payload.data.group_id = this.get("groupId");
}
if (this.get("categoryId") && this.get("categoryId") !== "all") {
payload.data.category_id = this.get("categoryId");
}
if (this.get("filter") && this.get("filter") !== "all") {
payload.data.filter = this.get("filter");
}
if (this.get("reportOptions.table.limit")) {
payload.data.limit = this.get("reportOptions.table.limit");
}
if (this.get("filters.customFilters")) {
payload.data.filters = this.get("filters.customFilters");
}
return payload;
},
@ -443,8 +387,8 @@ export default Ember.Component.extend({
Report.fillMissingDates(jsonReport, {
filledField: "prevChartData",
dataField: "prev_data",
starDate: jsonReport.prev_start_date,
endDate: jsonReport.prev_end_date
starDate: jsonReport.prev_startDate,
endDate: jsonReport.prev_endDate
});
if (jsonReport.prevChartData && jsonReport.prevChartData.length > 40) {

View File

@ -0,0 +1,14 @@
import Category from "discourse/models/category";
import { default as computed } from "ember-addons/ember-computed-decorators";
import FilterComponent from "admin/components/report-filters/filter";
export default FilterComponent.extend({
classNames: ["category-filter"],
layoutName: "admin/templates/components/report-filters/category",
@computed("filter.default")
category(categoryId) {
return Category.findById(categoryId);
}
});

View File

@ -0,0 +1,7 @@
import FilterComponent from "admin/components/report-filters/filter";
export default FilterComponent.extend({
classNames: ["file-extension-filter"],
layoutName: "admin/templates/components/report-filters/file-extension"
});

View File

@ -0,0 +1,7 @@
export default Ember.Component.extend({
actions: {
onChange(value) {
this.applyFilter(this.get("filter.id"), value);
}
}
});

View File

@ -0,0 +1,20 @@
import FilterComponent from "admin/components/report-filters/filter";
import { default as computed } from "ember-addons/ember-computed-decorators";
export default FilterComponent.extend({
classNames: ["group-filter"],
layoutName: "admin/templates/components/report-filters/group",
@computed()
groupOptions() {
return (this.site.groups || []).map(group => {
return { name: group["name"], value: group["id"] };
});
},
@computed("filter.default")
groupId(filterDefault) {
return filterDefault ? parseInt(filterDefault, 10) : null;
}
});

View File

@ -1,7 +1,10 @@
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Controller.extend({
queryParams: ["start_date", "end_date", "category_id", "group_id", "filter"],
queryParams: ["start_date", "end_date", "filters"],
start_date: null,
end_date: null,
filters: null,
@computed("model.type")
reportOptions(type) {
@ -12,28 +15,5 @@ export default Ember.Controller.extend({
}
return options;
},
@computed("category_id", "group_id", "start_date", "end_date", "filter")
filters(categoryId, groupId, startDate, endDate, filter) {
return {
categoryId,
groupId,
filter,
startDate,
endDate
};
},
actions: {
onParamsChange(params) {
this.setProperties({
start_date: params.startDate,
filter: params.filter,
category_id: params.categoryId,
group_id: params.groupId,
end_date: params.endDate
});
}
}
});

View File

@ -8,7 +8,7 @@ import { renderAvatar } from "discourse/helpers/user-avatar";
// Change this line each time report format change
// and you want to ensure cache is reset
export const SCHEMA_VERSION = 3;
export const SCHEMA_VERSION = 4;
const Report = Discourse.Model.extend({
average: false,

View File

@ -1,27 +1,65 @@
export default Discourse.Route.extend({
setupController(controller) {
this._super(...arguments);
queryParams: {
start_date: { refreshModel: true },
end_date: { refreshModel: true },
filters: { refreshModel: true }
},
if (!controller.get("start_date")) {
controller.set(
"start_date",
moment
.utc()
.subtract(1, "day")
.subtract(1, "month")
.startOf("day")
.format("YYYY-MM-DD")
);
model(params) {
params.customFilters = params.filters;
delete params.filters;
params.startDate =
params.start_date ||
moment
.utc()
.subtract(1, "day")
.subtract(1, "month")
.startOf("day")
.format("YYYY-MM-DD");
delete params.start_date;
params.endDate =
params.end_date ||
moment
.utc()
.endOf("day")
.format("YYYY-MM-DD");
delete params.end_date;
return params;
},
deserializeQueryParam(value, urlKey, defaultValueType) {
if (urlKey === "filters") {
return JSON.parse(decodeURIComponent(value));
}
if (!controller.get("end_date")) {
controller.set(
"end_date",
moment()
.utc()
.endOf("day")
.format("YYYY-MM-DD")
);
return this._super(value, urlKey, defaultValueType);
},
serializeQueryParam(value, urlKey, defaultValueType) {
if (urlKey === "filters") {
if (value && Object.keys(value).length > 0) {
return JSON.stringify(value);
} else {
return null;
}
}
return this._super(value, urlKey, defaultValueType);
},
actions: {
onParamsChange(params) {
const queryParams = {
type: params.type,
start_date: params.startDate,
filters: params.filters,
end_date: params.endDate
};
this.transitionTo("adminReports.show", { queryParams });
}
}
});

View File

@ -149,38 +149,17 @@
</div>
{{/if}}
{{#if showCategoryOptions}}
{{#each model.available_filters as |filter|}}
<div class="control">
<div class="input">
{{search-advanced-category-chooser
filterable=true
value=category
castInteger=true}}
</div>
</div>
{{/if}}
<span class="label">
{{i18n (concat "admin.dashboard.reports.filters." filter.id ".label")}}
</span>
{{#if showGroupOptions}}
<div class="control">
<div class="input">
{{combo-box
castInteger=true
filterable=true
valueAttribute="value"
content=groupOptions
value=groupId}}
</div>
</div>
{{/if}}
{{#each filterOptions as |filterOption|}}
<div class="control">
<div class="input">
{{combo-box content=filterOption.choices
filterable=true
allowAny=true
value=filterOption.selected
onSelect=(action "filter" filterOption.id)}}
{{component
(concat "report-filters/" filter.id)
filter=filter
applyFilter=(action "applyFilter")}}
</div>
</div>
{{/each}}

View File

@ -0,0 +1,6 @@
{{search-advanced-category-chooser
filterable=true
value=category
castInteger=true
onSelectNone=(action "onChange")
onSelect=(action "onChange")}}

View File

@ -0,0 +1,8 @@
{{combo-box
content=filter.choices
filterable=true
allowAny=filter.allow_any
value=filter.default
none="admin.dashboard.report_filter_any"
onSelectNone=(action "onChange")
onSelect=(action "onChange")}}

View File

@ -0,0 +1,10 @@
{{combo-box
castInteger=true
filterable=true
valueAttribute="value"
content=groupOptions
value=groupId
allowAny=filter.allow_any
none="admin.dashboard.reports.groups"
onSelectNone=(action "onChange")
onSelect=(action "onChange")}}

View File

@ -1,7 +1,7 @@
{{admin-report
showAllReportsLink=true
dataSourceName=model.type
filters=filters
filters=model
reportOptions=reportOptions
showFilteringUI=true
onRefresh=(action "onParamsChange")}}
onRefresh=(route-action "onParamsChange")}}

View File

@ -91,18 +91,6 @@ class Admin::ReportsController < Admin::AdminController
start_date = (report_params[:start_date].present? ? Time.parse(report_params[:start_date]).to_date : 1.days.ago).beginning_of_day
end_date = (report_params[:end_date].present? ? Time.parse(report_params[:end_date]).to_date : start_date + 30.days).end_of_day
if report_params.has_key?(:category_id) && report_params[:category_id].to_i > 0
category_id = report_params[:category_id].to_i
else
category_id = nil
end
if report_params.has_key?(:group_id) && report_params[:group_id].to_i > 0
group_id = report_params[:group_id].to_i
else
group_id = nil
end
facets = nil
if Array === report_params[:facets]
facets = report_params[:facets].map { |s| s.to_s.to_sym }
@ -113,17 +101,15 @@ class Admin::ReportsController < Admin::AdminController
limit = report_params[:limit].to_i
end
filter = nil
if report_params.has_key?(:filter)
filter = report_params[:filter]
filters = nil
if report_params.has_key?(:filters)
filters = report_params[:filters]
end
{
start_date: start_date,
end_date: end_date,
category_id: category_id,
group_id: group_id,
filter: filter,
filters: filters,
facets: facets,
limit: limit
}

View File

@ -3,14 +3,13 @@ require_dependency 'topic_subtype'
class Report
# Change this line each time report format change
# and you want to ensure cache is reset
SCHEMA_VERSION = 3
SCHEMA_VERSION = 4
attr_accessor :type, :data, :total, :prev30Days, :start_date,
:end_date, :category_id, :group_id, :filter,
:labels, :async, :prev_period, :facets, :limit, :processing, :average, :percent,
:higher_is_better, :icon, :modes, :category_filtering,
:group_filtering, :prev_data, :prev_start_date, :prev_end_date,
:dates_filtering, :error, :primary_color, :secondary_color, :filter_options
:end_date, :labels, :prev_period, :facets, :limit, :average,
:percent, :higher_is_better, :icon, :modes, :prev_data,
:prev_start_date, :prev_end_date, :dates_filtering, :error,
:primary_color, :secondary_color, :filters, :available_filters
def self.default_days
30
@ -24,13 +23,11 @@ class Report
@average = false
@percent = false
@higher_is_better = true
@category_filtering = false
@group_filtering = false
@modes = [:table, :chart]
@prev_data = nil
@dates_filtering = true
@filter_options = nil
@filter = nil
@available_filters = {}
@filters = {}
tertiary = ColorScheme.hex_for_name('tertiary') || '0088cc'
@primary_color = rgba_color(tertiary)
@ -41,17 +38,24 @@ class Report
(+"reports:") <<
[
report.type,
report.category_id,
report.start_date.to_date.strftime("%Y%m%d"),
report.end_date.to_date.strftime("%Y%m%d"),
report.group_id,
report.filter,
report.facets,
report.limit,
report.filters.blank? ? nil : MultiJson.dump(report.filters),
SCHEMA_VERSION,
].compact.map(&:to_s).join(':')
end
def add_filter(name, options = {})
default_filter = { allow_any: false, choices: [], default: nil }
available_filters[name] = default_filter.merge(options)
end
def remove_filter(name)
available_filters.delete(name)
end
def self.clear_cache(type = nil)
pattern = type ? "reports:#{type}:*" : "reports:*"
@ -76,13 +80,6 @@ class Report
self.start_date
end
def filter_values
if self.filter.present?
return self.filter.delete_prefix("[").delete_suffix("]").split("&").map { |param| param.split("=") }.to_h
end
{}
end
def as_json(options = nil)
description = I18n.t("reports.#{type}.description", default: "")
{
@ -97,14 +94,12 @@ class Report
prev_data: self.prev_data,
prev_start_date: prev_start_date&.iso8601,
prev_end_date: prev_end_date&.iso8601,
category_id: category_id,
group_id: group_id,
filter: self.filter,
prev30Days: self.prev30Days,
dates_filtering: self.dates_filtering,
report_key: Report.cache_key(self),
primary_color: self.primary_color,
secondary_color: self.secondary_color,
available_filters: self.available_filters.map { |k, v| { id: k }.merge(v) },
labels: labels || [
{
type: :date,
@ -117,13 +112,9 @@ class Report
title: I18n.t("reports.default.labels.count")
},
],
processing: self.processing,
average: self.average,
percent: self.percent,
higher_is_better: self.higher_is_better,
category_filtering: self.category_filtering,
group_filtering: self.group_filtering,
filter_options: self.filter_options,
modes: self.modes,
}.tap do |json|
json[:icon] = self.icon if self.icon
@ -150,15 +141,12 @@ class Report
report = Report.new(type)
report.start_date = opts[:start_date] if opts[:start_date]
report.end_date = opts[:end_date] if opts[:end_date]
report.category_id = opts[:category_id] if opts[:category_id]
report.group_id = opts[:group_id] if opts[:group_id]
report.filter = opts[:filter] if opts[:filter]
report.facets = opts[:facets] || [:total, :prev30Days]
report.limit = opts[:limit] if opts[:limit]
report.processing = false
report.average = opts[:average] if opts[:average]
report.percent = opts[:percent] if opts[:percent]
report.higher_is_better = opts[:higher_is_better] if opts[:higher_is_better]
report.filters = opts[:filters] if opts[:filters]
report
end
@ -192,7 +180,6 @@ class Report
report.error = :timeout
end
rescue Exception => e
# In test mode, don't swallow exceptions by default to help debug errors.
raise if Rails.env.test? && !opts[:wrap_exceptions_in_test]
@ -288,12 +275,15 @@ 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)
report.data = []
PostAction.count_per_day_for_type(post_action_type, category_id: report.category_id, 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_filter, 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(report.category_id)) if report.category_id
countable = countable.joins(post: :topic).merge(Topic.in_category_and_subcategories(category_filter)) if category_filter
add_counts report, countable, 'post_actions.created_at'
end

View File

@ -1,5 +1,5 @@
Report.add_report("bookmarks") do |report|
report.category_filtering = true
Report.add_report('bookmarks') do |report|
report.icon = 'bookmark'
post_action_report report, PostActionType.types[:bookmark]
end

View File

@ -1,5 +1,7 @@
Report.add_report("flags") do |report|
report.category_filtering = true
Report.add_report('flags') do |report|
category_filter = report.filters.dig(:category)
report.add_filter('category', default: category_filter)
report.icon = 'flag'
report.higher_is_better = false
@ -9,11 +11,14 @@ Report.add_report("flags") do |report|
:count_by_date,
report.start_date,
report.end_date,
report.category_id
category_filter
)
countable = ReviewableFlaggedPost.scores_with_topics
countable.merge!(Topic.in_category_and_subcategories(report.category_id)) if report.category_id
if category_filter
countable.merge!(Topic.in_category_and_subcategories(category_filter))
end
add_counts report, countable, 'reviewable_scores.created_at'
end

View File

@ -1,5 +1,5 @@
Report.add_report("likes") do |report|
report.category_filtering = true
report.icon = 'heart'
post_action_report report, PostActionType.types[:like]
end

View File

@ -1,5 +1,7 @@
Report.add_report("post_edits") do |report|
report.category_filtering = true
Report.add_report('post_edits') do |report|
category_filter = report.filters.dig(:category)
report.add_filter('category', default: category_filter)
report.modes = [:table]
report.labels = [
@ -77,14 +79,14 @@ Report.add_report("post_edits") do |report|
ON u.id = p.user_id
SQL
if report.category_id
if category_filter
sql += <<~SQL
JOIN topics t
ON t.id = p.topic_id
WHERE t.category_id = ? OR t.category_id IN (SELECT id FROM categories WHERE categories.parent_category_id = ?)
SQL
end
result = report.category_id ? DB.query(sql, report.category_id, report.category_id) : DB.query(sql)
result = category_filter ? DB.query(sql, category_filter, category_filter) : DB.query(sql)
result.each do |r|
revision = {}

View File

@ -1,10 +1,14 @@
Report.add_report("posts") do |report|
Report.add_report('posts') do |report|
report.modes = [:table, :chart]
report.category_filtering = true
basic_report_about report, Post, :public_posts_count_per_day, report.start_date, report.end_date, report.category_id
category_filter = report.filters.dig(:category)
report.add_filter('category', default: category_filter)
basic_report_about report, Post, :public_posts_count_per_day, report.start_date, report.end_date, category_filter
countable = Post.public_posts.where(post_type: Post.types[:regular])
if report.category_id
countable = countable.joins(:topic).merge(Topic.in_category_and_subcategories(report.category_id))
if category_filter
countable = countable.joins(:topic).merge(Topic.in_category_and_subcategories(category_filter))
end
add_counts report, countable, 'posts.created_at'
end

View File

@ -1,9 +1,11 @@
Report.add_report("profile_views") do |report|
report.group_filtering = true
Report.add_report('profile_views') do |report|
group_filter = report.filters.dig(:group)
report.add_filter('group', default: group_filter)
start_date = report.start_date
end_date = report.end_date
basic_report_about report, UserProfileView, :profile_views_by_day, start_date, end_date, report.group_id
basic_report_about report, UserProfileView, :profile_views_by_day, start_date, end_date, group_filter
report.total = UserProfile.sum(:views)
report.prev30Days = UserProfileView.where("viewed_at >= ? AND viewed_at < ?", start_date - 30.days, start_date + 1).count
report.prev30Days = UserProfileView.where('viewed_at >= ? AND viewed_at < ?', start_date - 30.days, start_date + 1).count
end

View File

@ -1,14 +1,13 @@
Report.add_report("signups") do |report|
report.group_filtering = true
Report.add_report('signups') do |report|
report.icon = 'user-plus'
if report.group_id
basic_report_about report, User.real, :count_by_signup_date, report.start_date, report.end_date, report.group_id
group_filter = report.filters.dig(:group)
report.add_filter('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
add_counts report, User.real, 'users.created_at'
else
report_about report, User.real, :count_by_signup_date
end
# add_prev_data report, User.real, :count_by_signup_date, report.prev_start_date, report.prev_end_date
end

View File

@ -1,11 +1,16 @@
Report.add_report("time_to_first_response") do |report|
report.category_filtering = true
Report.add_report('time_to_first_response') do |report|
category_filter = report.filters.dig(:category)
report.add_filter('category', default: 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: report.category_id).each do |r|
report.data << { x: r["date"], y: r["hours"].to_f.round(2) }
Topic.time_to_first_response_per_day(report.start_date, report.end_date, category_id: category_filter).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: report.category_id)
report.prev30Days = Topic.time_to_first_response_total(start_date: report.start_date - 30.days, end_date: report.start_date, category_id: report.category_id)
report.total = Topic.time_to_first_response_total(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_filter)
end

View File

@ -1,5 +1,6 @@
Report.add_report("top_referred_topics") do |report|
report.category_filtering = true
Report.add_report('top_referred_topics') do |report|
category_filter = report.filters.dig(:category)
report.add_filter('category', default: category_filter)
report.modes = [:table]
@ -10,12 +11,12 @@ Report.add_report("top_referred_topics") do |report|
title: :topic_title,
id: :topic_id
},
title: I18n.t("reports.top_referred_topics.labels.topic")
title: I18n.t('reports.top_referred_topics.labels.topic')
},
{
property: :num_clicks,
type: :number,
title: I18n.t("reports.top_referred_topics.labels.num_clicks")
title: I18n.t('reports.top_referred_topics.labels.num_clicks')
}
]
@ -23,7 +24,7 @@ Report.add_report("top_referred_topics") do |report|
end_date: report.end_date,
start_date: report.start_date,
limit: report.limit || 8,
category_id: report.category_id
category_id: category_filter
}
result = nil
result = IncomingLinksReport.find(:top_referred_topics, options)

View File

@ -1,22 +1,23 @@
Report.add_report("top_traffic_sources") do |report|
report.category_filtering = true
Report.add_report('top_traffic_sources') do |report|
category_filter = report.filters.dig(:category)
report.add_filter('category', default: category_filter)
report.modes = [:table]
report.labels = [
{
property: :domain,
title: I18n.t("reports.top_traffic_sources.labels.domain")
title: I18n.t('reports.top_traffic_sources.labels.domain')
},
{
property: :num_clicks,
type: :number,
title: I18n.t("reports.top_traffic_sources.labels.num_clicks")
title: I18n.t('reports.top_traffic_sources.labels.num_clicks')
},
{
property: :num_topics,
type: :number,
title: I18n.t("reports.top_traffic_sources.labels.num_topics")
title: I18n.t('reports.top_traffic_sources.labels.num_topics')
}
]
@ -24,7 +25,7 @@ Report.add_report("top_traffic_sources") do |report|
end_date: report.end_date,
start_date: report.start_date,
limit: report.limit || 8,
category_id: report.category_id
category_id: category_filter
}
result = IncomingLinksReport.find(:top_traffic_sources, options)

View File

@ -1,14 +1,13 @@
Report.add_report("top_uploads") do |report|
Report.add_report('top_uploads') do |report|
report.modes = [:table]
report.filter_options = [
{
id: "file-extension",
selected: report.filter_values.fetch("file-extension", "any"),
choices: (SiteSetting.authorized_extensions.split("|") + report.filter_values.values).uniq,
allowAny: true
}
]
extension_filter = report.filters.dig(:"file-extension")
report.add_filter('file-extension',
default: extension_filter || 'any',
choices: (
SiteSetting.authorized_extensions.split('|') + Array(extension_filter)
).uniq
)
report.labels = [
{
@ -59,12 +58,15 @@ Report.add_report("top_uploads") do |report|
LIMIT #{report.limit || 250}
SQL
extension_filter = report.filter_values["file-extension"]
builder = DB.build(sql)
builder.where("up.id > :seeded_id_threshold", seeded_id_threshold: Upload::SEEDED_ID_THRESHOLD)
builder.where("up.created_at >= :start_date", start_date: report.start_date)
builder.where("up.created_at < :end_date", end_date: report.end_date)
builder.where("up.extension = :extension", extension: extension_filter) if extension_filter.present?
if extension_filter
builder.where("up.extension = :extension", extension: extension_filter)
end
builder.query.each do |row|
data = {}
data[:author_id] = row.user_id

View File

@ -1,7 +1,12 @@
Report.add_report("topics") do |report|
report.category_filtering = true
basic_report_about report, Topic, :listable_count_per_day, report.start_date, report.end_date, report.category_id
Report.add_report('topics') do |report|
category_filter = report.filters.dig(:category)
report.add_filter('category', default: category_filter)
basic_report_about report, Topic, :listable_count_per_day, report.start_date, report.end_date, category_filter
countable = Topic.listable_topics
countable = countable.in_category_and_subcategories(report.category_id) if report.category_id
if category_filter
countable = countable.in_category_and_subcategories(category_filter)
end
add_counts report, countable, 'topics.created_at'
end

View File

@ -1,9 +1,13 @@
Report.add_report("topics_with_no_response") do |report|
report.category_filtering = true
Report.add_report('topics_with_no_response') do |report|
category_filter = report.filters.dig(:category)
report.add_filter('category', default: category_filter)
report.data = []
Topic.with_no_response_per_day(report.start_date, report.end_date, report.category_id).each do |r|
report.data << { x: r["date"], y: r["count"].to_i }
Topic.with_no_response_per_day(report.start_date, report.end_date, category_filter).each do |r|
report.data << { x: r['date'], y: r['count'].to_i }
end
report.total = Topic.with_no_response_total(category_id: report.category_id)
report.prev30Days = Topic.with_no_response_total(start_date: report.start_date - 30.days, end_date: report.start_date, category_id: report.category_id)
report.total = Topic.with_no_response_total(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_filter)
end

View File

@ -1,9 +1,11 @@
Report.add_report("visits") do |report|
report.group_filtering = true
Report.add_report('visits') do |report|
group_filter = report.filters.dig(:group)
report.add_filter('group', default: group_filter)
report.icon = 'user'
basic_report_about report, UserVisit, :by_day, report.start_date, report.end_date, report.group_id
basic_report_about report, UserVisit, :by_day, report.start_date, report.end_date, group_filter
add_counts report, UserVisit, 'visited_at'
report.prev30Days = UserVisit.where("visited_at >= ? and visited_at < ?", report.start_date - 30.days, report.start_date).count
report.prev30Days = UserVisit.where('visited_at >= ? and visited_at < ?', report.start_date - 30.days, report.start_date).count
end

View File

@ -3110,6 +3110,13 @@ en:
trending_search:
more: '<a href="%{basePath}/admin/logs/search_logs">Search logs</a>'
disabled: 'Trending search report is disabled. Enable <a href="%{basePath}/admin/site_settings/category/all_results?filter=log%20search%20queries">log search queries</a> to collect data.'
filters:
file-extension:
label: File extension
group:
label: Group
category:
label: Category
commits:
latest_changes: "Latest changes: please update often!"

View File

@ -20,7 +20,6 @@ describe Report do
end
shared_examples 'category filtering on subcategories' do
it 'returns the filtered data' do
expect(report.total).to eq(1)
end
@ -753,12 +752,12 @@ describe Report do
end
context "with category filtering" do
let(:report) { Report.find('flags', category_id: c1.id) }
let(:report) { Report.find('flags', filters: { category: c1.id }) }
include_examples 'category filtering'
context "on subcategories" do
let(:report) { Report.find('flags', category_id: c0.id) }
let(:report) { Report.find('flags', filters: { category: c0.id }) }
include_examples 'category filtering on subcategories'
end
@ -782,12 +781,12 @@ describe Report do
end
context "with category filtering" do
let(:report) { Report.find('topics', category_id: c1.id) }
let(:report) { Report.find('topics', filters: { category: c1.id }) }
include_examples 'category filtering'
context "on subcategories" do
let(:report) { Report.find('topics', category_id: c0.id) }
let(:report) { Report.find('topics', filters: { category: c0.id }) }
include_examples 'category filtering on subcategories'
end
@ -872,12 +871,12 @@ describe Report do
end
context "with category filtering" do
let(:report) { Report.find('posts', category_id: c1.id) }
let(:report) { Report.find('posts', filters: { category: c1.id }) }
include_examples 'category filtering'
context "on subcategories" do
let(:report) { Report.find('posts', category_id: c0.id) }
let(:report) { Report.find('posts', filters: { category: c0.id }) }
include_examples 'category filtering on subcategories'
end
@ -903,12 +902,12 @@ describe Report do
end
context "with category filtering" do
let(:report) { Report.find('topics_with_no_response', category_id: c1.id) }
let(:report) { Report.find('topics_with_no_response', filters: { category: c1.id }) }
include_examples 'category filtering'
context "on subcategories" do
let(:report) { Report.find('topics_with_no_response', category_id: c0.id) }
let(:report) { Report.find('topics_with_no_response', filters: { category: c0.id }) }
include_examples 'category filtering on subcategories'
end
@ -939,12 +938,12 @@ describe Report do
end
context "with category filtering" do
let(:report) { Report.find('likes', category_id: c1.id) }
let(:report) { Report.find('likes', filters: { category: c1.id }) }
include_examples 'category filtering'
context "on subcategories" do
let(:report) { Report.find('likes', category_id: c0.id) }
let(:report) { Report.find('likes', filters: { category: c0.id }) }
include_examples 'category filtering on subcategories'
end

View File

@ -4,6 +4,18 @@ acceptance("Dashboard", {
loggedIn: true,
settings: {
dashboard_general_tab_activity_metrics: "page_view_total_reqs"
},
site: {
groups: [
{
id: 88,
name: "tl1"
},
{
id: 89,
name: "tl2"
}
]
}
});
@ -86,3 +98,17 @@ QUnit.test("reports tab", async assert => {
"filter is case insensitive"
);
});
QUnit.test("report filters", async assert => {
await visit(
'/admin/reports/signups?end_date=2018-07-16&filters=%7B"group"%3A88%7D&start_date=2018-06-16'
);
const groupFilter = selectKit(".group-filter .combo-box");
assert.equal(
groupFilter.header().value(),
88,
"its set the value of the filter from the query params"
);
});

View File

@ -57,21 +57,20 @@ let signups = {
],
prev_start_date: "2018-05-17T00:00:00Z",
prev_end_date: "2018-06-17T00:00:00Z",
category_id: null,
group_id: null,
prev30Days: null,
dates_filtering: true,
report_key: "reports:signups::20180616:20180716::[:prev_period]:",
report_key:
'reports:signups:20180616:20180716:[:prev_period]:50:{"group":"88"}:4',
available_filters: [
{ id: "group", allow_any: false, choices: [], default: "88" }
],
labels: [
{ type: "date", properties: ["x"], title: "Day" },
{ type: "number", properties: ["y"], title: "Count" }
],
processing: false,
average: false,
percent: false,
higher_is_better: true,
category_filtering: false,
group_filtering: true,
modes: ["table", "chart"],
prev_period: 961
};
@ -158,8 +157,6 @@ const page_view_total_reqs = {
prev_data: null,
prev_start_date: "2018-06-20T00:00:00Z",
prev_end_date: "2018-07-23T00:00:00Z",
category_id: null,
group_id: null,
prev30Days: 58110,
dates_filtering: true,
report_key: `reports:page_view_total_reqs:${startDate.format(
@ -169,12 +166,9 @@ const page_view_total_reqs = {
{ type: "date", property: "x", title: "Day" },
{ type: "number", property: "y", title: "Count" }
],
processing: false,
average: false,
percent: false,
higher_is_better: true,
category_filtering: false,
group_filtering: false,
modes: ["table", "chart"],
icon: "file",
total: 921672