diff --git a/app/assets/javascripts/admin/models/report.js b/app/assets/javascripts/admin/models/report.js new file mode 100644 index 00000000000..5acca7dcec5 --- /dev/null +++ b/app/assets/javascripts/admin/models/report.js @@ -0,0 +1,15 @@ +Discourse.Report = Discourse.Model.extend({}); + +Discourse.Report.reopenClass({ + find: function(type) { + var model = Discourse.Report.create(); + jQuery.ajax("/admin/reports/" + type, { + type: 'GET', + success: function(json) { + model.mergeAttributes(json.report); + model.set('loaded', true); + } + }); + return(model); + } +}); \ No newline at end of file diff --git a/app/assets/javascripts/admin/routes/admin_reports_route.js b/app/assets/javascripts/admin/routes/admin_reports_route.js new file mode 100644 index 00000000000..46a97b31632 --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin_reports_route.js @@ -0,0 +1,9 @@ +Discourse.AdminReportsRoute = Discourse.Route.extend({ + model: function(params) { + return(Discourse.Report.find(params.type)); + }, + + renderTemplate: function() { + this.render('admin/templates/reports', {into: 'admin/templates/admin'}); + } +}); \ No newline at end of file diff --git a/app/assets/javascripts/admin/routes/admin_routes.js b/app/assets/javascripts/admin/routes/admin_routes.js index f3a2a09a47b..7e6c0f4f746 100644 --- a/app/assets/javascripts/admin/routes/admin_routes.js +++ b/app/assets/javascripts/admin/routes/admin_routes.js @@ -11,6 +11,8 @@ Discourse.Route.buildRoutes(function() { this.route('email_logs', { path: '/email_logs' }); this.route('customize', { path: '/customize' }); + this.resource('adminReports', { path: '/reports/:type' }); + this.resource('adminFlags', { path: '/flags' }, function() { this.route('active', { path: '/active' }); this.route('old', { path: '/old' }); diff --git a/app/assets/javascripts/admin/templates/reports.js.handlebars b/app/assets/javascripts/admin/templates/reports.js.handlebars new file mode 100644 index 00000000000..6139ebbb637 --- /dev/null +++ b/app/assets/javascripts/admin/templates/reports.js.handlebars @@ -0,0 +1,20 @@ +{{#if content.loaded}} +

{{content.title}}

+ + + + + + + + {{#each content.data}} + + + + + {{/each}} +
{{content.xaxis}}{{content.yaxis}}
{{x}}{{y}}
+ +{{else}} + {{i18n loading}} +{{/if}} \ No newline at end of file diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb new file mode 100644 index 00000000000..8b2d4f066fc --- /dev/null +++ b/app/controllers/admin/reports_controller.rb @@ -0,0 +1,17 @@ +require_dependency 'report' + +class Admin::ReportsController < Admin::AdminController + + def show + + report_type = params[:type] + + raise Discourse::NotFound.new unless report_type =~ /^[a-z0-9\_]+$/ + + report = Report.find(report_type) + raise Discourse::NotFound.new if report.blank? + + render_json_dump(report: report) + end + +end diff --git a/app/models/report.rb b/app/models/report.rb new file mode 100644 index 00000000000..82e6ef2ba44 --- /dev/null +++ b/app/models/report.rb @@ -0,0 +1,37 @@ +class Report + + attr_accessor :type, :data + + def initialize(type) + @type = type + @data = nil + end + + def as_json + { + type: self.type, + title: I18n.t("reports.#{self.type}.title"), + xaxis: I18n.t("reports.#{self.type}.xaxis"), + yaxis: I18n.t("reports.#{self.type}.yaxis"), + data: self.data + } + end + + def self.find(type) + report_method = :"report_#{type}" + return nil unless respond_to?(report_method) + + # Load the report + report = Report.new(type) + send(report_method, report) + report + end + + def self.report_visits(report) + report.data = [] + UserVisit.by_day.each do |date, count| + report.data << {x: date, y: count} + end + end + +end diff --git a/app/models/user_visit.rb b/app/models/user_visit.rb index 17a937ebb5c..1510f24e788 100644 --- a/app/models/user_visit.rb +++ b/app/models/user_visit.rb @@ -1,3 +1,9 @@ class UserVisit < ActiveRecord::Base attr_accessible :visited_at, :user_id + + # A list of visits in the last month by day + def self.by_day + where("visited_at > ?", 1.month.ago).group(:visited_at).order(:visited_at).count + end + end diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 02e1133080b..0aa3963cd03 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -230,6 +230,12 @@ en: title: "Re-Subscribed!" description: "You have been re-subscribed." + reports: + visits: + title: "Users Visits by Day" + xaxis: "Day" + yaxis: "Visits" + site_settings: min_post_length: "Minimum post length in characters" max_post_length: "Maximum post length in characters" diff --git a/config/routes.rb b/config/routes.rb index b153d30c245..c78878ed12a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -24,6 +24,8 @@ Discourse::Application.routes.draw do get '' => 'admin#index' resources :site_settings + get 'reports/:type' => 'reports#show' + resources :users, id: USERNAME_ROUTE_FORMAT do collection do get 'list/:query' => 'users#index' diff --git a/spec/controllers/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb new file mode 100644 index 00000000000..40c2b1764c5 --- /dev/null +++ b/spec/controllers/admin/reports_controller_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe Admin::ReportsController do + + it "is a subclass of AdminController" do + (Admin::ReportsController < Admin::AdminController).should be_true + end + + context 'while logged in as an admin' do + let!(:admin) { log_in(:admin) } + let(:user) { Fabricate(:user) } + + + context '.show' do + + context "invalid id form" do + let(:invalid_id) { "!!&asdfasdf" } + + it "never calls Report.find" do + Report.expects(:find).never + xhr :get, :show, type: invalid_id + end + + it "returns 404" do + xhr :get, :show, type: invalid_id + response.status.should == 404 + end + end + + context "valid type form" do + + context 'missing report' do + before do + Report.expects(:find).with('active').returns(nil) + xhr :get, :show, type: 'active' + end + + it "renders the report as JSON" do + response.status.should == 404 + end + end + + context 'a report is found' do + before do + Report.expects(:find).with('active').returns(Report.new('active')) + xhr :get, :show, type: 'active' + end + + it "renders the report as JSON" do + response.should be_success + end + + it "renders the report as JSON" do + ::JSON.parse(response.body).should be_present + end + + end + + end + + end + + end + +end diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb new file mode 100644 index 00000000000..4c9c0c5d997 --- /dev/null +++ b/spec/models/report_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe Report do + + + describe 'visits report' do + + let(:report) { Report.find('visits') } + + context "no visits" do + it "returns an empty report" do + report.data.should be_blank + end + end + + context "with visits" do + let(:user) { Fabricate(:user) } + + before do + user.user_visits.create(visited_at: 1.day.ago) + user.user_visits.create(visited_at: 2.days.ago) + end + + it "returns a report with data" do + report.data.should be_present + end + + end + + + end + + +end