rails_mini_profiler 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/rails_mini_profiler/profiled_requests_controller.rb +13 -10
  3. data/app/javascript/images/check.svg +3 -0
  4. data/app/javascript/images/chevron.svg +3 -0
  5. data/app/javascript/images/filter.svg +1 -0
  6. data/app/javascript/images/logo_variant.svg +2 -2
  7. data/app/javascript/images/search.svg +4 -5
  8. data/app/javascript/js/checklist_controller.js +48 -0
  9. data/app/javascript/js/enable_controller.js +24 -0
  10. data/app/javascript/js/filter_controller.js +44 -0
  11. data/app/javascript/js/search_controller.js +18 -0
  12. data/app/javascript/js/select_controller.js +47 -0
  13. data/app/javascript/packs/rails-mini-profiler.js +23 -15
  14. data/app/javascript/stylesheets/components/page_header/page_header.scss +3 -0
  15. data/app/javascript/stylesheets/components/pagination.scss +14 -13
  16. data/app/javascript/stylesheets/components/profiled_request_table/placeholder.scss +33 -0
  17. data/app/javascript/stylesheets/components/profiled_request_table/profiled_request_table.scss +179 -0
  18. data/app/javascript/stylesheets/flamegraph.scss +3 -2
  19. data/app/javascript/stylesheets/flashes.scss +3 -5
  20. data/app/javascript/stylesheets/navbar.scss +7 -13
  21. data/app/javascript/stylesheets/profiled_requests.scss +35 -120
  22. data/app/javascript/stylesheets/rails-mini-profiler.scss +90 -61
  23. data/app/javascript/stylesheets/traces.scss +17 -17
  24. data/app/presenters/rails_mini_profiler/profiled_request_presenter.rb +8 -15
  25. data/app/search/rails_mini_profiler/base_search.rb +67 -0
  26. data/app/search/rails_mini_profiler/profiled_request_search.rb +34 -0
  27. data/app/views/rails_mini_profiler/badge.html.erb +2 -2
  28. data/app/views/rails_mini_profiler/profiled_requests/index.html.erb +8 -58
  29. data/app/views/rails_mini_profiler/profiled_requests/shared/header/_header.erb +20 -0
  30. data/app/views/rails_mini_profiler/profiled_requests/shared/table/_placeholder.erb +12 -0
  31. data/app/views/rails_mini_profiler/profiled_requests/shared/table/_table.erb +14 -0
  32. data/app/views/rails_mini_profiler/profiled_requests/shared/table/_table_head.erb +125 -0
  33. data/app/views/rails_mini_profiler/profiled_requests/shared/table/_table_row.erb +21 -0
  34. data/app/views/rails_mini_profiler/profiled_requests/show.html.erb +1 -1
  35. data/lib/rails_mini_profiler/configuration.rb +3 -0
  36. data/lib/rails_mini_profiler/engine.rb +12 -8
  37. data/lib/rails_mini_profiler/middleware.rb +3 -3
  38. data/lib/rails_mini_profiler/tracing/controller_tracer.rb +15 -0
  39. data/lib/rails_mini_profiler/tracing/null_trace.rb +7 -0
  40. data/lib/rails_mini_profiler/tracing/sequel_tracer.rb +37 -0
  41. data/lib/rails_mini_profiler/tracing/sequel_tracker.rb +37 -0
  42. data/lib/rails_mini_profiler/tracing/subscriptions.rb +34 -0
  43. data/lib/rails_mini_profiler/{models → tracing}/trace.rb +10 -2
  44. data/lib/rails_mini_profiler/tracing/trace_factory.rb +37 -0
  45. data/lib/rails_mini_profiler/tracing/tracer.rb +31 -0
  46. data/lib/rails_mini_profiler/tracing/view_tracer.rb +12 -0
  47. data/lib/rails_mini_profiler/tracing.rb +11 -0
  48. data/lib/rails_mini_profiler/version.rb +1 -1
  49. data/lib/rails_mini_profiler.rb +4 -8
  50. data/vendor/assets/images/check.svg +3 -0
  51. data/vendor/assets/images/chevron.svg +3 -0
  52. data/vendor/assets/images/filter.svg +1 -0
  53. data/vendor/assets/images/logo_variant.svg +2 -2
  54. data/vendor/assets/images/search.svg +4 -5
  55. data/vendor/assets/javascripts/rails-mini-profiler.css +1 -1
  56. data/vendor/assets/javascripts/rails-mini-profiler.js +1 -1
  57. metadata +33 -4
  58. data/lib/rails_mini_profiler/tracers.rb +0 -85
@@ -25,13 +25,13 @@
25
25
  transition: opacity 0.3s;
26
26
  }
27
27
 
28
- #rails-mini-profiler-badge-icon {
28
+ #logo-variant {
29
29
  height: 18px;
30
30
  width: 18px;
31
31
  padding-bottom: 2px;
32
32
  }
33
33
  </style>
34
34
  <a id="rails-mini-profiler-badge" href="<%= profiled_request_path(@profiled_request.id) %>" data-turbolinks="false" title="View performance data">
35
- <%= inline_svg('logo_variant.svg', id: "rails-mini-profiler-badge-icon") %>
35
+ <%= inline_svg('logo_variant.svg') %>
36
36
  <%= (@profiled_request.duration.to_f / 100).round %>ms
37
37
  </a>
@@ -1,59 +1,9 @@
1
- <% if @profiled_requests.empty? %>
2
- <section class="placeholder">
3
- <%= inline_svg('logo_variant.svg', class: 'placeholder-image') %>
4
- <div class="placeholder-text">
5
- <h2> There is nothing here yet! </h2>
6
- <p>Send some requests to your app for them to show up here</p>
7
- </div>
8
- </section>
9
- <% else %>
10
- <h1>Profiled Requests</h1>
11
- <div class="profiled-requests-actions">
12
- <%= form_with id: 'profiled-request-search-form', url: profiled_requests_url, method: :get do |form| %>
13
- <%= form.search_field :path, id: 'profiled-request-search', placeholder: 'Search Path...', class: 'search-field' %>
14
- <% end %>
15
- <%= link_to(destroy_all_profiled_requests_url,
16
- method: :delete,
17
- class: 'clear-action',
18
- data: { confirm: "This will delete all requests. Are you sure?" }) do %>
19
- <button>Clear Requests</button>
20
- <% end %>
21
- </div>
22
- <table id="profiled-requests-table" class="table">
23
- <thead>
24
- <tr>
25
- <th class="text-left">Path</th>
26
- <th class="text-left">Method</th>
27
- <th class="text-left">Status</th>
28
- <th class="text-left">Media Type</th>
29
- <th class="text-right">Duration</th>
30
- <th class="text-left"></th>
31
- <th class="text-left"></th>
32
- </tr>
33
- </thead>
34
1
 
35
- <tbody>
36
- <% @profiled_requests.each do |profiled_request| %>
37
- <tr data-link="<%= profiled_request_path(profiled_request.id)%>">
38
- <td class="request-path"><%= profiled_request.request_name %></td>
39
- <td> <%= profiled_request.request_method %> </td>
40
- <td> <%= profiled_request.response_status %> </td>
41
- <td> <%= profiled_request.response_media_type %> </td>
42
- <td class="text-right"><%= profiled_request.duration %> ms</td>
43
- <td class=""><%= profiled_request.created_at %></td>
44
- <td class="request-buttons">
45
- <%= link_to(profiled_request_path(profiled_request.id), title: 'Show Details') do %>
46
- <%= inline_svg('show.svg', options = {}) %>
47
- <% end %>
48
- <%= profiled_request.flamegraph_icon %>
49
- <%= link_to(profiled_request_path(profiled_request.id), title: 'Delete Request', method: :delete) do %>
50
- <%= inline_svg('delete.svg') %>
51
- <% end %>
52
- </td>
53
- </tr>
54
- <% end %>
55
- </tbody>
56
- </table>
57
- <%== pagy_nav(@pagy) %>
58
- <br>
59
- <% end %>
2
+ <div
3
+ data-controller="filters selectable"
4
+ data-action="search-controller:submit@window->filters#apply">
5
+ <%= render "rails_mini_profiler/profiled_requests/shared/header/header" %>
6
+ <%= render "rails_mini_profiler/profiled_requests/shared/table/table" %>
7
+ </div>
8
+ <%== pagy_nav(@pagy) if @pagy.pages > 1 %>
9
+ <br>
@@ -0,0 +1,20 @@
1
+ <div class="page-header profiled-requests-header">
2
+ <div class="">
3
+ <h1>Profiled Requests</h1>
4
+ </div>
5
+ <div>
6
+ <%= button_tag('Delete Selected',
7
+ disabled: params[:id].blank?,
8
+ data: {
9
+ controller: 'enable',
10
+ 'enable-target': 'enable',
11
+ action: 'rmp:select:change@window->enable#change click->filters#post'
12
+ }) -%>
13
+ <%= link_to(destroy_all_profiled_requests_url,
14
+ method: :delete,
15
+ class: 'clear-action',
16
+ data: { confirm: "This will delete all requests. Are you sure?" }) do %>
17
+ <%= button_tag('Delete All') %>
18
+ <% end %>
19
+ </div>
20
+ </div>
@@ -0,0 +1,12 @@
1
+ <tr class="no-row">
2
+ <td colspan="100%">
3
+ <div class="placeholder">
4
+ <%= inline_svg('logo_variant.svg', class: 'placeholder-image') %>
5
+ <div class="placeholder-text">
6
+ <h2>No Requests found!</h2>
7
+ <p>Send some requests to your app or modify your filters</p>
8
+ </div>
9
+ <%= link_to('Clear Filters', profiled_requests_path, class: 'placeholder-link') %>
10
+ </div>
11
+ </td>
12
+ </tr>
@@ -0,0 +1,14 @@
1
+ <table
2
+ id="profiled-requests-table"
3
+ class="table">
4
+ <%= render "rails_mini_profiler/profiled_requests/shared/table/table_head" %>
5
+ <tbody>
6
+ <% if @profiled_requests.empty? %>
7
+ <%= render "rails_mini_profiler/profiled_requests/shared/table/placeholder" %>
8
+ <% else %>
9
+ <% @profiled_requests.each do |profiled_request| %>
10
+ <%= render "rails_mini_profiler/profiled_requests/shared/table/table_row", profiled_request: profiled_request %>
11
+ <% end %>
12
+ <% end %>
13
+ </tbody>
14
+ </table>
@@ -0,0 +1,125 @@
1
+ <thead>
2
+ <tr>
3
+ <th style="width: 20px;" class="text-left">
4
+ <%= label_tag nil do %>
5
+ <%= check_box_tag 'selected[]', nil,
6
+ params.fetch(:selected, []).size == @pagy.items,
7
+ class: "request-checkbox",
8
+ data: {
9
+ action: 'click->selectable#selectAll',
10
+ 'selectable-target': 'all'
11
+ }
12
+ %>
13
+ <% end %>
14
+ </th>
15
+ <th style="width: 25%;" class="text-left" data-controller="dropdown">
16
+ <button data-action="click->dropdown#toggle click@window->dropdown#hide" data-dropdown-target="button" class="dropdown-toggle none">
17
+ Path <%= inline_svg('search.svg', class: 'table-filter-icon') %> </button>
18
+ <div data-controller="search" data-dropdown-target="menu" class="dropdown-container hidden">
19
+ <div class="dropdown-body">
20
+ <%= form_with url: profiled_requests_url, method: :get do |form| %>
21
+ <%= form.search_field :path,
22
+ value: params[:path],
23
+ placeholder: 'Search Path',
24
+ class: 'dropdown-search-field',
25
+ data: { 'filters-target': 'filter' } %>
26
+ <%= form.submit 'Search', class: 'dropdown-search-button', data: { action: 'click->search#submit' } -%>
27
+ <% end %>
28
+ </div>
29
+ </div>
30
+ </th>
31
+ <th style="width: 10%;" class="" data-controller="dropdown">
32
+ <button data-action="click->dropdown#toggle click@window->dropdown#hide" data-dropdown-target="button" class="dropdown-toggle none">
33
+ Method <%= inline_svg('filter.svg', class: 'table-filter-icon') %> </button>
34
+ <div data-controller="checklist" data-dropdown-target="menu" class="dropdown-container hidden">
35
+ <div class="dropdown-header">
36
+ Select Method...
37
+ <button class="clear-filters" data-action="checklist#checkNone">
38
+ Clear filter
39
+ </button>
40
+ </div>
41
+ <% %w[get put post delete].map(&:upcase).each do |method| %>
42
+ <%= label_tag nil, class: "dropdown-entry" do %>
43
+ <%= check_box_tag 'method[]', method, params.fetch(:method, []).include?(method),
44
+ class: "",
45
+ data: { 'filters-target': "filter" } %>
46
+ <%= method %>
47
+ <% end %>
48
+ <% end %>
49
+ <button class="dropdown-footer" data-action="filters#apply">
50
+ Apply
51
+ </button>
52
+ </div>
53
+ </th>
54
+ <th style="width: 10%;" class="" data-controller="dropdown">
55
+ <button data-action="click->dropdown#toggle click@window->dropdown#hide" data-dropdown-target="button" class="dropdown-toggle none">
56
+ Status <%= inline_svg('filter.svg', class: 'table-filter-icon') %> </button>
57
+ <div data-controller="checklist" data-dropdown-target="menu" class="dropdown-container hidden">
58
+ <div class="dropdown-header">
59
+ Select Status...
60
+ <button class="clear-filters" data-action="checklist#checkNone filters#apply">
61
+ Clear filter
62
+ </button>
63
+ </div>
64
+ <% %w[200 300 400 500].each do |status| %>
65
+ <%= label_tag nil, class: "dropdown-entry" do %>
66
+ <%= check_box_tag 'status[]', status, params.fetch(:status, []).include?(status),
67
+ class: "",
68
+ data: { 'filters-target': "filter" } %>
69
+ <%= status %>
70
+ <% end %>
71
+ <% end %>
72
+ <button class="dropdown-footer" data-action="filters#apply">
73
+ Apply
74
+ </button>
75
+ </div>
76
+ </th>
77
+ <th style="width: 15%" class="" data-controller="dropdown">
78
+ <button data-action="click->dropdown#toggle click@window->dropdown#hide" data-dropdown-target="button" class="dropdown-toggle none">
79
+ Media Type <%= inline_svg('filter.svg', class: 'table-filter-icon') %> </button>
80
+ <div data-controller="checklist" data-dropdown-target="menu" class="dropdown-container hidden">
81
+ <div class="dropdown-header">
82
+ Select Media Type...
83
+ <button class="clear-filters" data-action="checklist#checkNone filters#apply">
84
+ Clear filter
85
+ </button>
86
+ </div>
87
+ <% %w[text/html application/json application/xml].each do |media_type| %>
88
+ <%= label_tag nil, class: "dropdown-entry" do %>
89
+ <%= check_box_tag 'media_type[]', media_type, params.fetch(:media_type, []).include?(media_type),
90
+ class: "",
91
+ data: { target: "filters.filter" } %>
92
+ <%= media_type %>
93
+ <% end %>
94
+ <% end %>
95
+ <button class="dropdown-footer" data-action="filters#apply">
96
+ Apply
97
+ </button>
98
+ </div>
99
+ </th>
100
+ <th style="width: 10%" class="text-left" data-controller="dropdown">
101
+ <button data-action="click->dropdown#toggle click@window->dropdown#hide" data-dropdown-target="button" class="dropdown-toggle none">
102
+ Duration <%= inline_svg('filter.svg', class: 'table-filter-icon') %> </button>
103
+ <div data-controller="checklist" data-dropdown-target="menu" class="dropdown-container hidden">
104
+ <div class="dropdown-header">
105
+ Select Duration...
106
+ <button class="clear-filters" data-action="checklist#checkNone filters#apply">
107
+ Clear filter
108
+ </button>
109
+ </div>
110
+ <% %w[>100ms >250ms].each do |duration| %>
111
+ <%= label_tag nil, class: "dropdown-entry" do %>
112
+ <%= radio_button_tag 'duration', duration, params.fetch(:duration, []).include?(duration),
113
+ class: "",
114
+ data: { target: "filters.filter" } %>
115
+ <%= duration %>
116
+ <% end %>
117
+ <% end %>
118
+ <button class="dropdown-footer" data-action="filters#apply">
119
+ Apply
120
+ </button>
121
+ </div>
122
+ </th>
123
+ <th style="width: 15%" class="text-left">Date</th>
124
+ </tr>
125
+ </thead>
@@ -0,0 +1,21 @@
1
+ <tr data-link="<%= profiled_request_path(profiled_request.id) %>">
2
+ <td class="">
3
+ <%= label_tag nil do %>
4
+ <%= check_box_tag 'id[]', profiled_request.id,
5
+ params.fetch(:selected, []).include?(profiled_request.id),
6
+ class: "request-checkbox",
7
+ data: {
8
+ 'selectable-target': 'selectable',
9
+ 'filters-target': 'filter',
10
+ action: 'click->selectable#onSelected'
11
+ },
12
+ onclick: "event.stopPropagation();" %>
13
+ <% end %>
14
+ </td>
15
+ <td class="request-path"><%= profiled_request.request_name %></td>
16
+ <td class="text-left"> <%= profiled_request.request_method %> </td>
17
+ <td class="text-left"> <%= profiled_request.response_status %> </td>
18
+ <td class="text-left"> <%= profiled_request.response_media_type %> </td>
19
+ <td class="text-left"><%= profiled_request.duration %> ms</td>
20
+ <td class="text-left"><%= profiled_request.created_at %></td>
21
+ </tr>
@@ -1,4 +1,4 @@
1
- <section class="request-details">
1
+ <section class="page-header">
2
2
  <h1> <%= @profiled_request.request_path %> </h1>
3
3
 
4
4
  <ul class="request-details-data">
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'rails_mini_profiler/configuration/storage'
4
+ require 'rails_mini_profiler/configuration/user_interface'
5
+
3
6
  module RailsMiniProfiler
4
7
  # The main Rails Mini Profiler configuration object
5
8
  #
@@ -11,14 +11,6 @@ module RailsMiniProfiler
11
11
  app.middleware.use(RailsMiniProfiler::Middleware)
12
12
  end
13
13
 
14
- initializer 'rails_mini_profiler.assets.precompile', group: :all do |app|
15
- app.config.assets.precompile += %w[
16
- rails_mini_profiler.js
17
- rails_mini_profiler/application.css
18
- vendor/assets/images
19
- ]
20
- end
21
-
22
14
  config.generators do |g|
23
15
  g.test_framework :rspec
24
16
  end
@@ -26,5 +18,17 @@ module RailsMiniProfiler
26
18
  initializer 'rails_mini_profiler_add_static assets' do |app|
27
19
  app.middleware.insert_before(ActionDispatch::Static, ActionDispatch::Static, "#{root}/public")
28
20
  end
21
+
22
+ # If sprockets is not being used then there is no need to hook into asset compilation. Calling config.assets
23
+ # without Sprockets installed breaks compilation.
24
+ if defined?(Sprockets::Rails)
25
+ initializer 'rails_mini_profiler.assets.precompile', group: :all do |app|
26
+ app.config.assets.precompile += %w[
27
+ rails_mini_profiler.js
28
+ rails_mini_profiler/application.css
29
+ vendor/assets/images
30
+ ]
31
+ end
32
+ end
29
33
  end
30
34
  end
@@ -5,7 +5,7 @@ module RailsMiniProfiler
5
5
  def initialize(app)
6
6
  @app = app
7
7
  @config = RailsMiniProfiler.configuration
8
- Tracers.setup! { |trace| track_trace(trace) }
8
+ Tracing::Subscriptions.setup! { |trace| track_trace(trace) }
9
9
  end
10
10
 
11
11
  def call(env)
@@ -33,8 +33,8 @@ module RailsMiniProfiler
33
33
  def track_trace(event)
34
34
  return if traces.nil?
35
35
 
36
- trace = Tracers.build_trace(event)
37
- traces.append(trace)
36
+ trace = RailsMiniProfiler::Tracing::TraceFactory.create(event)
37
+ traces.append(trace) unless trace.is_a?(RailsMiniProfiler::Tracing::NullTrace)
38
38
  end
39
39
 
40
40
  private
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ module Tracing
5
+ class ControllerTracer < Tracer
6
+ def trace
7
+ @event[:payload] = @event[:payload]
8
+ .slice(:view_runtime, :db_runtime)
9
+ .reject { |_k, v| v.blank? }
10
+ .transform_values { |value| value&.round(2) }
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ module Tracing
5
+ class NullTrace < Trace; end
6
+ end
7
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ module Tracing
5
+ class SequelTracer < Tracer
6
+ def trace
7
+ return NullTrace.new if ignore?
8
+
9
+ payload = @event[:payload].slice(:name, :sql, :binds, :type_casted_binds)
10
+ typecasted_binds = payload[:type_casted_binds]
11
+ # Sometimes, typecasted binds are a proc. Not sure why. In those instances, we extract the typecasted
12
+ # values from the proc by executing call.
13
+ typecasted_binds = typecasted_binds.call if typecasted_binds.respond_to?(:call)
14
+ payload[:binds] = transform_binds(payload[:binds], typecasted_binds)
15
+ payload.delete(:type_casted_binds)
16
+ payload.reject { |_k, v| v.blank? }
17
+ @event[:payload] = payload
18
+ super
19
+ end
20
+
21
+ private
22
+
23
+ def transform_binds(binds, type_casted_binds)
24
+ binds.each_with_object([]).with_index do |(binding, object), i|
25
+ name = binding.name
26
+ value = type_casted_binds[i]
27
+ object << { name: name, value: value }
28
+ end
29
+ end
30
+
31
+ def ignore?
32
+ payload = @event[:payload]
33
+ !SqlTracker.new(name: payload[:name], query: payload[:sql]).track?
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ module Tracing
5
+ class SqlTracker
6
+ TRACKED_SQL_COMMANDS = %w[SELECT INSERT UPDATE DELETE].freeze
7
+ UNTRACKED_NAMES = %w[SCHEMA].freeze
8
+ UNTRACKED_TABLES = %w[
9
+ SCHEMA_MIGRATIONS
10
+ SQLITE_MASTER
11
+ ACTIVE_MONITORING_METRICS
12
+ SQLITE_TEMP_MASTER
13
+ SQLITE_VERSION
14
+ AR_INTERNAL_METADATA
15
+ ].freeze
16
+
17
+ def initialize(query:, name:)
18
+ @query = query.to_s.upcase
19
+ @name = name.to_s.upcase
20
+ end
21
+
22
+ def track?
23
+ query.start_with?(*TRACKED_SQL_COMMANDS) &&
24
+ !name.start_with?(*UNTRACKED_NAMES) &&
25
+ !untracked_tables?
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :query, :name
31
+
32
+ def untracked_tables?
33
+ UNTRACKED_TABLES.any? { |table| query.include?(table) }
34
+ end
35
+ end
36
+ end
37
+ end