diff --git a/app/assets/javascripts/admin/components/admin-report-stacked-chart.js.es6 b/app/assets/javascripts/admin/components/admin-report-stacked-chart.js.es6
new file mode 100644
index 00000000000..dbc310b32a5
--- /dev/null
+++ b/app/assets/javascripts/admin/components/admin-report-stacked-chart.js.es6
@@ -0,0 +1,127 @@
+import { number } from "discourse/lib/formatter";
+import loadScript from "discourse/lib/load-script";
+
+export default Ember.Component.extend({
+ classNames: ["admin-report-chart", "admin-report-stacked-chart"],
+
+ init() {
+ this._super(...arguments);
+
+ this.resizeHandler = () =>
+ Ember.run.debounce(this, this._scheduleChartRendering, 500);
+ },
+
+ didInsertElement() {
+ this._super(...arguments);
+
+ $(window).on("resize.chart", this.resizeHandler);
+ },
+
+ willDestroyElement() {
+ this._super(...arguments);
+
+ $(window).off("resize.chart", this.resizeHandler);
+
+ this._resetChart();
+ },
+
+ didReceiveAttrs() {
+ this._super(...arguments);
+
+ Ember.run.debounce(this, this._scheduleChartRendering, 100);
+ },
+
+ _scheduleChartRendering() {
+ Ember.run.schedule("afterRender", () => {
+ this._renderChart(this.get("model"), this.$(".chart-canvas"));
+ });
+ },
+
+ _renderChart(model, $chartCanvas) {
+ if (!$chartCanvas || !$chartCanvas.length) return;
+
+ const context = $chartCanvas[0].getContext("2d");
+
+ const chartData = Ember.makeArray(
+ model.get("chartData") || model.get("data")
+ );
+
+ const data = {
+ labels: chartData[0].data.map(cd => cd.x),
+ datasets: chartData.map(cd => {
+ return {
+ label: cd.req,
+ stack: "pageviews-stack",
+ data: cd.data.map(d => Math.round(parseFloat(d.y))),
+ backgroundColor: cd.color
+ };
+ })
+ };
+
+ loadScript("/javascripts/Chart.min.js").then(() => {
+ this._resetChart();
+ this._chart = new window.Chart(context, this._buildChartConfig(data));
+ });
+ },
+
+ _buildChartConfig(data) {
+ return {
+ type: "bar",
+ data,
+ responsive: true,
+ maintainAspectRatio: false,
+ options: {
+ hover: { mode: "index" },
+ tooltips: {
+ mode: "index",
+ intersect: false,
+ callbacks: {
+ title: tooltipItem =>
+ moment(tooltipItem[0].xLabel, "YYYY-MM-DD").format("LL")
+ }
+ },
+ legend: { display: false },
+ layout: {
+ padding: {
+ left: 0,
+ top: 0,
+ right: 0,
+ bottom: 0
+ }
+ },
+ scales: {
+ yAxes: [
+ {
+ stacked: true,
+ display: true,
+ ticks: {
+ userCallback: label => {
+ if (Math.floor(label) === label) return label;
+ },
+ callback: label => number(label)
+ }
+ }
+ ],
+ xAxes: [
+ {
+ display: true,
+ gridLines: { display: false },
+ type: "time",
+ time: {
+ parser: "YYYY-MM-DD",
+ minUnit: "day"
+ }
+ }
+ ]
+ }
+ }
+ };
+ },
+
+ _resetChart() {
+ if (this._chart) {
+ this._chart.destroy();
+ this._chart = null;
+ }
+ }
+});
diff --git a/app/assets/javascripts/admin/components/admin-report.js.es6 b/app/assets/javascripts/admin/components/admin-report.js.es6
index 2fe345c9d0d..2f647f6631b 100644
--- a/app/assets/javascripts/admin/components/admin-report.js.es6
+++ b/app/assets/javascripts/admin/components/admin-report.js.es6
@@ -34,6 +34,7 @@ function collapseWeekly(data, average) {
bucket = bucket || { x: data[i].x, y: 0 };
bucket.y += data[i].y;
}
+
return aggregate;
}
@@ -389,7 +390,19 @@ export default Ember.Component.extend({
_loadReport(jsonReport) {
Report.fillMissingDates(jsonReport, { filledField: "chartData" });
- if (jsonReport.chartData && jsonReport.chartData.length > 40) {
+ if (jsonReport.chartData && jsonReport.modes[0] === "stacked_chart") {
+ jsonReport.chartData = jsonReport.chartData.map(chartData => {
+ if (chartData.length > 40) {
+ return {
+ data: collapseWeekly(chartData.data),
+ req: chartData.req,
+ color: chartData.color
+ };
+ } else {
+ return chartData;
+ }
+ });
+ } else if (jsonReport.chartData && jsonReport.chartData.length > 40) {
jsonReport.chartData = collapseWeekly(
jsonReport.chartData,
jsonReport.average
diff --git a/app/assets/javascripts/admin/models/report.js.es6 b/app/assets/javascripts/admin/models/report.js.es6
index e5a7634fb36..ae0205b9d84 100644
--- a/app/assets/javascripts/admin/models/report.js.es6
+++ b/app/assets/javascripts/admin/models/report.js.es6
@@ -473,11 +473,26 @@ Report.reopenClass({
.utc(report[endDate])
.locale("en")
.format("YYYY-MM-DD");
- report[filledField] = fillMissingDates(
- JSON.parse(JSON.stringify(report[dataField])),
- startDateFormatted,
- endDateFormatted
- );
+
+ if (report.modes[0] === "stacked_chart") {
+ report[filledField] = report[dataField].map(rep => {
+ return {
+ req: rep.req,
+ color: rep.color,
+ data: fillMissingDates(
+ JSON.parse(JSON.stringify(rep.data)),
+ startDateFormatted,
+ endDateFormatted
+ )
+ };
+ });
+ } else {
+ report[filledField] = fillMissingDates(
+ JSON.parse(JSON.stringify(report[dataField])),
+ startDateFormatted,
+ endDateFormatted
+ );
+ }
}
},
diff --git a/app/assets/javascripts/admin/templates/components/admin-report-stacked-chart.hbs b/app/assets/javascripts/admin/templates/components/admin-report-stacked-chart.hbs
new file mode 100644
index 00000000000..9c36cfacea9
--- /dev/null
+++ b/app/assets/javascripts/admin/templates/components/admin-report-stacked-chart.hbs
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/assets/javascripts/admin/templates/dashboard_next_general.hbs b/app/assets/javascripts/admin/templates/dashboard_next_general.hbs
index 000fcf152c5..9921a92ee32 100644
--- a/app/assets/javascripts/admin/templates/dashboard_next_general.hbs
+++ b/app/assets/javascripts/admin/templates/dashboard_next_general.hbs
@@ -14,6 +14,11 @@
+ {{admin-report
+ dataSourceName="consolidated_page_views"
+ forcedModes="stacked-chart"
+ filters=filters}}
+
{{admin-report
dataSourceName="signups"
showTrend=true
diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss
index f462ec295e6..17ccf65e94f 100644
--- a/app/assets/stylesheets/common/admin/admin_base.scss
+++ b/app/assets/stylesheets/common/admin/admin_base.scss
@@ -998,6 +998,7 @@ table#user-badges {
@import "common/admin/admin_report";
@import "common/admin/admin_report_counters";
@import "common/admin/admin_report_chart";
+@import "common/admin/admin_report_stacked_chart";
@import "common/admin/admin_report_table";
@import "common/admin/admin_report_inline_table";
@import "common/admin/dashboard_previous";
diff --git a/app/assets/stylesheets/common/admin/admin_report_stacked_chart.scss b/app/assets/stylesheets/common/admin/admin_report_stacked_chart.scss
new file mode 100644
index 00000000000..bbbd92c6e07
--- /dev/null
+++ b/app/assets/stylesheets/common/admin/admin_report_stacked_chart.scss
@@ -0,0 +1,9 @@
+.admin-report-stacked-chart {
+ display: flex;
+ justify-content: space-between;
+
+ .chart-canvas-container {
+ flex: 5;
+ margin: 0;
+ }
+}
diff --git a/app/assets/stylesheets/common/admin/dashboard_next.scss b/app/assets/stylesheets/common/admin/dashboard_next.scss
index 5a0b6da568a..eebb8879869 100644
--- a/app/assets/stylesheets/common/admin/dashboard_next.scss
+++ b/app/assets/stylesheets/common/admin/dashboard_next.scss
@@ -144,6 +144,11 @@
-ms-grid-rows: 1fr 1fr;
.admin-report {
-ms-grid-column-span: 4;
+
+ &.consolidated-page-views {
+ -ms-grid-column-span: 12;
+ }
+
&:nth-of-type(1) {
-ms-grid-row: 1;
-ms-grid-column: 1;
@@ -172,6 +177,10 @@
.admin-report {
grid-column: span 4;
+
+ &.consolidated-page-views {
+ grid-column: span 12;
+ }
}
@include breakpoint(medium) {
diff --git a/app/models/report.rb b/app/models/report.rb
index fa110517900..57a40b06743 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -196,6 +196,47 @@ class Report
report
end
+ def self.report_consolidated_page_views(report)
+ filters = %w[
+ page_view_crawler
+ page_view_logged_in
+ page_view_anon
+ ]
+
+ report.modes = [:stacked_chart]
+
+ tertiary = ColorScheme.hex_for_name('tertiary') || '0088cc'
+ danger = ColorScheme.hex_for_name('danger') || 'e45735'
+
+ requests = filters.map do |filter|
+ color = report.rgba_color(tertiary)
+
+ if filter == "page_view_anon"
+ color = report.rgba_color(tertiary, 0.5)
+ end
+
+ if filter == "page_view_crawler"
+ color = report.rgba_color(danger, 0.75)
+ end
+
+ {
+ req: filter,
+ color: color,
+ data: ApplicationRequest.where(req_type: ApplicationRequest.req_types[filter])
+ }
+ end
+
+ requests.each do |request|
+ request[:data] = request[:data].where('date >= ? AND date <= ?', report.start_date, report.end_date)
+ .order(date: :asc)
+ .group(:date)
+ .sum(:count)
+ .map { |date, count| { x: date, y: count } }
+ end
+
+ report.data = requests
+ end
+
def self.req_report(report, filter = nil)
data =
if filter == :page_view_total
@@ -1505,16 +1546,6 @@ class Report
end
end
- private
-
- def hex_to_rgbs(hex_color)
- hex_color = hex_color.gsub('#', '')
- rgbs = hex_color.scan(/../)
- rgbs
- .map! { |color| color.hex }
- .map! { |rgb| rgb.to_i }
- end
-
def rgba_color(hex, opacity = 1)
if hex.size == 3
chars = hex.scan(/\w/)
@@ -1529,4 +1560,14 @@ class Report
"rgba(#{rgbs.join(',')},#{opacity})"
end
+
+ private
+
+ def hex_to_rgbs(hex_color)
+ hex_color = hex_color.gsub('#', '')
+ rgbs = hex_color.scan(/../)
+ rgbs
+ .map! { |color| color.hex }
+ .map! { |rgb| rgb.to_i }
+ end
end
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index e0b34fe7b8f..5dc09aa8436 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -967,6 +967,11 @@ en:
xaxis: "Day"
yaxis: "Number of new contributors"
description: "Number of users who made their first post during this period."
+ consolidated_page_views:
+ title: "Consolidated Pageviews"
+ xaxis: "Pagesviews"
+ yaxis: "Day"
+ description: "Pageviews for logged in users, anonymous users and crawlers."
dau_by_mau:
title: "DAU/MAU"
xaxis: "Day"
diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb
index 5bc013e0af9..04f09de81a9 100644
--- a/spec/models/report_spec.rb
+++ b/spec/models/report_spec.rb
@@ -1097,4 +1097,43 @@ describe Report do
include_examples "no data"
end
+
+ describe "report_top_uploads" do
+ after do
+ ApplicationRequest.clear_cache!
+ end
+
+ let(:reports) { Report.find('consolidated_page_views') }
+
+ context "with no data" do
+ it "works" do
+ reports.data.each do |report|
+ expect(report[:data]).to be_empty
+ end
+ end
+ end
+
+ context "with data" do
+ it "works" do
+ freeze_time(Time.now.at_midnight)
+ 3.times { ApplicationRequest.increment!(:page_view_crawler) }
+ 2.times { ApplicationRequest.increment!(:page_view_logged_in) }
+ ApplicationRequest.increment!(:page_view_anon)
+ ApplicationRequest.write_cache!
+
+ page_view_crawler_report = reports.data.find { |r| r[:req] == "page_view_crawler" }
+ page_view_logged_in_report = reports.data.find { |r| r[:req] == "page_view_logged_in" }
+ page_view_anon_report = reports.data.find { |r| r[:req] == "page_view_anon" }
+
+ expect(page_view_crawler_report[:color]).to eql("rgba(228,87,53,0.75)")
+ expect(page_view_crawler_report[:data][0][:y]).to eql(3)
+
+ expect(page_view_logged_in_report[:color]).to eql("rgba(0,136,204,1)")
+ expect(page_view_logged_in_report[:data][0][:y]).to eql(2)
+
+ expect(page_view_anon_report[:color]).to eql("rgba(0,136,204,0.5)")
+ expect(page_view_anon_report[:data][0][:y]).to eql(1)
+ end
+ end
+ end
end