diff --git a/app/controllers/dashboards_controller.rb b/app/controllers/dashboards_controller.rb
index 121bd5612..8212d5538 100644
--- a/app/controllers/dashboards_controller.rb
+++ b/app/controllers/dashboards_controller.rb
@@ -9,15 +9,16 @@ class DashboardsController < ApplicationController
target = @user
not_authorized if params[:org_id] && !@user.org_admin?(params[:org_id] || @user.any_admin?)
- @organizations = @user.admin_organizations.includes(:users)
- @member_organizations = @user.member_organizations
+ @organizations = @user.admin_organizations
if params[:which] == "organization" && params[:org_id] && (@user.org_admin?(params[:org_id]) || @user.any_admin?)
target = @organizations.find_by(id: params[:org_id])
@organization = target
end
- @articles = target.articles.sorting(params[:sort]).decorate
- # Updates analytics in background if appropriate:
+
+ @articles = target.articles.includes(:organization).sorting(params[:sort]).decorate
+
+ # Updates analytics in background if appropriate
ArticleAnalyticsFetcher.new.delay.update_analytics(current_user.id) if @articles && ApplicationConfig["GA_FETCH_RATE"] < 50 # Rate limit concerned, sometimes we throttle down.
end
diff --git a/app/javascript/analytics/dashboard.js b/app/javascript/analytics/dashboard.js
new file mode 100644
index 000000000..dd62475bb
--- /dev/null
+++ b/app/javascript/analytics/dashboard.js
@@ -0,0 +1,226 @@
+import Chart from 'chart.js';
+
+function resetActive(activeButton) {
+ const buttons = document.getElementsByClassName('timerange-button');
+ for (let i = 0; i < buttons.length; i += 1) {
+ const button = buttons[i];
+ button.classList.remove('selected');
+ }
+
+ activeButton.classList.add('selected');
+}
+
+function sumAnalytics(data, key) {
+ return Object.entries(data).reduce((sum, day) => sum + day[1][key].total, 0);
+}
+
+function cardHTML(stat, header) {
+ return `
+
${header}
+ ${stat}
+ `;
+}
+
+function writeCards(data, timeRangeLabel) {
+ const readers = sumAnalytics(data, 'page_views');
+ const reactions = sumAnalytics(data, 'reactions');
+ const comments = sumAnalytics(data, 'comments');
+ const follows = sumAnalytics(data, 'follows');
+
+ const reactionCard = document.getElementById('reactions-card');
+ const commentCard = document.getElementById('comments-card');
+ const followerCard = document.getElementById('followers-card');
+ const readerCard = document.getElementById('readers-card');
+
+ readerCard.innerHTML = cardHTML(readers, `Readers ${timeRangeLabel}`);
+ commentCard.innerHTML = cardHTML(comments, `Comments ${timeRangeLabel}`);
+ reactionCard.innerHTML = cardHTML(reactions, `Reactions ${timeRangeLabel}`);
+ followerCard.innerHTML = cardHTML(follows, `Followers ${timeRangeLabel}`);
+}
+
+function drawChart({ canvas, title, labels, datasets }) {
+ const options = {
+ legend: {
+ position: 'bottom',
+ },
+ responsive: true,
+ title: {
+ display: true,
+ text: title,
+ },
+ scales: {
+ yAxes: [
+ {
+ ticks: {
+ suggestedMin: 0,
+ precision: 0,
+ },
+ },
+ ],
+ },
+ };
+
+ // eslint-disable-next-line no-new
+ new Chart(canvas, {
+ type: 'line',
+ data: {
+ labels,
+ datasets,
+ options,
+ },
+ });
+}
+
+function drawCharts(data, timeRangeLabel) {
+ const labels = Object.keys(data);
+ const parsedData = Object.entries(data).map(date => date[1]);
+ const comments = parsedData.map(date => date.comments.total);
+ const reactions = parsedData.map(date => date.reactions.total);
+ const likes = parsedData.map(date => date.reactions.like);
+ const readingList = parsedData.map(date => date.reactions.readinglist);
+ const unicorns = parsedData.map(date => date.reactions.unicorn);
+ const followers = parsedData.map(date => date.follows.total);
+ const readers = parsedData.map(date => date.page_views.total);
+
+ drawChart({
+ canvas: document.getElementById('reactionsChart'),
+ title: `Reactions ${timeRangeLabel}`,
+ labels,
+ datasets: [
+ {
+ label: 'Total',
+ data: reactions,
+ fill: false,
+ borderColor: 'rgb(75, 192, 192)',
+ lineTension: 0.1,
+ },
+ {
+ label: 'Likes',
+ data: likes,
+ fill: false,
+ borderColor: 'rgb(229, 100, 100)',
+ lineTension: 0.1,
+ },
+ {
+ label: 'Unicorns',
+ data: unicorns,
+ fill: false,
+ borderColor: 'rgb(157, 57, 233)',
+ lineTension: 0.1,
+ },
+ {
+ label: 'Bookmarks',
+ data: readingList,
+ fill: false,
+ borderColor: 'rgb(10, 133, 255)',
+ lineTension: 0.1,
+ },
+ ],
+ });
+
+ drawChart({
+ canvas: document.getElementById('commentsChart'),
+ title: `Comments ${timeRangeLabel}`,
+ labels,
+ datasets: [
+ {
+ label: 'Comments',
+ data: comments,
+ fill: false,
+ borderColor: 'rgb(75, 192, 192)',
+ lineTension: 0.1,
+ },
+ ],
+ });
+
+ drawChart({
+ canvas: document.getElementById('followersChart'),
+ title: `New Followers ${timeRangeLabel}`,
+ labels,
+ datasets: [
+ {
+ label: 'Followers',
+ data: followers,
+ fill: false,
+ borderColor: 'rgb(10, 133, 255)',
+ lineTension: 0.1,
+ },
+ ],
+ });
+
+ drawChart({
+ canvas: document.getElementById('readersChart'),
+ title: `Reads ${timeRangeLabel}`,
+ labels,
+ datasets: [
+ {
+ label: 'Reads',
+ data: readers,
+ fill: false,
+ borderColor: 'rgb(157, 57, 233)',
+ lineTension: 0.1,
+ },
+ ],
+ });
+}
+
+function callAnalyticsApi(date, timeRangeLabel, organizationId) {
+ let url = `/api/analytics/historical?start=${
+ date.toISOString().split('T')[0]
+ }`;
+
+ if (organizationId) {
+ url = `${url}&organization_id=${organizationId}`;
+ }
+
+ fetch(url)
+ .then(data => data.json())
+ .then(data => {
+ drawCharts(data, timeRangeLabel);
+ writeCards(data, timeRangeLabel);
+ });
+}
+
+function drawWeekCharts(organizationId) {
+ resetActive(document.getElementById('week-button'));
+ const oneWeekAgo = new Date();
+ oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
+ callAnalyticsApi(oneWeekAgo, 'this Week', organizationId);
+}
+
+function drawMonthCharts(organizationId) {
+ resetActive(document.getElementById('month-button'));
+ const oneMonthAgo = new Date();
+ oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
+ callAnalyticsApi(oneMonthAgo, 'this Month', organizationId);
+}
+
+function drawInfinityCharts(organizationId) {
+ resetActive(document.getElementById('infinity-button'));
+ // April 1st is when the DEV analytics feature went into place
+ const beginningOfTime = new Date('2019-4-1');
+ callAnalyticsApi(beginningOfTime, '', organizationId);
+}
+
+export default function initCharts({ organizationId }) {
+ const weekButton = document.getElementById('week-button');
+ weekButton.addEventListener(
+ 'click',
+ drawWeekCharts.bind(null, organizationId),
+ );
+
+ const monthButton = document.getElementById('month-button');
+ monthButton.addEventListener(
+ 'click',
+ drawMonthCharts.bind(null, organizationId),
+ );
+
+ const infinityButton = document.getElementById('infinity-button');
+ infinityButton.addEventListener(
+ 'click',
+ drawInfinityCharts.bind(null, organizationId),
+ );
+
+ // draw week charts by default
+ drawWeekCharts(organizationId);
+}
diff --git a/app/javascript/packs/analyticsDashboard.js b/app/javascript/packs/analyticsDashboard.js
new file mode 100644
index 000000000..11302d5f8
--- /dev/null
+++ b/app/javascript/packs/analyticsDashboard.js
@@ -0,0 +1,16 @@
+import initCharts from '../analytics/dashboard';
+
+function initDashboard() {
+ const activeOrg = document.querySelector('.organization.active');
+ if (activeOrg) {
+ initCharts({ organizationId: activeOrg.dataset.organizationId });
+ } else {
+ initCharts({ organizationId: null });
+ }
+}
+
+window.InstantClick.on('change', () => {
+ initDashboard();
+});
+
+initDashboard();
diff --git a/app/javascript/packs/proCharts.js b/app/javascript/packs/proCharts.js
deleted file mode 100644
index 42ac1fd10..000000000
--- a/app/javascript/packs/proCharts.js
+++ /dev/null
@@ -1,263 +0,0 @@
-import Chart from 'chart.js';
-
-const reactionsCanvas = document.getElementById('reactionsChart');
-const commentsCanvas = document.getElementById('commentsChart');
-const readersCanvas = document.getElementById('readersChart');
-const followersCanvas = document.getElementById('followersChart');
-
-const weekButton = document.getElementById('week-button');
-const monthButton = document.getElementById('month-button');
-const infinityButton = document.getElementById('infinity-button');
-
-const reactionCard = document.getElementById('reactions-card');
-const commentCard = document.getElementById('comments-card');
-const followerCard = document.getElementById('followers-card');
-const readerCard = document.getElementById('readers-card');
-
-function resetActive(activeButton) {
- [weekButton, monthButton, infinityButton].forEach(button => {
- button.classList.remove('selected');
- });
-
- activeButton.classList.add('selected');
-}
-
-function sumAnalytics(data, key) {
- return Object.entries(data).reduce((sum, day) => sum + day[1][key].total, 0);
-}
-
-function writeCard(stat, element, header) {
- element.innerHTML = `
- ${header}
- ${stat}
- `;
-}
-
-function writeCards(data, timeFrame) {
- const readers = sumAnalytics(data, 'page_views');
- const reactions = sumAnalytics(data, 'reactions');
- const comments = sumAnalytics(data, 'comments');
- const follows = sumAnalytics(data, 'follows');
-
- writeCard(readers, readerCard, `Readers ${timeFrame}`);
- writeCard(comments, commentCard, `Comments ${timeFrame}`);
- writeCard(reactions, reactionCard, `Reactions ${timeFrame}`);
- writeCard(follows, followerCard, `Followers ${timeFrame}`);
-}
-
-function callAnalyticsApi(date, timeRange) {
- fetch(`/api/analytics/historical?start=${date.toISOString().split('T')[0]}`)
- .then(data => data.json())
- .then(data => {
- drawCharts(data, timeRange);
- writeCards(data, timeRange);
- });
-}
-
-function drawWeekCharts() {
- resetActive(weekButton);
- const oneWeekAgo = new Date();
- oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
- callAnalyticsApi(oneWeekAgo, 'this Week');
-}
-
-function drawMonthCharts() {
- resetActive(monthButton);
- const oneMonthAgo = new Date();
- oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
- callAnalyticsApi(oneMonthAgo, 'this Month');
-}
-
-function drawInfinityCharts() {
- resetActive(infinityButton);
- // April 1st is when the DEV analytics feature went into place
- const beginningOfTime = new Date('2019-4-1');
- callAnalyticsApi(beginningOfTime, '');
-}
-
-drawWeekCharts();
-weekButton.addEventListener('click', drawWeekCharts);
-monthButton.addEventListener('click', drawMonthCharts);
-infinityButton.addEventListener('click', drawInfinityCharts);
-
-function drawCharts(data, timeRange) {
- const labels = Object.keys(data);
- const parsedData = Object.entries(data).map(date => date[1]);
- const comments = parsedData.map(date => date.comments.total);
- const reactions = parsedData.map(date => date.reactions.total);
- const likes = parsedData.map(date => date.reactions.like);
- const readingList = parsedData.map(date => date.reactions.readinglist);
- const unicorns = parsedData.map(date => date.reactions.unicorn);
- const followers = parsedData.map(date => date.follows.total);
- const readers = parsedData.map(date => date.page_views.total);
-
- const reactionsChart = new Chart(reactionsCanvas, {
- type: 'line',
- data: {
- labels,
- datasets: [
- {
- label: 'Total',
- data: reactions,
- // data: [5, 10, 15, 17, 25, 23],
- fill: false,
- borderColor: 'rgb(75, 192, 192)',
- lineTension: 0.1,
- },
- {
- label: 'Likes',
- data: likes,
- // data: [2, 5, 10, 10, 15, 13],
- fill: false,
- borderColor: 'rgb(229, 100, 100)',
- lineTension: 0.1,
- },
- {
- label: 'Unicorns',
- data: unicorns,
- // data: [1, 2, 2, 4, 5, 3],
- fill: false,
- borderColor: 'rgb(157, 57, 233)',
- lineTension: 0.1,
- },
- {
- label: 'Bookmarks',
- data: readingList,
- // data: [2, 3, 3, 3, 5, 7],
- fill: false,
- borderColor: 'rgb(10, 133, 255)',
- lineTension: 0.1,
- },
- ],
- },
- options: {
- legend: {
- position: 'bottom',
- },
- responsive: true,
- title: {
- display: true,
- text: `Reactions ${timeRange}`,
- },
- scales: {
- yAxes: [
- {
- ticks: {
- suggestedMin: 0,
- precision: 0,
- },
- },
- ],
- },
- },
- });
-
- const commentsChart = new Chart(commentsCanvas, {
- type: 'line',
- data: {
- labels,
- datasets: [
- {
- label: 'Comments',
- data: comments,
- fill: false,
- borderColor: 'rgb(75, 192, 192)',
- lineTension: 0.1,
- },
- ],
- },
- options: {
- legend: {
- position: 'bottom',
- },
- responsive: true,
- title: {
- display: true,
- text: `Comments ${timeRange}`,
- },
- scales: {
- yAxes: [
- {
- ticks: {
- suggestedMin: 0,
- precision: 0,
- },
- },
- ],
- },
- },
- });
-
- const followChart = new Chart(followersCanvas, {
- type: 'line',
- data: {
- labels,
- datasets: [
- {
- label: 'Followers',
- data: followers,
- fill: false,
- borderColor: 'rgb(10, 133, 255)',
- lineTension: 0.1,
- },
- ],
- },
- options: {
- legend: {
- position: 'bottom',
- },
- responsive: true,
- title: {
- display: true,
- text: `New Followers ${timeRange}`,
- },
- scales: {
- yAxes: [
- {
- ticks: {
- suggestedMin: 0,
- precision: 0,
- },
- },
- ],
- },
- },
- });
-
- const readsChart = new Chart(readersCanvas, {
- type: 'line',
- data: {
- labels,
- datasets: [
- {
- label: 'Reads',
- data: readers,
- fill: false,
- borderColor: 'rgb(157, 57, 233)',
- lineTension: 0.1,
- },
- ],
- },
- options: {
- legend: {
- position: 'bottom',
- },
- responsive: true,
- title: {
- display: true,
- text: `Reads ${timeRange}`,
- },
- scales: {
- yAxes: [
- {
- ticks: {
- suggestedMin: 0,
- precision: 0,
- },
- },
- ],
- },
- },
- });
-
-}
diff --git a/app/views/dashboards/pro.html.erb b/app/views/dashboards/pro.html.erb
index 52bb3622e..4a6aa5b08 100644
--- a/app/views/dashboards/pro.html.erb
+++ b/app/views/dashboards/pro.html.erb
@@ -2,30 +2,39 @@
+
+<%= javascript_pack_tag "analyticsDashboard", defer: true %>