diff --git a/app/controllers/api/v0/statistics_controller.rb b/app/controllers/api/v0/statistics_controller.rb
index b3b95a4..5567512 100644
--- a/app/controllers/api/v0/statistics_controller.rb
+++ b/app/controllers/api/v0/statistics_controller.rb
@@ -3,23 +3,47 @@
class StatisticsController < Api::V0::BaseController
before_action :authenticate
- ##
- # GET
- # @return a count of users who joined DMPonline between the optional specified dates
- # users are scoped to the organisation of the user initiating the call
+ # GET /api/v0/statistics/users_joined?start_date=&end_date=&org_id=
+ # Returns the number of users joined for the user's org.
+ # If start_date is passed, only counts those with created_at is >= than start_date
+ # If end_date is passed, only counts those with created_at is <= than end_date are
+ # If org_id is passed and user has super_admin privileges that counter is performed against org_id param instead of user's org
+ # @return
def users_joined
raise Pundit::NotAuthorizedError unless Api::V0::StatisticsPolicy.new(@user, :statistics).users_joined?
- users = restrict_date_range(@user.org.users)
- confirmed_users = []
- users.each do |user|
- unless user.confirmed_at.blank?
- confirmed_users += [user]
- end
- end
- @users_count = confirmed_users.count
- respond_with @users_count
- end
+ scoped = User.unscoped.where.not(confirmed_at: nil)
+ if @user.can_super_admin? && params[:org_id].present?
+ scoped = scoped.where(org_id: params[:org_id])
+ else
+ scoped = scoped.where(org_id: @user.org_id)
+ end
+
+ if params[:range_dates].present?
+ r = {}
+ params[:range_dates].each_pair do |k, v|
+ r[k] = scoped
+ .where('created_at >=?', v['start_date'])
+ .where('created_at <=?', v['end_date']).count
+ end
+ render(json: r.to_json)
+ else
+ scoped = scoped.where('created_at >= ?', Date.parse(params[:start_date])) if params[:start_date].present?
+ scoped = scoped.where('created_at <= ?', Date.parse(params[:end_date])) if params[:end_date].present?
+ @users_count = scoped.count
+ respond_with @users_count
+ end
+ end
+ # GET
+ # Returns the number of completed plans within the user's org for the data start_date and end_date specified
+ def completed_plans
+ # raise Pundit::NotAuthorizedError unless Api::V0::StatisticsPolicy.new(@user, :statistics).completed_plans?
+ # users = User.unscoped.where(org_id: org)
+ # roles = Role.where(access: Role.access_values_for(:creator, :administrator, :editor, :commenter).min)
+ # plan_ids = Plan.select(:id).joins(:roles).merge(roles.joins(:user).merge(users))
+ # Plan.joins(:questions).where("plans.id": plan_ids).group("plans.id").count("questions.id") plan_id followed by number of questions
+ # Plan.joins(:answers).where("plans.id": plan_ids).group("plans.id").count("answers.id") plan_id followed by numbers of answers
+ end
##
# GET
diff --git a/app/controllers/usage_controller.rb b/app/controllers/usage_controller.rb
new file mode 100644
index 0000000..08b9299
--- /dev/null
+++ b/app/controllers/usage_controller.rb
@@ -0,0 +1,7 @@
+class UsageController < ApplicationController
+ # GET /usage
+ def index
+ raise Pundit::NotAuthorizedError unless current_user.present? && (current_user.can_org_admin? || current_user.can_super_admin?)
+ render('index', locals: { orgs: Org.all })
+ end
+end
\ No newline at end of file
diff --git a/app/policies/api/v0/statistics_policy.rb b/app/policies/api/v0/statistics_policy.rb
index 155a3da..44ed714 100644
--- a/app/policies/api/v0/statistics_policy.rb
+++ b/app/policies/api/v0/statistics_policy.rb
@@ -18,6 +18,9 @@
true
end
+ def completed_plans?
+ true
+ end
##
# need to check if your org owns this template
def using_template?
diff --git a/app/views/layouts/_branding.html.erb b/app/views/layouts/_branding.html.erb
index e262438..7d89c63 100644
--- a/app/views/layouts/_branding.html.erb
+++ b/app/views/layouts/_branding.html.erb
@@ -96,6 +96,11 @@
<%= link_to(_('Themes'), super_admin_themes_path) %>
<% end %>
+ <% if current_user.can_org_admin? || current_user.can_super_admin? %>
+
>
+ <%= link_to(_('Usage'), usage_index_path) %>
+
+ <% end %>
<% end %>
diff --git a/app/views/usage/index.html.erb b/app/views/usage/index.html.erb
new file mode 100644
index 0000000..665886c
--- /dev/null
+++ b/app/views/usage/index.html.erb
@@ -0,0 +1,87 @@
+
+
+
+
<%= _('No. users joined during last year') %>
+
+
+
+
+
+
+
<%= _('Run your own filter') %>
+ <% if current_user.api_token.present? %>
+
+ <% else %>
+
+ <%= _('You don\'t have access to use the API. An api token is needed to generate usage statistics.') %>
+
+ <% end %>
+
+
+
+
+
+
+
+
+
+
<%= _('New users') %>
+
+
+
+
+
+
+
+
<%= _('Total users') %>
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index f477585..6e9684d 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -218,6 +218,8 @@
end
end
+ resources :usage, only: [:index]
+
resources :roles, only: [:create, :update, :destroy] do
member do
put :deactivate
diff --git a/lib/assets/javascripts/application.js b/lib/assets/javascripts/application.js
index ad89292..f5e2ac2 100644
--- a/lib/assets/javascripts/application.js
+++ b/lib/assets/javascripts/application.js
@@ -45,5 +45,6 @@
import './views/shared/my_org';
import './views/shared/sign_in_form';
import './views/super_admin/themes/new_edit';
+import './views/usage/index';
import './views/users/notification_preferences';
import './views/users/admin_grant_permissions';
diff --git a/lib/assets/javascripts/views/usage/index.js b/lib/assets/javascripts/views/usage/index.js
new file mode 100644
index 0000000..c38673e
--- /dev/null
+++ b/lib/assets/javascripts/views/usage/index.js
@@ -0,0 +1,79 @@
+import moment from 'moment/moment';
+import Chart from 'chart.js';
+
+$(() => {
+ const usageFormSelector = '.usage_index';
+ const apiToken = $(usageFormSelector).find('input[name="api_token"]').val();
+ // Builds an object whose keys are the topic fro the select options and value its the value
+ // associated to the attribute data-url of each option
+ const topicToURL = $(`${usageFormSelector} select[name="topic"]`).find('option').map((i, el) => {
+ const topic = $(el);
+ return { [topic.val()]: $(el).attr('data-url') };
+ }).get() // An array of objects { topic: URL }
+ .reduce((acc, value) => Object.assign(acc, value), {}); // Flatten to a single object
+ // Events
+ $(usageFormSelector).on('submit', (e) => {
+ e.preventDefault();
+ const target = $(e.target);
+ const topic = target.find('select[name="topic"]').val();
+ const orgId = target.find('select[name="org_id"]').val() || target.find('input[name="org_id"]').val();
+ $('[data-topic]').hide(); // Hides any data-topic view
+ const ajaxSettings = ({ totals = false } = {}) => ({
+ headers: { Authorization: `Token token="${apiToken}"` },
+ url: topicToURL[topic],
+ data: totals ? { topic, org_id: orgId } : target.serialize(),
+ });
+ $.when($.ajax(ajaxSettings()), $.ajax(ajaxSettings({ totals: true }))).then((r1, r2) => {
+ const view = $(`[data-topic="${topic}"]`);
+ if (topic === 'users') {
+ view.find('[data-range]').html(r1[0].users_joined);
+ view.find('[data-totals]').html(r2[0].users_joined);
+ view.show();
+ }
+ }); // TODO request error handling
+ });
+ const rangeDatesUpToLastYearFromNow = () => {
+ const getLastMonth = () => moment().subtract(1, 'month').clone();
+ const rangeDates = new Array(12).fill(1).reduce((acc, v, i) => {
+ const id = getLastMonth().subtract(i, 'month').format('MMM-YY');
+ acc[id] = {
+ start_date: getLastMonth().startOf('month').subtract(i, 'month').format('YYYY-MM-DD'),
+ end_date: getLastMonth().endOf('month').subtract(i, 'month').format('YYYY-MM-DD'),
+ id };
+ return acc;
+ }, {});
+
+ return rangeDates;
+ };
+ const initialise = () => {
+ $.ajax({
+ headers: { Authorization: `Token token="${apiToken}"` },
+ url: topicToURL.users,
+ data: { range_dates: rangeDatesUpToLastYearFromNow() },
+ }).then((data) => {
+ new Chart($('#yearly_users'), { // eslint-disable-line no-new
+ type: 'bar',
+ data: {
+ labels: Object.keys(data),
+ datasets: [{
+ data: Object.keys(data).map(k => data[k]),
+ backgroundColor: '#4F5253', // TODO parameterised according to roadmap main colour instance
+ }],
+ },
+ options: {
+ legend: {
+ display: false,
+ },
+ tooltips: {
+ callbacks: {
+ label: tooltipItem => `${tooltipItem.yLabel} users`,
+ },
+ },
+ },
+ });
+ }, (jqXHR) => {
+ console.log('error: %o', jqXHR);
+ });
+ };
+ initialise();
+});
diff --git a/lib/assets/package.json b/lib/assets/package.json
index 213eb91..36c3c16 100644
--- a/lib/assets/package.json
+++ b/lib/assets/package.json
@@ -20,12 +20,14 @@
"homepage": "https://github.com/DMPRoadmap/roadmap#readme",
"dependencies": {
"bootstrap-sass": "^3.3.7",
+ "chart.js": "^2.7.1",
"font-awesome": "^4.7.0",
"jquery": "3.2.1",
"jquery-accessible-autocomplete-list-aria": "1.5.5",
"jquery-ui-dist": "1.12.1",
"jquery-ujs": "1.2.2",
"js-cookie": "2.1.4",
+ "moment": "^2.20.1",
"number-to-text": "^0.3.2",
"placeholder": "1.0.2",
"timeago.js": "^3.0.2",