FEATURE: initial implementation of generic filters for reports
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
@@ -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"
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
export default Ember.Component.extend({
|
||||
actions: {
|
||||
onChange(value) {
|
||||
this.applyFilter(this.get("filter.id"), value);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{{search-advanced-category-chooser
|
||||
filterable=true
|
||||
value=category
|
||||
castInteger=true
|
||||
onSelectNone=(action "onChange")
|
||||
onSelect=(action "onChange")}}
|
||||
@@ -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")}}
|
||||
@@ -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")}}
|
||||
@@ -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")}}
|
||||
|
||||
Reference in New Issue
Block a user