rails_mini_profiler 0.4.0 → 0.5.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 (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