FEATURE: adds a new chart report to track pageviews (#6913)
This commit is contained in:
parent
52f2e0d6b9
commit
b95165b838
|
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<div class="chart-canvas-container">
|
||||
<canvas class="chart-canvas" height="250"></canvas>
|
||||
</div>
|
|
@ -14,6 +14,11 @@
|
|||
|
||||
<div class="section-body">
|
||||
<div class="charts">
|
||||
{{admin-report
|
||||
dataSourceName="consolidated_page_views"
|
||||
forcedModes="stacked-chart"
|
||||
filters=filters}}
|
||||
|
||||
{{admin-report
|
||||
dataSourceName="signups"
|
||||
showTrend=true
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.admin-report-stacked-chart {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.chart-canvas-container {
|
||||
flex: 5;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue