FEATURE: allows to display charts by day/week/month (#10325)
This commit is contained in:
parent
bc11769118
commit
0c7eaa57b2
|
@ -8,6 +8,7 @@ export default Component.extend({
|
|||
classNames: ["admin-report-chart"],
|
||||
limit: 8,
|
||||
total: 0,
|
||||
options: null,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
@ -49,7 +50,11 @@ export default Component.extend({
|
|||
if (!chartCanvas) return;
|
||||
|
||||
const context = chartCanvas.getContext("2d");
|
||||
const chartData = makeArray(model.get("chartData") || model.get("data"));
|
||||
const chartData = this._applyChartGrouping(
|
||||
model,
|
||||
makeArray(model.get("chartData") || model.get("data"), "weekly"),
|
||||
this.options
|
||||
);
|
||||
const prevChartData = makeArray(
|
||||
model.get("prevChartData") || model.get("prev_data")
|
||||
);
|
||||
|
@ -91,11 +96,14 @@ export default Component.extend({
|
|||
return;
|
||||
}
|
||||
|
||||
this._chart = new window.Chart(context, this._buildChartConfig(data));
|
||||
this._chart = new window.Chart(
|
||||
context,
|
||||
this._buildChartConfig(data, this.options)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
_buildChartConfig(data) {
|
||||
_buildChartConfig(data, options) {
|
||||
return {
|
||||
type: "line",
|
||||
data,
|
||||
|
@ -144,7 +152,7 @@ export default Component.extend({
|
|||
gridLines: { display: false },
|
||||
type: "time",
|
||||
time: {
|
||||
parser: "YYYY-MM-DD"
|
||||
unit: this._unitForGrouping(options)
|
||||
},
|
||||
ticks: {
|
||||
sampleSize: 5,
|
||||
|
@ -163,5 +171,65 @@ export default Component.extend({
|
|||
this._chart.destroy();
|
||||
this._chart = null;
|
||||
}
|
||||
},
|
||||
|
||||
_applyChartGrouping(model, data, options) {
|
||||
if (!options.chartGrouping || options.chartGrouping === "daily") {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (
|
||||
options.chartGrouping === "weekly" ||
|
||||
options.chartGrouping === "monthly"
|
||||
) {
|
||||
const isoKind = options.chartGrouping === "weekly" ? "isoWeek" : "month";
|
||||
const kind = options.chartGrouping === "weekly" ? "week" : "month";
|
||||
const startMoment = moment(model.start_date, "YYYY-MM-DD");
|
||||
|
||||
let currentIndex = 0;
|
||||
let currentStart = startMoment.clone().startOf(isoKind);
|
||||
let currentEnd = startMoment.clone().endOf(isoKind);
|
||||
const transformedData = [
|
||||
{
|
||||
x: currentStart.format("YYYY-MM-DD"),
|
||||
y: 0
|
||||
}
|
||||
];
|
||||
|
||||
data.forEach(d => {
|
||||
let date = moment(d.x, "YYYY-MM-DD");
|
||||
|
||||
if (!date.isBetween(currentStart, currentEnd)) {
|
||||
currentIndex += 1;
|
||||
currentStart = currentStart.add(1, kind).startOf(isoKind);
|
||||
currentEnd = currentEnd.add(1, kind).endOf(isoKind);
|
||||
}
|
||||
|
||||
if (transformedData[currentIndex]) {
|
||||
transformedData[currentIndex].y += d.y;
|
||||
} else {
|
||||
transformedData[currentIndex] = {
|
||||
x: d.x,
|
||||
y: d.y
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return transformedData;
|
||||
}
|
||||
|
||||
// ensure we return something if grouping is unknown
|
||||
return data;
|
||||
},
|
||||
|
||||
_unitForGrouping(options) {
|
||||
switch (options.chartGrouping) {
|
||||
case "monthly":
|
||||
return "month";
|
||||
case "weekly":
|
||||
return "week";
|
||||
default:
|
||||
return "day";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -131,6 +131,18 @@ export default Component.extend({
|
|||
return displayedModesLength > 1;
|
||||
},
|
||||
|
||||
@discourseComputed("currentMode")
|
||||
isChartMode(currentMode) {
|
||||
return currentMode === "chart";
|
||||
},
|
||||
|
||||
@action
|
||||
changeGrouping(grouping) {
|
||||
this.send("refreshReport", {
|
||||
chartGrouping: grouping
|
||||
});
|
||||
},
|
||||
|
||||
@discourseComputed("currentMode", "model.modes", "forcedModes")
|
||||
displayedModes(currentMode, reportModes, forcedModes) {
|
||||
const modes = forcedModes ? forcedModes.split(",") : reportModes;
|
||||
|
@ -184,6 +196,19 @@ export default Component.extend({
|
|||
return reportKey;
|
||||
},
|
||||
|
||||
@discourseComputed("reportOptions.chartGrouping")
|
||||
chartGroupings(chartGrouping) {
|
||||
chartGrouping = chartGrouping || "daily";
|
||||
|
||||
return ["daily", "weekly", "monthly"].map(id => {
|
||||
return {
|
||||
id,
|
||||
label: `admin.dashboard.reports.${id}`,
|
||||
class: `chart-grouping ${chartGrouping === id ? "active" : "inactive"}`
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
@action
|
||||
onChangeDateRange(range) {
|
||||
this.send("refreshReport", {
|
||||
|
@ -211,6 +236,7 @@ export default Component.extend({
|
|||
refreshReport(options = {}) {
|
||||
this.attrs.onRefresh({
|
||||
type: this.get("model.type"),
|
||||
chartGrouping: options.chartGrouping,
|
||||
startDate:
|
||||
typeof options.startDate === "undefined"
|
||||
? this.startDate
|
||||
|
@ -243,6 +269,10 @@ export default Component.extend({
|
|||
@action
|
||||
changeMode(mode) {
|
||||
this.set("currentMode", mode);
|
||||
|
||||
this.send("refreshReport", {
|
||||
chartGrouping: null
|
||||
});
|
||||
},
|
||||
|
||||
_computeReport() {
|
||||
|
@ -364,7 +394,9 @@ export default Component.extend({
|
|||
} else {
|
||||
const chartOptions = JSON.parse(JSON.stringify(CHART_OPTIONS));
|
||||
return EmberObject.create(
|
||||
Object.assign(chartOptions, this.get("reportOptions.chart") || {})
|
||||
Object.assign(chartOptions, this.get("reportOptions.chart") || {}, {
|
||||
chartGrouping: this.get("reportOptions.chartGrouping")
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -2,10 +2,11 @@ import discourseComputed from "discourse-common/utils/decorators";
|
|||
import Controller from "@ember/controller";
|
||||
|
||||
export default Controller.extend({
|
||||
queryParams: ["start_date", "end_date", "filters"],
|
||||
queryParams: ["start_date", "end_date", "filters", "chart_grouping"],
|
||||
start_date: null,
|
||||
end_date: null,
|
||||
filters: null,
|
||||
chart_grouping: null,
|
||||
|
||||
@discourseComputed("model.type")
|
||||
reportOptions(type) {
|
||||
|
@ -15,6 +16,8 @@ export default Controller.extend({
|
|||
options.table.limit = 10;
|
||||
}
|
||||
|
||||
options.chartGrouping = this.chart_grouping;
|
||||
|
||||
return options;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -4,7 +4,8 @@ export default DiscourseRoute.extend({
|
|||
queryParams: {
|
||||
start_date: { refreshModel: true },
|
||||
end_date: { refreshModel: true },
|
||||
filters: { refreshModel: true }
|
||||
filters: { refreshModel: true },
|
||||
chart_grouping: { refreshModel: true }
|
||||
},
|
||||
|
||||
model(params) {
|
||||
|
@ -27,6 +28,9 @@ export default DiscourseRoute.extend({
|
|||
.format("YYYY-MM-DD");
|
||||
delete params.end_date;
|
||||
|
||||
params.chartGrouping = params.chart_grouping || "daily";
|
||||
delete params.chart_grouping;
|
||||
|
||||
return params;
|
||||
},
|
||||
|
||||
|
@ -57,6 +61,7 @@ export default DiscourseRoute.extend({
|
|||
start_date: params.startDate
|
||||
? params.startDate.toISOString(true).split("T")[0]
|
||||
: null,
|
||||
chart_grouping: params.chartGrouping,
|
||||
filters: params.filters,
|
||||
end_date: params.endDate
|
||||
? params.endDate.toISOString(true).split("T")[0]
|
||||
|
|
|
@ -130,6 +130,18 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if isChartMode}}
|
||||
<div class="chart-groupings">
|
||||
{{#each chartGroupings as |chartGrouping|}}
|
||||
{{d-button
|
||||
label=chartGrouping.label
|
||||
action=(action "changeGrouping" chartGrouping.id)
|
||||
class=chartGrouping.class
|
||||
}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if showDatesOptions}}
|
||||
<div class="control">
|
||||
<span class="label">
|
||||
|
|
|
@ -140,6 +140,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
.chart-groupings {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, 1fr);
|
||||
grid-gap: 0.5em;
|
||||
margin-bottom: 1em;
|
||||
|
||||
.chart-grouping.active {
|
||||
background: $tertiary;
|
||||
color: $secondary;
|
||||
}
|
||||
}
|
||||
|
||||
.control {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
.admin-report-chart {
|
||||
animation: fadein 2s;
|
||||
|
||||
.chart-canvas-container {
|
||||
position: relative;
|
||||
height: 50vh;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3500,6 +3500,9 @@ en:
|
|||
view_table: "table"
|
||||
view_graph: "graph"
|
||||
refresh_report: "Refresh Report"
|
||||
daily: Daily
|
||||
monthly: Monthly
|
||||
weekly: Weekly
|
||||
dates: "Dates (UTC)"
|
||||
groups: "All groups"
|
||||
disabled: "This report is disabled"
|
||||
|
@ -3630,7 +3633,6 @@ en:
|
|||
optional_allowed_parameters: Allowed Parameters (optional)
|
||||
any_parameter: (any parameter)
|
||||
|
||||
|
||||
web_hooks:
|
||||
title: "Webhooks"
|
||||
none: "There are no webhooks right now."
|
||||
|
|
Loading…
Reference in New Issue