deforest 0.0.1

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 (72) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +37 -0
  4. data/app/assets/javascripts/deforest/application.js +14 -0
  5. data/app/assets/javascripts/deforest/logs.js +2 -0
  6. data/app/assets/stylesheets/deforest/application.css +15 -0
  7. data/app/assets/stylesheets/deforest/logs.css +4 -0
  8. data/app/controllers/deforest/application_controller.rb +5 -0
  9. data/app/controllers/deforest/files_controller.rb +50 -0
  10. data/app/helpers/deforest/application_helper.rb +4 -0
  11. data/app/helpers/deforest/logs_helper.rb +4 -0
  12. data/app/models/deforest/log.rb +37 -0
  13. data/app/views/deforest/files/dashboard.html.erb +28 -0
  14. data/app/views/deforest/files/index.html.erb +16 -0
  15. data/app/views/deforest/files/show.html.erb +39 -0
  16. data/app/views/layouts/deforest/application.html.erb +14 -0
  17. data/config/routes.rb +5 -0
  18. data/db/migrate/20230113124304_create_deforest_logs.rb +12 -0
  19. data/lib/deforest/engine.rb +5 -0
  20. data/lib/deforest/version.rb +3 -0
  21. data/lib/deforest.rb +161 -0
  22. data/lib/tasks/deforest_initializer.rb +6 -0
  23. data/lib/tasks/deforest_tasks.rake +7 -0
  24. data/test/controllers/deforest/logs_controller_test.rb +13 -0
  25. data/test/deforest_test.rb +47 -0
  26. data/test/dummy/README.rdoc +28 -0
  27. data/test/dummy/Rakefile +6 -0
  28. data/test/dummy/app/assets/javascripts/application.js +13 -0
  29. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  30. data/test/dummy/app/controllers/application_controller.rb +5 -0
  31. data/test/dummy/app/helpers/application_helper.rb +2 -0
  32. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  33. data/test/dummy/bin/bundle +3 -0
  34. data/test/dummy/bin/rails +4 -0
  35. data/test/dummy/bin/rake +4 -0
  36. data/test/dummy/bin/setup +29 -0
  37. data/test/dummy/config/application.rb +27 -0
  38. data/test/dummy/config/boot.rb +5 -0
  39. data/test/dummy/config/database.yml +25 -0
  40. data/test/dummy/config/environment.rb +5 -0
  41. data/test/dummy/config/environments/development.rb +41 -0
  42. data/test/dummy/config/environments/production.rb +79 -0
  43. data/test/dummy/config/environments/test.rb +42 -0
  44. data/test/dummy/config/initializers/assets.rb +11 -0
  45. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  46. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  47. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  48. data/test/dummy/config/initializers/inflections.rb +16 -0
  49. data/test/dummy/config/initializers/mime_types.rb +4 -0
  50. data/test/dummy/config/initializers/session_store.rb +3 -0
  51. data/test/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
  52. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  53. data/test/dummy/config/locales/en.yml +23 -0
  54. data/test/dummy/config/routes.rb +4 -0
  55. data/test/dummy/config/secrets.yml +22 -0
  56. data/test/dummy/config.ru +4 -0
  57. data/test/dummy/db/development.sqlite3 +0 -0
  58. data/test/dummy/db/migrate/20230211204438_create_users.rb +13 -0
  59. data/test/dummy/db/schema.rb +41 -0
  60. data/test/dummy/db/test.sqlite3 +0 -0
  61. data/test/dummy/deforest_db_sync.txt +1 -0
  62. data/test/dummy/log/development.log +60 -0
  63. data/test/dummy/log/test.log +3086 -0
  64. data/test/dummy/public/404.html +67 -0
  65. data/test/dummy/public/422.html +67 -0
  66. data/test/dummy/public/500.html +66 -0
  67. data/test/dummy/public/favicon.ico +0 -0
  68. data/test/fixtures/deforest/logs.yml +37 -0
  69. data/test/integration/navigation_test.rb +8 -0
  70. data/test/models/deforest/log_test.rb +45 -0
  71. data/test/test_helper.rb +21 -0
  72. metadata +247 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 10e1677827a13fb5b13d336d9e3188ff9e9532a2a3df58851018e70b9ac393ee
4
+ data.tar.gz: e107f4b02e7f55ec65fd39059a57d6f042296267883719dab2f20dc12c67d457
5
+ SHA512:
6
+ metadata.gz: 2f4c6e0fc8958773ef5e67999bda1048990acf2a9a6f73901858ea21bb7ef252ee2ff612f312ec079d474882cccfd5f292265abc670db04cd7afd37dbc958daf
7
+ data.tar.gz: c7d2c7446888150a1ce9da482147d726cb36c03e0ad285b093a63fdbb0b8aefa105e693b70bf72dea05d67c15da3d5c4743213f9963b12e535add067ab5c1447
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2023 Akshay Takkar
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Deforest'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task default: :test
@@ -0,0 +1,14 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
14
+ //= require jquery
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,5 @@
1
+ module Deforest
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+ end
5
+ end
@@ -0,0 +1,50 @@
1
+ require_dependency "deforest/application_controller"
2
+
3
+ module Deforest
4
+ class FilesController < ApplicationController
5
+ before_action :check_if_admin_logged_in
6
+
7
+ def dashboard
8
+ @top_percentile_methods = {}
9
+ @medium_percentile_methods = {}
10
+ @low_percentile_methods = {}
11
+
12
+ Deforest::Log.percentile().each do |log, pcnt|
13
+ if pcnt >= Deforest.most_used_percentile_threshold
14
+ @top_percentile_methods["#{log.model_name}##{log.method_name}"] = { color: "highlight-red", total_call_count: log.count_sum, file_name: log.file_name, line_no: log.line_no }
15
+ elsif pcnt <= Deforest.least_used_percentile_threshold
16
+ @low_percentile_methods["#{log.model_name}##{log.method_name}"] = { color: "highlight-green", total_call_count: log.count_sum, file_name: log.file_name, line_no: log.line_no }
17
+ else
18
+ @medium_percentile_methods["#{log.model_name}##{log.method_name}"] = { color: "highlight-yellow", total_call_count: log.count_sum, file_name: log.file_name, line_no: log.line_no }
19
+ end
20
+ end
21
+ end
22
+
23
+ def index
24
+ @dirs = []
25
+ @files = []
26
+ @path = params[:path] || "#{Rails.root}/app/models"
27
+ Dir.entries(@path)[2..-1].each do |m|
28
+ if Dir.exists?("#{@path}/#{m}")
29
+ @dirs << m
30
+ else
31
+ @files << m
32
+ end
33
+ end
34
+ @dirs.uniq!
35
+ end
36
+
37
+ def show
38
+ @full_path = params[:path]
39
+ # @full_path = "#{params[:path]}/#{params[:file_name]}.rb"
40
+ end
41
+
42
+ private
43
+
44
+ def check_if_admin_logged_in
45
+ if send(Deforest.current_admin_method_name).blank?
46
+ raise ActionController::RoutingError.new('Not Found')
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,4 @@
1
+ module Deforest
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Deforest
2
+ module LogsHelper
3
+ end
4
+ end
@@ -0,0 +1,37 @@
1
+ module Deforest
2
+ class Log < ActiveRecord::Base
3
+ def model_name
4
+ idx = self.file_name.index(/\/app\/models\/(\w)*.rb/)
5
+ if idx.present?
6
+ self.file_name[idx, file_name.size].gsub("/app/models/", "").chomp(".rb").camelize
7
+ end
8
+ end
9
+
10
+ def self.percentile()
11
+ grouped_logs = Deforest::Log.where("file_name like '%/app/models/%'").group(:file_name, :line_no, :method_name).select("file_name, line_no, method_name, SUM(count) AS count_sum")
12
+ groups_of_count_sum = grouped_logs.group_by { |r| r.count_sum }
13
+ n = groups_of_count_sum.size
14
+ result = Hash.new { |h,k| h[k] = nil }
15
+ groups_of_count_sum.sort_by { |count, logs| count }.each_with_index do |(_, logs), idx|
16
+ logs.each do |log, _|
17
+ result[log] = (idx.to_f / n) * 100
18
+ end
19
+ end
20
+ result
21
+ end
22
+
23
+ def self.get_highlight_colors_for_file(file_name)
24
+ result = {}
25
+ self.percentile.select { |log, _| log.file_name == file_name }.each do |log, pcnt|
26
+ result[log.line_no] = if pcnt <= Deforest.least_used_percentile_threshold
27
+ "highlight-green"
28
+ elsif pcnt >= Deforest.most_used_percentile_threshold
29
+ "highlight-red"
30
+ else
31
+ "highlight-yellow"
32
+ end
33
+ end
34
+ result
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,28 @@
1
+ <h2>Top <%= 100 - Deforest.most_used_percentile_threshold %> percentile</h2>
2
+ <% @top_percentile_methods.each do |method_name, color_and_count| %>
3
+ <a href=<%= file_path(path: "#{color_and_count[:file_name]}", line_no: color_and_count[:line_no]) %> class="<%= color_and_count[:color] %>"><%= method_name %> = <%= color_and_count[:total_call_count] %></a><br/><br/>
4
+ <% end %>
5
+
6
+ <h2>Bottom <%= Deforest.least_used_percentile_threshold %> percentile</h2>
7
+ <% @low_percentile_methods.each do |method_name, color_and_count| %>
8
+ <a href=<%= file_path(path: "#{color_and_count[:file_name]}", line_no: color_and_count[:line_no]) %> class="<%= color_and_count[:color] %>"><%= method_name %> = <%= color_and_count[:total_call_count] %></a><br/><br/>
9
+ <% end %>
10
+
11
+ <h2>Between <%= Deforest.least_used_percentile_threshold %> and <%= Deforest.most_used_percentile_threshold %> percentile</h2>
12
+ <% @medium_percentile_methods.each do |method_name, color_and_count| %>
13
+ <a href=<%= file_path(path: "#{color_and_count[:file_name]}", line_no: color_and_count[:line_no]) %> class="<%= color_and_count[:color] %>"><%= method_name %> = <%= color_and_count[:total_call_count] %></a><br/><br/>
14
+ <% end %>
15
+
16
+ <style>
17
+ .highlight-red {
18
+ background: rgba(245, 66, 72, 0.8);
19
+ }
20
+ .highlight-yellow {
21
+ background: rgba(240, 240, 5, 0.8);
22
+ color: #000;
23
+ }
24
+ .highlight-green {
25
+ background: rgba(0, 227, 38, 0.8);
26
+ color: #000;
27
+ }
28
+ </style>
@@ -0,0 +1,16 @@
1
+ <% @dirs.each do |d| %>
2
+ <div class="link dir-link"><%= link_to d, files_path(path: @path + "/#{d}") %></div>
3
+ <hr />
4
+ <% end %>
5
+ <% @files.each do |f| %>
6
+ <div class="link file-link"><%= link_to f, file_path(path: "#{@path}/#{f}") %></div>
7
+ <hr />
8
+ <% end %>
9
+
10
+ <style>
11
+ .link {
12
+ width: 100%;
13
+ padding: 10px;
14
+ # border: 1px solid #999999;
15
+ }
16
+ </style>
@@ -0,0 +1,39 @@
1
+ <code>
2
+ <%= Deforest.prepare_file_for_render(@full_path).html_safe %>
3
+ </code>
4
+
5
+ <style>
6
+ .method_call_count {
7
+ padding: 2px 10px;
8
+ background: rgba(242, 39, 39, 0.7);
9
+ border: 1px solid red;
10
+ border-radius: 55px;
11
+ color: #fff;
12
+ }
13
+ .highlight-line {
14
+ color: #fff;
15
+ padding: 2px;
16
+ }
17
+ .highlight-red {
18
+ background: rgba(245, 66, 72, 0.8);
19
+ }
20
+ .highlight-yellow {
21
+ background: rgba(240, 240, 5, 0.8);
22
+ color: #000;
23
+ }
24
+ .highlight-green {
25
+ background: rgba(0, 227, 38, 0.8);
26
+ color: #000;
27
+ }
28
+ .last_accessed {
29
+ color: #999999;
30
+ margin-left: 10px;
31
+ }
32
+ </style>
33
+
34
+ <script>
35
+ function scroll() {
36
+ document.getElementById('<%= params[:line_no] %>').scrollIntoView();
37
+ }
38
+ document.addEventListener("DOMContentLoaded", scroll);
39
+ </script>
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Deforest</title>
5
+ <%= stylesheet_link_tag "deforest/application", media: "all" %>
6
+ <%= javascript_include_tag "deforest/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ Deforest::Engine.routes.draw do
2
+ get "/files", controller: "files", action: "index"
3
+ get "/file", controller: "files", action: "show"
4
+ get "/files/dashboard", controller: "files", action: "dashboard"
5
+ end
@@ -0,0 +1,12 @@
1
+ class CreateDeforestLogs < ActiveRecord::Migration
2
+ def change
3
+ create_table :deforest_logs do |t|
4
+ t.string :file_name
5
+ t.integer :line_no
6
+ t.string :method_name
7
+ t.integer :count
8
+
9
+ t.timestamps null: false
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ module Deforest
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Deforest
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module Deforest
2
+ VERSION = "0.0.1"
3
+ end
data/lib/deforest.rb ADDED
@@ -0,0 +1,161 @@
1
+ require "deforest/engine"
2
+ require "deforest/version"
3
+ require "active_support"
4
+ require "active_record"
5
+
6
+ module Deforest
7
+ mattr_accessor :write_logs_to_db_every, :current_admin_method_name, :most_used_percentile_threshold, :least_used_percentile_threshold
8
+ @@last_saved_log_file_at = nil
9
+ @@saving_log_file = false
10
+
11
+ def self.initialize!
12
+ if block_given?
13
+ yield self
14
+ end
15
+ self.initialize_db_sync_file()
16
+ Dir["#{Rails.root}/app/models/**/*.rb"].map do |f|
17
+ idx = f.index("app/models")
18
+ models_heirarchy = f[idx..-1].gsub("app/models/","")
19
+ exec_str = ""
20
+ loop do
21
+ parent, *children = models_heirarchy.split("/")
22
+ if children.any?
23
+ exec_str += "#{parent.camelize}::"
24
+ else
25
+ exec_str += parent.chomp(".rb").camelize
26
+ break
27
+ end
28
+ models_heirarchy = children.join("/")
29
+ end
30
+ begin
31
+ model = exec_str.constantize
32
+ rescue
33
+ puts "Deforest warning: could not track #{exec_str}"
34
+ end
35
+ if model.present?
36
+ model.instance_methods(false).each do |mname|
37
+ model.instance_eval do
38
+ alias_method "old_#{mname}", mname
39
+ define_method mname do |*args, &block|
40
+ old_method = self.class.instance_method("old_#{mname}")
41
+ file_name, line_no = old_method.source_location
42
+ if file_name.include?("/app/models")
43
+ Deforest.insert_into_logs(mname, file_name, line_no)
44
+ end
45
+ Deforest.insert_into_logs(mname, file_name, line_no)
46
+ if @@last_saved_log_file_at < Deforest.write_logs_to_db_every.ago && !@@saving_log_file
47
+ Deforest.parse_and_save_log_file()
48
+ t = Time.zone.now
49
+ @@last_saved_log_file_at = t
50
+ File.open("deforest_db_sync.txt", "w") { |fl| fl.write(t.to_i) }
51
+ end
52
+ old_method.bind(self).call(*args, &block)
53
+ end
54
+ end
55
+ end
56
+ model.singleton_methods(false).each do |mname|
57
+ model.singleton_class.send(:alias_method, "old_#{mname}", mname)
58
+ model.define_singleton_method mname do |*args, &block|
59
+ old_method = self.singleton_method("old_#{mname}")
60
+ file_name, line_no = old_method.source_location
61
+ if file_name.include?("/app/models")
62
+ Deforest.insert_into_logs(mname, file_name, line_no)
63
+ end
64
+ if @@last_saved_log_file_at < Deforest.write_logs_to_db_every.ago && !@@saving_log_file
65
+ Deforest.parse_and_save_log_file()
66
+ t = Time.zone.now
67
+ @@last_saved_log_file_at = t
68
+ File.open("deforest_db_sync.txt", "w") { |fl| fl.write(t.to_i) }
69
+ end
70
+ old_method.unbind.bind(self).call(*args, &block)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ def self.initialize_db_sync_file
78
+ if File.exists?("deforest_db_sync.txt")
79
+ @@last_saved_log_file_at = Time.at(File.open("deforest_db_sync.txt").read.to_i)
80
+ else
81
+ File.open("deforest_db_sync.txt", "w") do |f|
82
+ current_time = Time.zone.now.to_i
83
+ @@last_saved_log_file_at = current_time
84
+ f.write(current_time)
85
+ end
86
+ end
87
+ end
88
+
89
+ def self.insert_into_logs(method_name, file_name, line_no)
90
+ key = "#{file_name}|#{line_no}|#{method_name}\n"
91
+ log_file_name = @@saving_log_file ? "deforest_tmp.log" : "deforest.log"
92
+ File.open(log_file_name, "a") do |f|
93
+ f.write(key)
94
+ end
95
+ end
96
+
97
+ def self.parse_and_save_log_file
98
+ @@saving_log_file = true
99
+ sql_stmt = "INSERT INTO deforest_logs (file_name, line_no, method_name, count, created_at, updated_at) VALUES "
100
+ hash = {}
101
+ File.foreach("deforest.log") do |line|
102
+ line = line.chomp("\n")
103
+ if hash.has_key?(line)
104
+ hash[line] += 1
105
+ else
106
+ hash[line] = 1
107
+ end
108
+ end
109
+ hash.each do |loc, count|
110
+ sql_stmt += "(#{loc.split("|").map { |s| "'#{s}'" }.join(",")}, #{count}, current_timestamp, current_timestamp),"
111
+ end
112
+ sql_stmt.chomp!(",")
113
+ sql_stmt += ";"
114
+ ActiveRecord::Base.connection.execute(sql_stmt)
115
+ if File.exists?("deforest_tmp.log")
116
+ File.delete("deforest.log")
117
+ File.rename("deforest_tmp.log", "deforest.log")
118
+ else
119
+ File.delete("deforest.log")
120
+ end
121
+ @@saving_log_file = false
122
+ end
123
+
124
+ def self.prepare_file_for_render(file)
125
+ line_no_count = Log.where(file_name: file).group(:line_no).select("line_no,SUM(count) AS count_sum").inject({}) { |h, el| h.merge!(el.line_no => el.count_sum) }
126
+ stack = []
127
+ current_highlight_color = nil
128
+ highlight = Log.get_highlight_colors_for_file(file)
129
+ prepare_for_render(File.open(file).read) do |line, idx|
130
+ idx += 1
131
+ if line_no_count.has_key?(idx)
132
+ stack = [1]
133
+ current_highlight_color = highlight[idx]
134
+ last_log_for_current_line = Log.where(file_name: file).where(line_no: idx).order("created_at DESC").limit(1).first
135
+ "<span id='#{idx}' class='highlight-line #{current_highlight_color}'>" +
136
+ line +
137
+ "</span>&nbsp;&nbsp;" +
138
+ "<span class='method_call_count'>#{line_no_count[idx]}</span>" +
139
+ "<span class='last_accessed'>last called at: #{last_log_for_current_line.created_at.strftime('%m/%d/%Y')}</span>"
140
+ else
141
+ "<span>#{line}</span>"
142
+ end
143
+ end
144
+ end
145
+
146
+ def self.prepare_for_render(source_code, add_line_number = true)
147
+ source_code.split("\n").map.with_index do |line, index|
148
+ first_letter_idx = line.chars.index { |ch| ch != " " }
149
+ if first_letter_idx && first_letter_idx >= 0 && first_letter_idx < line.size
150
+ leading_nbsp = (0...first_letter_idx).map { "&nbsp;" }.join("")
151
+ prepared_line = leading_nbsp.present? ? leading_nbsp + line.lstrip : line.strip
152
+ result_line = if block_given?
153
+ yield prepared_line + "\n", index
154
+ else
155
+ "<span>#{prepared_line}</span>"
156
+ end
157
+ "<span class='line-no'>#{index + 1}</span>" + result_line
158
+ end
159
+ end.join("<br/>")
160
+ end
161
+ end
@@ -0,0 +1,6 @@
1
+ Deforest.initialize! do |config|
2
+ config.write_logs_to_db_every = 1.minute
3
+ config.current_admin_method_name = :current_admin
4
+ config.most_used_percentile_threshold = 80
5
+ config.least_used_percentile_threshold = 20
6
+ end
@@ -0,0 +1,7 @@
1
+ desc "Copy initializer file to target application"
2
+ file "deforest" do
3
+ source_file = "#{Deforest::Engine.root}/lib/tasks/deforest_initializer.rb"
4
+ dest_file = "#{Rails.root}/config/initializers/deforest.rb"
5
+ FileUtils.copy_file source_file, dest_file
6
+ puts "Created initializer config/initiliazer/deforest.rb"
7
+ end
@@ -0,0 +1,13 @@
1
+ require 'test_helper'
2
+
3
+ module Deforest
4
+ class LogsControllerTest < ActionController::TestCase
5
+ setup do
6
+ @routes = Engine.routes
7
+ end
8
+
9
+ # test "the truth" do
10
+ # assert true
11
+ # end
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ require 'test_helper'
2
+
3
+ class DeforestTest < ActiveSupport::TestCase
4
+ setup do
5
+ Deforest.class_variable_set("@@last_saved_log_file_at", nil)
6
+ end
7
+
8
+ test "initialize db_sync_file when db_sync_file does not exist" do
9
+ if File.exist?("deforest_db_sync.txt")
10
+ File.delete("deforest_db_sync.txt")
11
+ Deforest.initialize_db_sync_file
12
+ assert Deforest.class_variable_get("@@last_saved_log_file_at") > 1.minute.ago.to_i
13
+ end
14
+ end
15
+
16
+ test "initialize db_sync_file when db_sync_file does exist" do
17
+ File.open("deforest_db_sync.txt", "w") { |f| f.write("1676127114") }
18
+ Deforest.initialize_db_sync_file
19
+ assert Deforest.class_variable_get("@@last_saved_log_file_at") == Time.at(1676127114)
20
+ end
21
+
22
+ test "insert into logs" do
23
+ Deforest.insert_into_logs("lock_user", "/Users/johndoe/workspace/app/models/doctor.rb", 2144)
24
+ File.foreach("deforest.log") do |line|
25
+ line = line.chomp("\n")
26
+ file_name, line_no, method_name = line.split("|")
27
+ assert file_name == "/Users/johndoe/workspace/app/models/doctor.rb", "file_name mismatched"
28
+ assert method_name == "lock_user", "method_name mismatched"
29
+ assert line_no == "2144", "line_no mismatched"
30
+ end
31
+ end
32
+
33
+ test "parse_and_save_log_file" do
34
+ Deforest.insert_into_logs("get_email", "/Users/johndoe/workspace/app/models/corporate.rb", 2144)
35
+ Deforest.insert_into_logs("get_name", "/Users/johndoe/workspace/app/models/myuser.rb", 120)
36
+ Deforest.insert_into_logs("get_title", "/Users/johndoe/workspace/app/models/post.rb", 2211)
37
+ Deforest.insert_into_logs("get_title", "/Users/johndoe/workspace/app/models/post.rb", 2211)
38
+ Deforest.insert_into_logs("get_body", "/Users/johndoe/workspace/app/models/comment.rb", 879)
39
+
40
+ Deforest.parse_and_save_log_file()
41
+
42
+ assert Deforest::Log.where(file_name: "/Users/johndoe/workspace/app/models/corporate.rb").last.count == 1
43
+ assert Deforest::Log.where(file_name: "/Users/johndoe/workspace/app/models/post.rb").last.count == 2
44
+ assert Deforest::Log.where(file_name: "/Users/johndoe/workspace/app/models/myuser.rb").last.count == 1
45
+ assert Deforest::Log.where(file_name: "/Users/johndoe/workspace/app/models/comment.rb").last.count == 1
46
+ end
47
+ end
@@ -0,0 +1,28 @@
1
+ == README
2
+
3
+ This README would normally document whatever steps are necessary to get the
4
+ application up and running.
5
+
6
+ Things you may want to cover:
7
+
8
+ * Ruby version
9
+
10
+ * System dependencies
11
+
12
+ * Configuration
13
+
14
+ * Database creation
15
+
16
+ * Database initialization
17
+
18
+ * How to run the test suite
19
+
20
+ * Services (job queues, cache servers, search engines, etc.)
21
+
22
+ * Deployment instructions
23
+
24
+ * ...
25
+
26
+
27
+ Please feel free to use a different markup language if you do not plan to run
28
+ <tt>rake doc:app</tt>.
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */