FEATURE: allows to display charts by day/week/month (#10325)

FEATURE: allows to display charts by day/week/month (#10325)

diff --git a/app/assets/javascripts/admin/components/admin-report-chart.js b/app/assets/javascripts/admin/components/admin-report-chart.js
index 7af8ce0..3232b03 100644
--- a/app/assets/javascripts/admin/components/admin-report-chart.js
+++ b/app/assets/javascripts/admin/components/admin-report-chart.js
@@ -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";
+    }
   }
 });
diff --git a/app/assets/javascripts/admin/components/admin-report.js b/app/assets/javascripts/admin/components/admin-report.js
index e64e02f..234959f 100644
--- a/app/assets/javascripts/admin/components/admin-report.js
+++ b/app/assets/javascripts/admin/components/admin-report.js
@@ -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")
+        })
       );
     }
   },
diff --git a/app/assets/javascripts/admin/controllers/admin-reports-show.js b/app/assets/javascripts/admin/controllers/admin-reports-show.js
index 6d30220..f3919e4 100644
--- a/app/assets/javascripts/admin/controllers/admin-reports-show.js
+++ b/app/assets/javascripts/admin/controllers/admin-reports-show.js
@@ -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;
   }
 });
diff --git a/app/assets/javascripts/admin/routes/admin-reports-show.js b/app/assets/javascripts/admin/routes/admin-reports-show.js
index 3a8a543..234c30a 100644
--- a/app/assets/javascripts/admin/routes/admin-reports-show.js
+++ b/app/assets/javascripts/admin/routes/admin-reports-show.js
@@ -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]

[... diff too long, it was truncated ...]

GitHub sha: 0c7eaa57

1 Like

This commit appears in #10325 which was approved by eviltrout. It was merged by jjaffeux.