audit_rails 1.1.10 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 == {} %>