audit_rails 1.1.10 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -1
  3. data/app/assets/javascripts/audit_rails/application.js +4 -0
  4. data/app/assets/javascripts/audit_rails/d3/d3.tip.js +276 -0
  5. data/app/assets/javascripts/audit_rails/d3/d3.v3.js +9300 -0
  6. data/app/assets/javascripts/audit_rails/page-views.js +71 -0
  7. data/app/assets/javascripts/audit_rails/user-counts.js +72 -0
  8. data/app/assets/stylesheets/audit_rails/audit_rails.css +62 -0
  9. data/app/controllers/audit_rails/audits_controller.rb +22 -9
  10. data/app/helpers/audit_rails/audits_helper.rb +7 -2
  11. data/app/models/audit_rails/audit.rb +12 -3
  12. data/app/views/audit_rails/audits/_form.html.erb +3 -8
  13. data/app/views/audit_rails/audits/_form_elements.html.erb +20 -0
  14. data/app/views/audit_rails/audits/_list.html.erb +12 -0
  15. data/app/views/audit_rails/audits/_page_views.html.erb +7 -20
  16. data/app/views/audit_rails/audits/_user_clicks.html.erb +6 -21
  17. data/app/views/audit_rails/audits/analytics.html.erb +1 -1
  18. data/app/views/audit_rails/audits/index.html.erb +6 -26
  19. data/app/views/layouts/audit_rails/application.html.erb +1 -5
  20. data/config/routes.rb +0 -1
  21. data/db/migrate/20121213191242_create_audit_rails_audits.rb +1 -0
  22. data/lib/audit_rails/version.rb +1 -1
  23. data/spec/controllers/audit_rails/application_controller_spec.rb +7 -0
  24. data/spec/controllers/audit_rails/audits_controller_spec.rb +1 -1
  25. data/spec/dummy/db/development.sqlite3 +0 -0
  26. data/spec/dummy/db/schema.rb +1 -0
  27. data/spec/dummy/db/test.sqlite3 +0 -0
  28. data/spec/dummy/log/development.log +44 -0
  29. data/spec/dummy/log/test.log +5977 -0
  30. data/spec/models/audit_rails/audit_spec.rb +2 -2
  31. metadata +8 -2
@@ -0,0 +1,71 @@
1
+ var data = JSON.parse( $('div#pageViewsCount').attr('data-visit-count'));
2
+ var right = d3.max([20, (900 - data.length * 50)]);
3
+ var margin = {top: 40, right: right, bottom: 100, left: 40},
4
+ width = 960 - margin.left - margin.right,
5
+ height = 360 - margin.top - margin.bottom;
6
+
7
+ var x = d3.scale.ordinal()
8
+ .rangeRoundBands([0, width], .1);
9
+
10
+ var y = d3.scale.linear()
11
+ .range([height, 0]);
12
+
13
+ var xAxis = d3.svg.axis()
14
+ .scale(x)
15
+ .orient("bottom");
16
+
17
+ var yAxis = d3.svg.axis()
18
+ .scale(y)
19
+ .orient("left");
20
+
21
+ var tip = d3.tip()
22
+ .attr('class', 'd3-tip')
23
+ .offset([-10, 0])
24
+ .html(function(d) {
25
+ return "<strong>Page Views:</strong> <span style='color:red'>" + d.count + "</span>";
26
+ })
27
+
28
+ var svg = d3.select("div#pageViewsCount").append("svg")
29
+ .attr("width", width + margin.left + margin.right)
30
+ .attr("height", height + margin.top + margin.bottom)
31
+ .append("g")
32
+ .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
33
+
34
+ svg.call(tip);
35
+
36
+ x.domain(data.map(function(d) { return d.page; }));
37
+ y.domain([0, d3.max(data, function(d) { return d.count; })]);
38
+
39
+ svg.append("g")
40
+ .attr("class", "x axis")
41
+ .attr("transform", "translate(0," + height + ")")
42
+ .call(xAxis)
43
+ .selectAll("text")
44
+ .style("text-anchor", "end")
45
+ .attr("dx", "-.8em")
46
+ .attr("dy", ".15em")
47
+ .attr("transform", function(d) {
48
+ return "rotate(-65)"
49
+ });
50
+
51
+ svg.append("g")
52
+ .attr("class", "y axis")
53
+ .call(yAxis)
54
+ .append("text")
55
+ .attr("transform", "rotate(-90)")
56
+ .attr("y", -40)
57
+ .attr("x", -80)
58
+ .attr("dy", "0.71em")
59
+ .style("text-anchor", "end")
60
+ .text("Page Views");
61
+
62
+ svg.selectAll(".bar")
63
+ .data(data)
64
+ .enter().append("rect")
65
+ .attr("class", "bar")
66
+ .attr("x", function(d) { return x(d.page); })
67
+ .attr("width", x.rangeBand())
68
+ .attr("y", function(d) { return y(d.count); })
69
+ .attr("height", function(d) { return height - y(d.count); })
70
+ .on('mouseover', tip.show)
71
+ .on('mouseout', tip.hide);
@@ -0,0 +1,72 @@
1
+ var data = JSON.parse( $('div#userViewsCount').attr('data-visit-count'));
2
+
3
+ var right = d3.max([20, (900 - data.length * 50)]);
4
+ var margin = {top: 40, right: right, bottom: 100, left: 40},
5
+ width = 960 - margin.left - margin.right,
6
+ height = 360 - margin.top - margin.bottom;
7
+
8
+ var x = d3.scale.ordinal()
9
+ .rangeRoundBands([0, width], .1);
10
+
11
+ var y = d3.scale.linear()
12
+ .range([height, 0]);
13
+
14
+ var xAxis = d3.svg.axis()
15
+ .scale(x)
16
+ .orient("bottom");
17
+
18
+ var yAxis = d3.svg.axis()
19
+ .scale(y)
20
+ .orient("left");
21
+
22
+ var tip = d3.tip()
23
+ .attr('class', 'd3-tip')
24
+ .offset([-10, 0])
25
+ .html(function(d) {
26
+ return "<strong>Page Views:</strong> <span style='color:red'>" + d.count + "</span>";
27
+ })
28
+
29
+ var svg = d3.select("div#userViewsCount").append("svg")
30
+ .attr("width", width + margin.left + margin.right)
31
+ .attr("height", height + margin.top + margin.bottom)
32
+ .append("g")
33
+ .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
34
+
35
+ svg.call(tip);
36
+
37
+ x.domain(data.map(function(d) { return d.user; }));
38
+ y.domain([0, d3.max(data, function(d) { return d.count; })]);
39
+
40
+ svg.append("g")
41
+ .attr("class", "x axis")
42
+ .attr("transform", "translate(0," + height + ")")
43
+ .call(xAxis)
44
+ .selectAll("text")
45
+ .style("text-anchor", "end")
46
+ .attr("dx", "-.8em")
47
+ .attr("dy", ".15em")
48
+ .attr("transform", function(d) {
49
+ return "rotate(-65)"
50
+ });
51
+
52
+ svg.append("g")
53
+ .attr("class", "y axis")
54
+ .call(yAxis)
55
+ .append("text")
56
+ .attr("transform", "rotate(-90)")
57
+ .attr("y", -40)
58
+ .attr("x", -80)
59
+ .attr("dy", "0.71em")
60
+ .style("text-anchor", "end")
61
+ .text("Page Views");
62
+
63
+ svg.selectAll(".bar")
64
+ .data(data)
65
+ .enter().append("rect")
66
+ .attr("class", "bar")
67
+ .attr("x", function(d) { return x(d.user); })
68
+ .attr("width", x.rangeBand())
69
+ .attr("y", function(d) { return y(d.count); })
70
+ .attr("height", function(d) { return height - y(d.count); })
71
+ .on('mouseover', tip.show)
72
+ .on('mouseout', tip.hide);
@@ -1,3 +1,65 @@
1
1
  .left {
2
2
  float: left;
3
3
  }
4
+ .audit-row{
5
+ width: 60%;
6
+ border-radius: 15px;
7
+ }
8
+ .clear{
9
+ clear: both;
10
+ }
11
+ .axis path,
12
+ .axis line {
13
+ fill: none;
14
+ stroke: #000;
15
+ shape-rendering: crispEdges;
16
+ }
17
+
18
+ .bar {
19
+ fill: orange;
20
+ }
21
+
22
+ .bar:hover {
23
+ fill: orangered ;
24
+ }
25
+
26
+ .line {
27
+ fill: white;
28
+ border-color: orange;
29
+ }
30
+ .line:hover {
31
+ border-color: orangered;
32
+ }
33
+
34
+ .x.axis path {
35
+ /*display: none;*/
36
+ }
37
+
38
+ .d3-tip {
39
+ line-height: 1;
40
+ font-weight: bold;
41
+ padding: 12px;
42
+ background: rgba(0, 0, 0, 0.8);
43
+ color: #fff;
44
+ border-radius: 2px;
45
+ }
46
+
47
+ /* Creates a small triangle extender for the tooltip */
48
+ .d3-tip:after {
49
+ box-sizing: border-box;
50
+ display: inline;
51
+ font-size: 10px;
52
+ width: 100%;
53
+ line-height: 1;
54
+ color: rgba(0, 0, 0, 0.8);
55
+ content: "\25BC";
56
+ position: absolute;
57
+ text-align: center;
58
+ }
59
+
60
+ /* Style northward tooltips differently */
61
+ .d3-tip.n:after {
62
+ margin: -1px 0 0 0;
63
+ top: 100%;
64
+ left: 0;
65
+ }
@@ -2,16 +2,14 @@ require_dependency "audit_rails/application_controller"
2
2
 
3
3
  module AuditRails
4
4
  class AuditsController < ApplicationController
5
+ before_filter :apply_filter, except: [:create]
5
6
 
6
7
  def index
7
- @audits = AuditRails::Audit.reverse_chronological
8
-
9
- respond_to do |format|
10
- format.html # index.html.erb
11
- format.json { render json: @audits }
12
- format.xls { send_data @audits.to_xls(:columns => [:user_name, :action, :description, :created_at],
13
- :headers => ['User name', 'Action', 'Details', 'When?']), filename: 'audits.xls'}
8
+ find_all_audits
14
9
 
10
+ if params[:commit] == "Download Filtered Report"
11
+ send_data(@audits.to_xls(:columns => [:user_name, :action, :description, :created_at],
12
+ :headers => ['User name', 'Action', 'Details', 'When?']), filename: 'audits.xls') and return
15
13
  end
16
14
  end
17
15
 
@@ -21,12 +19,27 @@ module AuditRails
21
19
  end
22
20
 
23
21
  def analytics
24
- @range_begin = params[:analytics] ? params[:analytics][:range_begin] : nil
25
- @range_end = params[:analytics] ? params[:analytics][:range_end] : nil
26
22
  @analysis_by_user_name = AuditRails::Audit.in_range(@range_begin, @range_end).analysis_by_user_name
27
23
  @analysis_by_page_views = AuditRails::Audit.in_range(@range_begin, @range_end).analysis_by_page_views
28
24
  @total = AuditRails::Audit.in_range(@range_begin, @range_end).count
29
25
  @no_audits = AuditRails::Audit.count == 0
26
+
27
+ if params[:commit] == "Download Filtered Report"
28
+ find_all_audits
29
+ send_data(@audits.to_xls(:columns => [:user_name, :action, :description, :created_at],
30
+ :headers => ['User name', 'Action', 'Details', 'When?']), filename: 'audits.xls') and return
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def apply_filter
37
+ @range_begin = params[:analytics] ? params[:analytics][:range_begin] : nil
38
+ @range_end = params[:analytics] ? params[:analytics][:range_end] : nil
39
+ end
40
+
41
+ def find_all_audits
42
+ @audits = AuditRails::Audit.reverse_chronological.in_range(@range_begin, @range_end)
30
43
  end
31
44
  end
32
45
  end
@@ -3,13 +3,18 @@ module AuditRails
3
3
  def add_to_audit(action_name=nil, controller_name=nil, user_name=nil, description=nil)
4
4
  if action_name == "login"
5
5
  if AuditRails::Audit.no_audit_entry_for_today?(action_name, user_name)
6
- AuditRails::Audit.create(action: action_name, controller: controller_name, user_name: user_name, description: description)
6
+ AuditRails::Audit.create(action: action_name,
7
+ controller: controller_name || request.params[:controller],
8
+ user_name: user_name,
9
+ description: description,
10
+ ip_address: request.remote_ip.to_s)
7
11
  end
8
12
  else
9
13
  AuditRails::Audit.create(action: action_name || request.params[:action],
10
14
  controller: controller_name || request.params[:controller],
11
15
  user_name: user_name,
12
- description: description)
16
+ description: description,
17
+ ip_address: request.remote_ip.to_s)
13
18
  end
14
19
  end
15
20
 
@@ -5,7 +5,7 @@ module AuditRails
5
5
  end
6
6
 
7
7
  if needs_attr_accessible?
8
- attr_accessible :action, :controller, :description, :user_name
8
+ attr_accessible :action, :controller, :description, :user_name, :ip_address
9
9
  end
10
10
 
11
11
  # Supports both string and date format of range given
@@ -19,6 +19,7 @@ module AuditRails
19
19
  scope :reverse_chronological, ->{order('created_at DESC')}
20
20
  scope :group_by_controller_action, ->{group([:controller, :action])}
21
21
  scope :group_by_user_name, ->{group('user_name')}
22
+ scope :group_by_ip_address, ->{group('ip_address')}
22
23
 
23
24
  def self.no_audit_entry_for_today?(action_name, user_name)
24
25
  audits = where(action: action_name, user_name: user_name,
@@ -28,11 +29,19 @@ module AuditRails
28
29
  end
29
30
 
30
31
  def self.analysis_by_user_name
31
- group_by_user_name.count
32
+ group_by_user_name.count.map{|k,v| {'user' => k, 'count' => v}}.to_json
32
33
  end
33
34
 
34
35
  def self.analysis_by_page_views
35
- group_by_controller_action.count
36
+ group_by_controller_action.count.map{|k,v| {'page' => k.join('/'), 'count' => v}}.to_json
37
+ end
38
+
39
+ def self.unique_visitor_count
40
+ group_by_ip_address.count.values.size
41
+ end
42
+
43
+ def self.visitor_count
44
+ group_by_ip_address.count.values.sum
36
45
  end
37
46
  end
38
47
  end
@@ -1,10 +1,5 @@
1
- <div>
2
- <%= form_tag(analytics_audits_path) do %>
3
- Begin:
4
- <%= text_field_tag('analytics[range_begin]', @range_begin)%>
5
- &nbsp;&nbsp;&nbsp;&nbsp;
6
- End:
7
- <%= text_field_tag('analytics[range_end]', @range_end)%>
8
- <%= submit_tag('filter')%>
1
+ <div class='row'>
2
+ <%= form_tag(path, method: :get) do %>
3
+ <%= render partial: 'form_elements' %>
9
4
  <% end %>
10
5
  </div>
@@ -0,0 +1,20 @@
1
+ <div class='col-sm-2'>
2
+ <div class="input-group">
3
+ <%= text_field_tag('analytics[range_begin]', @range_begin, placeholder: 'Begin', class: 'form-control')%>
4
+ </div>
5
+ </div>
6
+ <div class='col-sm-2'>
7
+ <div class="input-group">
8
+ <%= text_field_tag('analytics[range_end]', @range_end, placeholder: 'End', class: 'form-control')%>
9
+ </div>
10
+ </div>
11
+ <div class='col-sm-1'>
12
+ <div class="input-group">
13
+ <%= submit_tag('Filter', class: 'btn btn-primary btn-sm')%>
14
+ </div>
15
+ </div>
16
+ <div class='col-sm-2'>
17
+ <div class="input-group">
18
+ <%= submit_tag('Download Filtered Report', class: 'btn btn-primary btn-sm') %>
19
+ </div>
20
+ </div>
@@ -0,0 +1,12 @@
1
+ <% audits.each do |audit| %>
2
+ <div class='audit-row well'>
3
+ <div class='audit-title'>
4
+ <%= "#{audit.controller}/" if audit.controller %><%= audit.action %> by <%= audit.user_name %> about <%= distance_of_time_in_words(audit.created_at, Time.now) %> ago
5
+ </div>
6
+ <% if audit.description %>
7
+ <div class='audit-description'>
8
+ <%= audit.description %>
9
+ </div>
10
+ <% end %>
11
+ </div>
12
+ <% end %>
@@ -1,24 +1,11 @@
1
+ <div class='clear'></div>
1
2
  <div class='left'>
2
- <div id='analyticsByPage'>Total page views: <%= @total %></div>
3
+ <ul class="nav nav-pills">
4
+ <li class="active">
5
+ <a href="#">Total page views <span class="badge"><%= @total %></span></a>
6
+ </li>
7
+ </ul>
3
8
  <br/>
4
- <canvas id="pageViews" height="300" width="400"></canvas>
5
- <script>
9
+ <div id='pageViewsCount' data-visit-count='<%= @analysis_by_page_views %>'></div>
6
10
 
7
- var lineChartData = {
8
- labels : <%= "#{@analysis_by_page_views.keys}".html_safe %>,
9
- datasets : [
10
- {
11
- fillColor : "rgba(151,187,205,0.5)",
12
- strokeColor : "rgba(220,220,220,1)",
13
- pointColor : "rgba(220,220,220,1)",
14
- pointStrokeColor : "#fff",
15
- data : <%= @analysis_by_page_views.values %>
16
- }
17
- ]
18
-
19
- }
20
-
21
- var myLine = new Chart(document.getElementById("pageViews").getContext("2d")).Line(lineChartData, {scaleLineWidth : 2, scaleLineColor : "rgba(0,0,0,.1)", scaleSteps : <%= @analysis_by_user_name.values.max/5 + 2%>});
22
-
23
- </script>
24
11
  </div>
@@ -1,24 +1,9 @@
1
1
  <div class='left'>
2
- <div id='analyticsByUser'>Total user clicks : <%= @total %></div>
2
+ <ul class="nav nav-pills">
3
+ <li class="active">
4
+ <a href="#">Total user clicks <span class="badge"><%= @total %></span></a>
5
+ </li>
6
+ </ul>
3
7
  <br/>
4
- <canvas id="userViews" height="300" width="400"></canvas>
5
- <script>
6
-
7
- var barChartData = {
8
- labels : <%= "#{@analysis_by_user_name.keys}".html_safe %>,
9
- datasets : [
10
- {
11
- fillColor : "rgba(151,187,205,0.5)",
12
- strokeColor : "rgba(220,220,220,1)",
13
- pointColor : "rgba(220,220,220,1)",
14
- pointStrokeColor : "#fff",
15
- data : <%= @analysis_by_user_name.values %>
16
- }
17
- ]
18
-
19
- }
20
-
21
- var myBar = new Chart(document.getElementById("userViews").getContext("2d")).Bar(barChartData, {scaleLineWidth : 2, scaleLineColor : "rgba(0,0,0,.1)", scaleSteps : <%= @analysis_by_user_name.values.max/5 + 2%>});
22
-
23
- </script>
8
+ <div id='userViewsCount' data-visit-count='<%= @analysis_by_user_name %>'></div>
24
9
  </div>
@@ -2,7 +2,7 @@
2
2
  <h1>Analytics</h1>
3
3
  </div>
4
4
 
5
- <%= render partial: 'form' %>
5
+ <%= render partial: 'form', locals: {path: audit_rails.analytics_audits_path} %>
6
6
  <br/>
7
7
  <br/>
8
8
  <% if @analysis_by_user_name == {} %>