rails_notebook 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +22 -0
  4. data/app/assets/javascripts/rails_notebook/application.js +13 -0
  5. data/app/assets/stylesheets/rails_notebook/application.css +15 -0
  6. data/app/controllers/rails_notebook/application_controller.rb +4 -0
  7. data/app/helpers/rails_notebook/application_helper.rb +4 -0
  8. data/app/views/layouts/rails_notebook/application.html.erb +14 -0
  9. data/config/routes.rb +2 -0
  10. data/lib/rails_notebook.rb +14 -0
  11. data/lib/rails_notebook/assets/d3.js +9553 -0
  12. data/lib/rails_notebook/assets/dagre-d3.js +17572 -0
  13. data/lib/rails_notebook/assets/jquery.jsonview.css +52 -0
  14. data/lib/rails_notebook/assets/jquery.jsonview.js +284 -0
  15. data/lib/rails_notebook/assets/jquery.qtip.min.css +2 -0
  16. data/lib/rails_notebook/assets/jquery.qtip.min.js +3 -0
  17. data/lib/rails_notebook/assets/jquery.tipsy.js +288 -0
  18. data/lib/rails_notebook/assets/kernel.css +7 -0
  19. data/lib/rails_notebook/assets/kernel.js +18 -0
  20. data/lib/rails_notebook/assets/kernel.json +1 -0
  21. data/lib/rails_notebook/assets/logo-32x32.png +0 -0
  22. data/lib/rails_notebook/assets/logo-64x64.png +0 -0
  23. data/lib/rails_notebook/assets/nv.d3.css +641 -0
  24. data/lib/rails_notebook/assets/nv.d3.js +13298 -0
  25. data/lib/rails_notebook/assets/rails_notebook.css +136 -0
  26. data/lib/rails_notebook/assets/rails_notebook.js +548 -0
  27. data/lib/rails_notebook/assets/tipsy.css +25 -0
  28. data/lib/rails_notebook/command.rb +104 -0
  29. data/lib/rails_notebook/engine.rb +9 -0
  30. data/lib/rails_notebook/profiler.rb +111 -0
  31. data/lib/rails_notebook/renderers.rb +121 -0
  32. data/lib/rails_notebook/route.rb +121 -0
  33. data/lib/rails_notebook/schemaTable.rb +27 -0
  34. data/lib/rails_notebook/serializers.rb +92 -0
  35. data/lib/rails_notebook/table.rb +28 -0
  36. data/lib/rails_notebook/version.rb +3 -0
  37. data/lib/tasks/rails_notebook_tasks.rake +8 -0
  38. metadata +196 -0
@@ -0,0 +1,25 @@
1
+ .tipsy { font-size: 10px; position: absolute; padding: 5px; z-index: 100000; }
2
+ .tipsy-inner { background-color: #000; color: #FFF; max-width: 600px; padding: 5px 8px 4px 8px; text-align: center; }
3
+
4
+ /* Rounded corners */
5
+ .tipsy-inner { border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; }
6
+
7
+ /* Uncomment for shadow */
8
+ /*.tipsy-inner { box-shadow: 0 0 5px #000000; -webkit-box-shadow: 0 0 5px #000000; -moz-box-shadow: 0 0 5px #000000; }*/
9
+
10
+ .tipsy-arrow { position: absolute; width: 0; height: 0; line-height: 0; border: 5px dashed #000; }
11
+
12
+ /* Rules to colour arrows */
13
+ .tipsy-arrow-n { border-bottom-color: #000; }
14
+ .tipsy-arrow-s { border-top-color: #000; }
15
+ .tipsy-arrow-e { border-left-color: #000; }
16
+ .tipsy-arrow-w { border-right-color: #000; }
17
+
18
+ .tipsy-n .tipsy-arrow { top: 0px; left: 50%; margin-left: -5px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent; }
19
+ .tipsy-nw .tipsy-arrow { top: 0; left: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;}
20
+ .tipsy-ne .tipsy-arrow { top: 0; right: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;}
21
+ .tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -5px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
22
+ .tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
23
+ .tipsy-se .tipsy-arrow { bottom: 0; right: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
24
+ .tipsy-e .tipsy-arrow { right: 0; top: 50%; margin-top: -5px; border-left-style: solid; border-right: none; border-top-color: transparent; border-bottom-color: transparent; }
25
+ .tipsy-w .tipsy-arrow { left: 0; top: 50%; margin-top: -5px; border-right-style: solid; border-left: none; border-top-color: transparent; border-bottom-color: transparent; }
@@ -0,0 +1,104 @@
1
+ require 'fileutils'
2
+ require 'multi_json'
3
+
4
+ module RailsNotebook
5
+
6
+ class Command
7
+
8
+ def initialize
9
+ ipython_dir = ENV['IPYTHONDIR'] || '~/.ipython'
10
+ @kernel_dir = File.join(File.expand_path(ipython_dir), 'kernels', 'rails_notebook')
11
+ @kernel_file = File.join(@kernel_dir, 'kernel.json')
12
+ puts @kernel_file
13
+ @iruby_path = File.expand_path $0
14
+ puts @iruby_path
15
+ end
16
+
17
+ def run_kernel
18
+ require 'iruby/logger'
19
+ IRuby.logger = Rails.logger
20
+
21
+ connection_file = ENV['connection_file']
22
+ change_working_dir
23
+
24
+ IRuby::Kernel.new(connection_file).run
25
+ rescue Exception => e
26
+ IRuby.logger.fatal "Kernel died: #{e.message}\n#{e.backtrace.join("\n")}"
27
+ raise
28
+ end
29
+
30
+ def run_ipython
31
+ check_version
32
+ check_registered_kernel
33
+ change_working_dir
34
+ #create_static_symlink!
35
+ Kernel.exec('ipython', 'notebook')
36
+ end
37
+
38
+ private
39
+
40
+ #================================= For Symlink
41
+ def create_static_symlink!
42
+ src, dst = static_path, File.join(profile_path, '/kernels/rails_notebook')
43
+ puts src
44
+ puts dst
45
+ FileUtils.rm_r dst
46
+ File.symlink src, dst
47
+ end
48
+
49
+ def profile_path
50
+ `ipython locate rails_notebook`.strip
51
+ #File.expand_path(".." , `ipython locate rails_notebook`.strip)
52
+ end
53
+
54
+ def static_path
55
+ File.join(File.dirname(__FILE__), "assets")
56
+ end
57
+ #================================= For Symlink
58
+
59
+
60
+ def change_working_dir
61
+ working_dir = File.join(Rails.root, "notebooks")
62
+ Dir.mkdir(working_dir) unless Dir.exist?(working_dir)
63
+ Dir.chdir(working_dir) if working_dir
64
+ end
65
+
66
+ def check_version
67
+ required = '3.0.0'
68
+ version = `ipython --version`.chomp
69
+ if version < required
70
+ STDERR.puts "Your IPython version #{version} is too old, at least #{required} is required"
71
+ exit 1
72
+ end
73
+ end
74
+
75
+ def check_registered_kernel
76
+ if (kernel = registered_iruby_path)
77
+ STDERR.puts "#{@iruby_path} differs from registered path #{registered_iruby_path}.
78
+ This might not work. Run 'iruby register --force' to fix it." if @iruby_path != kernel
79
+ else
80
+ register_kernel
81
+ end
82
+ end
83
+
84
+ def register_kernel
85
+ FileUtils.mkpath(@kernel_dir)
86
+
87
+ if RUBY_PLATFORM =~ /mswin(?!ce)|mingw|cygwin/
88
+ ruby_path, iruby_path = [RbConfig.ruby, @iruby_path].map { |path| path.gsub('/', '\\\\') }
89
+ File.write(@kernel_file, MultiJson.dump(argv: [ruby_path, iruby_path, 'rails_notebook_kernel', 'connection_file={connection_file}'],
90
+ display_name: "Rails #{Rails.application.class.to_s}", language: 'ruby'))
91
+ else
92
+ File.write(@kernel_file, MultiJson.dump(argv: [@iruby_path, 'rails_notebook_kernel', 'connection_file={connection_file}'],
93
+ display_name: "Rails #{Rails.application.class.to_s}", language: 'ruby'))
94
+ end
95
+
96
+ FileUtils.copy(Dir[File.join(__dir__, 'assets', '*')], @kernel_dir) rescue nil
97
+ end
98
+
99
+ def registered_iruby_path
100
+ File.exist?(@kernel_file) && MultiJson.load(File.read(@kernel_file))['argv'].first
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,9 @@
1
+ module RailsNotebook
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace RailsNotebook
4
+
5
+ config.generators do |g|
6
+ g.test_framework :rspec
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,111 @@
1
+ #https://github.com/SamSaffron/flamegraph/
2
+ #Modified from Sam Saffron's original code for flamegraph
3
+ require "json"
4
+ require "stackprof"
5
+
6
+ module Profiler
7
+ def self.profile(filename=nil, opts = {})
8
+ backtraces = nil;
9
+ fidelity = opts[:fidelity] || 0.05
10
+ backtraces = StackProfSampler.collect( fidelity ) do yield end
11
+ Profiler::Profile.new( Profiler::generate_data( backtraces ) , fidelity )
12
+ end
13
+
14
+ def self.generate_data( stacks )
15
+ table = []
16
+ prev = []
17
+
18
+ # a 2d array makes collapsing easy
19
+ stacks.each_with_index do |stack, pos|
20
+ next unless stack
21
+ col = []
22
+
23
+ stack.reverse.map{|r| r.to_s}.each_with_index do |frame, i|
24
+ if !prev[i].nil?
25
+ last_col = prev[i]
26
+ if last_col[0] == frame
27
+ last_col[1] += 1
28
+ col << nil
29
+ next
30
+ end
31
+ end
32
+
33
+ prev[i] = [frame, 1]
34
+ col << prev[i]
35
+ end
36
+ prev = prev[0..col.length-1].to_a
37
+ table << col
38
+ end
39
+
40
+ data = []
41
+ # a 1d array makes rendering easy
42
+ table.each_with_index do |col, col_num|
43
+ col.each_with_index do |row, row_num|
44
+ next unless row && row.length == 2
45
+ data << {
46
+ :x => col_num + 1,
47
+ :y => row_num + 1,
48
+ :width => row[1],
49
+ :frame => row[0]
50
+ }
51
+ end
52
+ end
53
+ data
54
+ end
55
+
56
+ class Profile
57
+ def initialize( data, _sampleTime )
58
+ @data = data
59
+ @sampleTime = _sampleTime
60
+ end
61
+ end
62
+
63
+ class StackProfSampler
64
+ def self.collect(fidelity=0.5)
65
+ result = StackProf.run(mode: :wall,
66
+ raw: true,
67
+ aggregate: false,
68
+ interval: (fidelity * 1000).to_i) do
69
+ yield
70
+ end
71
+
72
+ stacks = []
73
+ stack = []
74
+
75
+ return [] unless result[:raw]
76
+
77
+ length = nil
78
+ result[:raw].each do |i|
79
+ if length.nil?
80
+ length = i
81
+ next
82
+ end
83
+
84
+ if length > 0
85
+ frame = result[:frames][i]
86
+ frame = "#{frame[:file]}:#{frame[:line]}:in `#{frame[:name]}'"
87
+ # puts frame
88
+ #if frame.to_s =~ /iruby/ && !( frame.to_s =~ /IRuby::/ )
89
+ s = frame.to_s
90
+ if !( s =~ /application.rb/ ) &&
91
+ !( s =~ /Rake::Application/) &&
92
+ !( s =~ /task.rb/) &&
93
+ !( s =~ /kernel.rb/) &&
94
+ !( s =~ /profiler.rb/)
95
+ stack << frame.to_s
96
+ end
97
+ length -= 1
98
+ next
99
+ end
100
+
101
+ i.times do
102
+ stacks << stack.reverse
103
+ end
104
+
105
+ stack = []
106
+ length = nil
107
+ end
108
+ stacks
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,121 @@
1
+ require 'json'
2
+
3
+ module RailsNotebook
4
+
5
+ module Renderers
6
+
7
+ def self.pretty_print_json( json )
8
+ <<-HTML
9
+ <div id="json-#{json.object_id}"></div>
10
+ <script>
11
+ require(["jquery", "/kernelspecs/rails_notebook/jquery.jsonview.js"], function ($, jsonview) {
12
+ $("#json-#{json.object_id}").JSONView( #{json} );
13
+ });
14
+ </script>
15
+ HTML
16
+ end
17
+
18
+ def self.render_html( object , functionName )
19
+ <<-HTML
20
+ <div id="railsnb-#{object.object_id}"></div>
21
+ <script>
22
+ require(["/kernelspecs/rails_notebook/rails_notebook.js"] , function ( railsNB ) {
23
+ railsNB.#{functionName}( #{MultiJson.dump(object)} , document.getElementById( "railsnb-#{object.object_id}" ) );
24
+ });
25
+ </script>
26
+ HTML
27
+ end
28
+ end
29
+
30
+ IRuby::Display::Registry.type { String }
31
+ IRuby::Display::Registry.format("text/html") do |string|
32
+ begin JSON.parse( string )
33
+ Renderers.pretty_print_json( string )
34
+ rescue
35
+ string
36
+ end
37
+ end
38
+
39
+ IRuby::Display::Registry.type { Rails.application }
40
+ IRuby::Display::Registry.format("text/html") do |obj|
41
+ Renderers.json_view( Serializers.serialize(obj) )
42
+ end
43
+
44
+ IRuby::Display::Registry.type { Profiler::Profile }
45
+ IRuby::Display::Registry.format("text/html") do |profiledData|
46
+ Renderers.render_html( profiledData, "renderFlamechart" )
47
+ end
48
+
49
+ IRuby::Display::Registry.type { ActionDispatch::Routing::RouteSet }
50
+ IRuby::Display::Registry.format("text/html") do |route_set|
51
+ all_routes = route_set.routes.to_a
52
+ all_routes.reject! { |route| route.verb.nil? || route.path.spec.to_s == '/assets' || ( route.path.spec.to_s =~ /^\/rails(.*)/ ) || route.path.spec.to_s =~ /^\/pages(.*)/ }
53
+ routeTree = RouteTree.new( RouteNode.new( "/" , nil, nil, nil, "" ) )
54
+ all_routes.group_by { |route| route.defaults[:controller] }.each_value do |group|
55
+ group.each do |r|
56
+ routeNode = RouteNode.new( (r.path.spec.to_s.sub '(.:format)','') ,
57
+ r.verb.inspect.gsub(/^.{2}|.{2}$/, "") ,
58
+ r.name.to_s , r.defaults[:action].to_s ,
59
+ (r.path.spec.to_s.sub '(.:format)','').split("/")[-1]
60
+ )
61
+ routeTree.insertNode( routeNode )
62
+ end
63
+ end
64
+ #routeTree.printTree() # for debugging
65
+ Renderers.render_html( routeTree, "renderRoutes" )
66
+ end
67
+
68
+ IRuby::Display::Registry.type { ActiveRecord::Base.connection.tables }
69
+ IRuby::Display::Registry.format("text/html") do |obj|
70
+ tables = [] # List of Table objects
71
+ tableNames = [] # List of table names
72
+ obj.each do |table_name|
73
+ tableNames.push(table_name.singularize.foreign_key)
74
+ end # Populates table names
75
+
76
+ obj.each do |table_name| # Finding foreign keys and populating the array of Table objects
77
+ columnsTemp = []
78
+ arrowsTo = []
79
+ ActiveRecord::Base.connection.columns(table_name).each do |c|
80
+ columnTemp = []
81
+ if (tableNames.include? c.name)
82
+ arrowsTo.push(c.name[0..-4].pluralize.downcase) # [0..-4] chops off the _id suffix for foreign keys. .humanize renmoves existing underscores, which causes bugs.
83
+ columnTemp.push("* " + c.name)
84
+ columnTemp.push(c.type.to_s)
85
+ else
86
+ columnTemp.push("- " + c.name)
87
+ columnTemp.push(c.type.to_s)
88
+ end
89
+ columnsTemp.push(columnTemp)
90
+ end # end iterating through columns in tables
91
+ tables.push(Table.new(table_name , columnsTemp , arrowsTo ))
92
+ end # end iterating through tables
93
+ tables = tables.sort_by { |x| x.arrowsTo.length }
94
+ Renderers.render_html( tables, "renderSchema" ) # Parses the array of tables to Javascript for rendering
95
+ end
96
+
97
+ IRuby::Display::Registry.type { ActiveRecord::Relation}
98
+ IRuby::Display::Registry.format("text/html") do |obj|
99
+ columnNames = []
100
+ tableData = []
101
+ tableData.push(obj.name)
102
+ obj.columns.each do |column|
103
+ columnNames.push(column.name)
104
+ end
105
+ columnNames.each do |index|
106
+ tempValues = []
107
+ tempValues.push(index)
108
+ obj[0,10].each do |row| # Display only the first ten rows
109
+ tempValues.push(row[index])
110
+ end
111
+ tableData.push(tempValues)
112
+ end
113
+ Renderers.render_html( tableData, "renderTableData" )
114
+ end # DatabaseQueries
115
+
116
+ IRuby::Display::Registry.type { SchemaTable::ChartRenderer }
117
+ IRuby::Display::Registry.format("text/html") do |obj|
118
+ Renderers.render_html( obj.data , "renderBarChart" )
119
+ end
120
+
121
+ end
@@ -0,0 +1,121 @@
1
+ class RouteNode
2
+
3
+ def initialize( _uriPattern , _verb , _controller , _action, _nodeUri )
4
+ @uriPattern = _uriPattern
5
+ _verb ? @verbs = [_verb] : @verbs = []
6
+ @controller = _controller
7
+ _action ? @actions = [_action] : @actions = []
8
+ @countChildrenNodes = 0
9
+ @childrenNodes = []
10
+ @nodeUri = _nodeUri
11
+ @id = self.object_id
12
+ end
13
+ def updateParams( _verb , _controller , _action )
14
+ @verbs << _verb unless _verb.nil?
15
+ @controller = _controller
16
+ @actions << _action unless _action.nil?
17
+ end
18
+ def verbs
19
+ @verbs
20
+ end
21
+ def verb
22
+ @verbs.first
23
+ end
24
+ def controller
25
+ @controller
26
+ end
27
+ def actions
28
+ @actions
29
+ end
30
+ def action
31
+ @actions.first
32
+ end
33
+ def uriPattern
34
+ @uriPattern
35
+ end
36
+ def getChildren
37
+ @childrenNodes
38
+ end
39
+ def getCountChildrenNodes
40
+ @countChildrenNodes
41
+ end
42
+
43
+ def hasRoute( uri_mapping )
44
+ @childrenNodes.each do |childNode|
45
+ if childNode.uriPattern == uri_mapping
46
+ return childNode
47
+ end
48
+ end
49
+ return false
50
+ end
51
+
52
+ def insertChild( childNode )
53
+ @childrenNodes << childNode
54
+ end
55
+
56
+ def countChildrenNodes( count )
57
+ @childrenNodes.each do |child|
58
+ count = count + child.countChildrenNodes(0)
59
+ end
60
+ @countChildrenNodes = count
61
+ count += 1
62
+ return count
63
+ end
64
+
65
+ def printNode
66
+ puts "====================NODE===================="
67
+ puts @uriPattern
68
+ puts @verbs
69
+ puts @controller
70
+ puts @actions
71
+ puts "============================================"
72
+ @childrenNodes.each do |child|
73
+ child.printNode
74
+ end
75
+ end
76
+ end
77
+
78
+
79
+
80
+ class RouteTree
81
+ def initialize( _headNode )
82
+ @headNode = _headNode
83
+ end
84
+
85
+ def insertNode( _node )
86
+ #Get route mapping and iteratively either walk the tree or insert a node at the correct position
87
+ uri_mapping = ""
88
+ thisNode = @headNode #always insert from the headNode
89
+
90
+ _node.uriPattern.split("/").each do |uri|
91
+ uri_mapping += uri + "/"
92
+ if uri_mapping[0...-1] == _node.uriPattern || uri_mapping == _node.uriPattern # We are at node to insert or update
93
+ if thisNode.uriPattern == uri_mapping # Update the parameters to include details
94
+ thisNode.updateParams( _node.verb , _node.controller , _node.action )
95
+ elsif thisNode.hasRoute( uri_mapping )
96
+ thisNode.hasRoute( uri_mapping ).updateParams( _node.verb , _node.controller , _node.action )
97
+ else
98
+ child = RouteNode.new( uri_mapping , _node.verb , _node.controller , _node.action, uri )
99
+ thisNode.insertChild( child )
100
+ end
101
+ elsif thisNode.hasRoute( uri_mapping ) # node exists so walk the tree
102
+ thisNode = thisNode.hasRoute( uri_mapping )
103
+ elsif thisNode.uriPattern == uri_mapping # already there so do nothing
104
+ #thisNode.updateParams( _node.verb , _node.controller , _node.action )
105
+ else
106
+ child = RouteNode.new( uri_mapping , nil, nil , nil, uri )
107
+ thisNode.insertChild( child )
108
+ thisNode = child
109
+ end
110
+ end
111
+ end
112
+ def removeNode( _node )
113
+ puts "removing node"
114
+ end
115
+ def getRoot
116
+ @headNode
117
+ end
118
+ def printTree
119
+ @headNode.printNode
120
+ end
121
+ end