rails-buddy 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +45 -0
  4. data/Rakefile +10 -0
  5. data/app/assets/builds/rails/buddy/application.css +7 -0
  6. data/app/assets/builds/rails/buddy/application.js +28660 -0
  7. data/app/assets/config/rails_buddy_manifest.js +1 -0
  8. data/app/controllers/rails/buddy/application_controller.rb +8 -0
  9. data/app/controllers/rails/buddy/requests_controller.rb +23 -0
  10. data/app/helpers/rails/buddy/application_helper.rb +8 -0
  11. data/app/jobs/rails/buddy/application_job.rb +8 -0
  12. data/app/mailers/rails/buddy/application_mailer.rb +10 -0
  13. data/app/models/rails/buddy/application_record.rb +9 -0
  14. data/app/views/layouts/rails/buddy/application.html.erb +14 -0
  15. data/app/views/rails/buddy/icons/_expand.html.erb +50 -0
  16. data/app/views/rails/buddy/requests/_request.html.erb +7 -0
  17. data/app/views/rails/buddy/requests/close.html.erb +2 -0
  18. data/app/views/rails/buddy/requests/deleted.html.erb +14 -0
  19. data/app/views/rails/buddy/requests/index.html.erb +32 -0
  20. data/app/views/rails/buddy/requests/show/_controller.html.erb +60 -0
  21. data/app/views/rails/buddy/requests/show/_models.html.erb +10 -0
  22. data/app/views/rails/buddy/requests/show/_queries.html.erb +29 -0
  23. data/app/views/rails/buddy/requests/show.html.erb +37 -0
  24. data/config/routes.rb +10 -0
  25. data/lib/generators/rails/buddy/config_generator.rb +22 -0
  26. data/lib/generators/rails/buddy/templates/initializer.rb +6 -0
  27. data/lib/rails/buddy/config.rb +33 -0
  28. data/lib/rails/buddy/current.rb +20 -0
  29. data/lib/rails/buddy/engine.rb +39 -0
  30. data/lib/rails/buddy/middlewares/track_current_request.rb +37 -0
  31. data/lib/rails/buddy/request.rb +66 -0
  32. data/lib/rails/buddy/requests_buffer.rb +31 -0
  33. data/lib/rails/buddy/subscribers/action_controller.rb +40 -0
  34. data/lib/rails/buddy/subscribers/active_record.rb +61 -0
  35. data/lib/rails/buddy/subscribers/base.rb +19 -0
  36. data/lib/rails/buddy/tracker.rb +25 -0
  37. data/lib/rails/buddy/version.rb +7 -0
  38. data/lib/rails/buddy.rb +26 -0
  39. data/lib/tasks/rails/buddy_tasks.rake +6 -0
  40. metadata +99 -0
@@ -0,0 +1 @@
1
+ //= link_directory ../builds/rails/buddy
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module Buddy
5
+ class ApplicationController < ActionController::Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module Buddy
5
+ class RequestsController < ApplicationController
6
+ def index
7
+ @requests = RequestsBuffer.all
8
+ end
9
+
10
+ def show
11
+ @request = RequestsBuffer.find(params[:id])
12
+ render :deleted if @request.nil?
13
+ end
14
+
15
+ def clear
16
+ RequestsBuffer.clear!
17
+ redirect_to root_path
18
+ end
19
+
20
+ def close; end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module Buddy
5
+ module ApplicationHelper
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module Buddy
5
+ class ApplicationJob < ActiveJob::Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module Buddy
5
+ class ApplicationMailer < ActionMailer::Base
6
+ default from: 'from@example.com'
7
+ layout 'mailer'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module Buddy
5
+ class ApplicationRecord < ActiveRecord::Base
6
+ self.abstract_class = true
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Rails Buddy</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "rails/buddy/application", media: "all" %>
9
+ <%= javascript_include_tag "rails/buddy/application", media: "all" %>
10
+ </head>
11
+ <body data-controller="home" data-action="keydown.esc->home#closeRequest">
12
+ <%= yield %>
13
+ </body>
14
+ </html>
@@ -0,0 +1,50 @@
1
+ <svg id="svg_expand" class="h-4" viewBox="0 0 30 21" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <style>
3
+ #svg_expand {
4
+ line {
5
+ stroke: var(--color1);
6
+ transform-box: fill-box;
7
+ transform-origin: center;
8
+ transition: all 0.3s ease-in-out;
9
+ }
10
+
11
+ path {
12
+ fill: var(--color2);
13
+ transform-box: fill-box;
14
+ transform-origin: center;
15
+ transition: all 0.3s ease-in-out;
16
+ transform: scaleY(-1);
17
+ }
18
+
19
+ line {
20
+ transform: translateY(var(--translate));
21
+ }
22
+
23
+ #linetop {
24
+ --translate: 10px;
25
+ opacity: 0;
26
+ }
27
+
28
+ #linebottom {
29
+ --translate: -9px;
30
+ opacity: 0;
31
+ }
32
+ }
33
+
34
+ .<%= css_class_expanded %> #svg_expand {
35
+ line {
36
+ transform: translateY(0px);
37
+ opacity: 1 !important;
38
+ }
39
+
40
+ path {
41
+ transform: scaleY(1);
42
+ }
43
+ }
44
+ </style>
45
+ <line id="linetop" x1="10" y1="1" x2="30" y2="1" stroke-width="2"/>
46
+ <line id="linemiddle" x1="10" y1="11" x2="30" y2="11" stroke-width="2"/>
47
+ <line id="linebottom" x1="10" y1="20" x2="30" y2="20" stroke-width="2"/>
48
+ <path id="arrowtop" d="M4.35355 0.646447C4.15829 0.451184 3.84171 0.451184 3.64645 0.646447L0.464466 3.82843C0.269204 4.02369 0.269204 4.34027 0.464466 4.53553C0.659728 4.7308 0.976311 4.7308 1.17157 4.53553L4 1.70711L6.82843 4.53553C7.02369 4.7308 7.34027 4.7308 7.53553 4.53553C7.7308 4.34027 7.7308 4.02369 7.53553 3.82843L4.35355 0.646447ZM4.5 9L4.5 1H3.5L3.5 9H4.5Z"/>
49
+ <path id="arrowbottom" d="M3.64645 20.3536C3.84171 20.5488 4.15829 20.5488 4.35355 20.3536L7.53553 17.1716C7.7308 16.9763 7.7308 16.6597 7.53553 16.4645C7.34027 16.2692 7.02369 16.2692 6.82843 16.4645L4 19.2929L1.17157 16.4645C0.97631 16.2692 0.659728 16.2692 0.464466 16.4645C0.269204 16.6597 0.269204 16.9763 0.464466 17.1716L3.64645 20.3536ZM3.5 12L3.5 20H4.5L4.5 12H3.5Z"/>
50
+ </svg>
@@ -0,0 +1,7 @@
1
+ <%= link_to "#{Rails::Buddy.config.prefix}/requests/#{request.id}", class: 'request-line', data: {turbo_frame: :request} do %>
2
+ <span class="method method--<%= request.method %>"><%= request.method %></span>
3
+ <p class="px-4 truncate"><%= request.path %></p>
4
+ <span class="status status--<%= request.familly_status.dasherize %>"><%= request.status %></span>
5
+ <span class="truncate"><%= request.meta&.dig(:controller) %></span>
6
+ <span class="truncate"><%= request.meta&.dig(:action) %></span>
7
+ <% end %>
@@ -0,0 +1,2 @@
1
+ <%= turbo_frame_tag :request do %>
2
+ <% end %>
@@ -0,0 +1,14 @@
1
+ <%= turbo_frame_tag :request do %>
2
+ <div class="p-8">
3
+ <p class="font-bold">This request has been <span class="text-red-500">deleted</span>.</p>
4
+
5
+ <p class="mb-4">You exceeded the maximum number of requests that can be stored in buffer. You can increase the limit by changing the value of buffer size in the configuration file.</p>
6
+
7
+ <pre class="border border-gray-300 rounded bg-gray-50">
8
+ <code>
9
+ Rails::Buddy.configure do |config|
10
+ config.buffer_size = <%= Rails::Buddy::Config::DEFAULT_BUGGER_SIZE * 2 %> # default is <%= Rails::Buddy::Config::DEFAULT_BUGGER_SIZE %>
11
+ end</code>
12
+ </pre>
13
+ </div>
14
+ <% end %>
@@ -0,0 +1,32 @@
1
+ <%= turbo_stream_from :requests %>
2
+
3
+
4
+ <div class="flex items-center justify-between px-4 bg-indigo-800 h-navbar">
5
+ <h1 class="text-2xl font-bold text-white">Buddy <span class="text-indigo-500">Monitoring</<span></h1>
6
+ <%= button_to clear_requests_path, class: 'transition-all flex gap-2 items-center px-4 text-sm py-2 font-semibold rounded-full border border-indigo-500 text-indigo-500 hover:text-indigo-300 hover:border-indigo-300' do %>
7
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-4">
8
+ <path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
9
+ </svg>
10
+ <span>
11
+ Clean up
12
+ </span>
13
+ <% end %>
14
+ </div>
15
+ <div class="flex flex-col h-full-nonav">
16
+ <div class="requests-grid requests-header">
17
+ <span>Method</span>
18
+ <span class="px-4">Path</span>
19
+ <span>Status</span>
20
+ <span>Controller</span>
21
+ <span>Action</span>
22
+ </div>
23
+ <div class="flex-grow overflow-y-auto">
24
+ <%= content_tag :div, id: :requests, class: 'flex flex-col-reverse' do %>
25
+ <%= render partial: 'request', collection: @requests %>
26
+ <% end %>
27
+ </div>
28
+ </div>
29
+
30
+ <div class="fixed bottom-0 right-0 w-3/4 overflow-y-auto transition-all translate-x-full bg-white border shadow-lg h-full-nonav request-opened:translate-x-0 rounded-l-md">
31
+ <%= turbo_frame_tag :request, data: {action: 'turbo:frame-load->home#openRequest'} %>
32
+ </div>
@@ -0,0 +1,60 @@
1
+ <h3 class="flex items-center gap-4 p-2 mb-8 border border-gray-300 rounded bg-gray-50">
2
+ <span class="font-semibold text-indigo-500 uppercase"><%= @request.method %></span>
3
+ <span class="flex-grow truncate"><%= @request.path %></span>
4
+ <% if @request.meta %>
5
+ <span class="font-semibold text-gray-700 uppercase"><%= @request.meta[:format] %></span>
6
+ <% end %>
7
+ </h3>
8
+
9
+
10
+ <% if @request.meta %>
11
+ <div class="p-2 mb-8 border border-gray-300 rounded bg-gray-50">
12
+ <%= @request.meta[:params] %>
13
+ </div>
14
+
15
+ <div class="flex items-center gap-4 mb-8">
16
+ <p class="font-semibold"><%= @request.meta[:controller] %><span class="text-indigo-500">#</span><%= @request.meta[:action] %></p>
17
+ <%= link_to "Open in Vscode", "vscode://file#{@request.action_definition}", class: 'text-xs' %>
18
+ </div>
19
+
20
+ <table class="table-auto">
21
+ <tbody class="divide-y">
22
+ <% if @request.meta[:view_runtime].presence %>
23
+ <tr>
24
+ <td class="px-2 py-1 font-semibold">view_runtime</td>
25
+ <td><span class="badge"><%= @request.meta[:view_runtime].round(2) %> ms</span></td>
26
+ </tr>
27
+ <% end %>
28
+ <% if @request.meta[:db_runtime].presence %>
29
+ <tr>
30
+ <td class="px-2 py-1 font-semibold">db_runtime</td>
31
+ <td><span class="badge"><%= @request.meta[:db_runtime].round(2) %> ms</span></td>
32
+ </tr>
33
+ <% end %>
34
+ <% if @request.meta[:duration].presence %>
35
+ <tr>
36
+ <td class="px-2 py-1 font-semibold">duration</td>
37
+ <td><span class="badge"><%= @request.meta[:duration].round(2) %> ms</span></td>
38
+ </tr>
39
+ <% end %>
40
+ <% if @request.meta[:cpu_time].presence %>
41
+ <tr>
42
+ <td class="px-2 py-1 font-semibold">cpu_time</td>
43
+ <td><span class="badge"><%= @request.meta[:cpu_time].round(2) %> ms</span></td>
44
+ </tr>
45
+ <% end %>
46
+ <% if @request.meta[:idle_time].presence %>
47
+ <tr>
48
+ <td class="px-2 py-1 font-semibold">idle_time</td>
49
+ <td><span class="badge"><%= @request.meta[:idle_time].round(2) %> ms</span></td>
50
+ </tr>
51
+ <% end %>
52
+ <% if @request.meta[:allocations].presence %>
53
+ <tr>
54
+ <td class="px-2 py-1 font-semibold">allocations</td>
55
+ <td><span class="badge"><%= @request.meta[:allocations] %></span></td>
56
+ </tr>
57
+ <% end %>
58
+ </tbody>
59
+ </table>
60
+ <% end %>
@@ -0,0 +1,10 @@
1
+ <table class="table-auto">
2
+ <tbody class="divide-y">
3
+ <% models.sort_by {|_k, v| -v }.to_h.each do |model_name, count| %>
4
+ <tr class="">
5
+ <td class="px-2 py-1 font-semibold"><%= model_name %></td>
6
+ <td><span class="badge"><%= count %></span></td>
7
+ </tr>
8
+ <% end %>
9
+ </tbody>
10
+ </table>
@@ -0,0 +1,29 @@
1
+ <div class="divide-y">
2
+ <% queries.each do |query| %>
3
+ <div class="p-4 text-sm odd:bg-gray-50 group" data-controller="code-syntax" data-code-syntax-query-value="<%= {sql: query[:sql]}.to_json %>">
4
+ <div class="flex items-center h-6 mb-2">
5
+ <h3 class="mr-4 font-bold"><%= query[:title] %></h3>
6
+
7
+ <span class="[--color1:--color-indigo-500] [--color2:--color-gray-800] mr-auto cursor-pointer" data-action="click->code-syntax#toggleFormat">
8
+ <%= render 'rails/buddy/icons/expand', css_class_expanded: 'formatted' %>
9
+ </span>
10
+
11
+ <div class="items-center justify-center hidden gap-1 py-1 text-xs font-semibold text-gray-500 border rounded cursor-pointer group-hover:flex w-36 hover:text-indigo-500 transition-color hover:border-indigo-500" data-action="click->code-syntax#copyClipboard">
12
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-4 copied:hidden">
13
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184" />
14
+ </svg>
15
+ <span class="copied:hidden">
16
+ Copy to clipboard
17
+ </span>
18
+ <span class="hidden copied:block">
19
+ Copied!
20
+ </span>
21
+ </div>
22
+ </div>
23
+
24
+ <div class="w-full py-4 overflow-x-auto">
25
+ <pre data-code-syntax-target="container"></pre>
26
+ </div>
27
+ </div>
28
+ <% end %>
29
+ </div>
@@ -0,0 +1,37 @@
1
+ <%= turbo_frame_tag :request do %>
2
+ <div data-controller="tabs">
3
+ <div class="flex items-center justify-between border-b">
4
+ <ul class="flex">
5
+ <li data-action="click->tabs#nav" data-tabs-target="tab" target="controller" class="current">Controller</li>
6
+ <li data-action="click->tabs#nav" data-tabs-target="tab" target="models">
7
+ Models
8
+ <% if @request.models.present? %>
9
+ <span><%= @request.models.values.sum %></span>
10
+ <% end %>
11
+ </li>
12
+ <li data-action="click->tabs#nav" data-tabs-target="tab" target="queries">
13
+ Queries
14
+ <% if @request.queries.present? %>
15
+ <span><%= @request.queries.size %></span>
16
+ <% end %>
17
+ </li>
18
+ </ul>
19
+
20
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-5 mr-2 rotate-45 cursor-pointer hover:scale-110 hover:text-indigo-500" data-action="click->home#closeRequest">
21
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
22
+ </svg>
23
+ </div>
24
+
25
+ <div data-tabs-target="panel" id="controller">
26
+ <%= render 'rails/buddy/requests/show/controller' %>
27
+ </div>
28
+
29
+ <div data-tabs-target="panel" id="models" class="hidden">
30
+ <%= render 'rails/buddy/requests/show/models', models: @request.models %>
31
+ </div>
32
+
33
+ <div data-tabs-target="panel" id="queries" class="hidden">
34
+ <%= render 'rails/buddy/requests/show/queries', queries: @request.queries %>
35
+ </div>
36
+ </div>
37
+ <% end %>
data/config/routes.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails::Buddy::Engine.routes.draw do
4
+ resources :requests, only: [:show] do
5
+ post :clear, on: :collection
6
+ get :close, on: :collection
7
+ end
8
+
9
+ root to: '/rails/buddy/requests#index'
10
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module Rails
6
+ module Buddy
7
+ class ConfigGenerator < Rails::Generators::Base
8
+ source_root File.expand_path('templates', __dir__)
9
+
10
+ desc 'This generator creates an initializer file at config/initializers, ' \
11
+ 'with the default configuration options for Rails::Buddy.'
12
+ def add_initializer
13
+ path = File.expand_path('templates/initializer.rb', __dir__)
14
+ content = File.binread(path)
15
+ .gsub!('[PREFIX]', Config::DEFAULT_PATH_PREFIX)
16
+ .gsub!('[BUFFER_SIZE]', Config::DEFAULT_BUGGER_SIZE.to_s)
17
+ File.binwrite('config/initializers/buddy.rb', content)
18
+ say_status :create, relative_to_original_destination_root('config/initializers/buddy.rb')
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails::Buddy.configure do |config|
4
+ # config.prefix = '[PREFIX]'
5
+ # config.buffer_size = [BUFFER_SIZE]
6
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module Buddy
5
+ class Config
6
+ DEFAULT_BUGGER_SIZE = 200
7
+ DEFAULT_PATH_PREFIX = '/buddy'
8
+
9
+ attr_accessor :prefix, :buffer_size
10
+
11
+ def initialize(options = {})
12
+ opt = defaults.merge options
13
+
14
+ @prefix = opt[:prefix]
15
+ @buffer_size = opt[:buffer_size]
16
+ end
17
+
18
+ def ignore_request?(env)
19
+ [prefix, '/assets'].any? { |s| env['PATH_INFO'].start_with? s } || env['HTTP_UPGRADE'] == 'websocket'
20
+ end
21
+
22
+ private
23
+
24
+ def defaults
25
+ {
26
+ enabled: Rails.env.development?,
27
+ prefix: DEFAULT_PATH_PREFIX,
28
+ buffer_size: DEFAULT_BUGGER_SIZE
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module Buddy
5
+ class Current < ActiveSupport::CurrentAttributes
6
+ attribute :request
7
+ attribute :ignore
8
+
9
+ alias ignore? ignore
10
+
11
+ def new_request!(rack_env)
12
+ self.request = Request.from_rack_env(rack_env)
13
+ end
14
+
15
+ def pop_request!
16
+ request.tap { self.request = nil }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'middlewares/track_current_request'
4
+
5
+ module Rails
6
+ module Buddy
7
+ class Engine < ::Rails::Engine
8
+ isolate_namespace Rails::Buddy
9
+
10
+ initializer 'rails_buddy.init' do |_app|
11
+ RequestsBuffer.init
12
+ end
13
+
14
+ initializer 'rails_buddy.middlewares' do |app|
15
+ app.middleware.insert_after ActionDispatch::Executor, TrackCurrentRequest
16
+ end
17
+
18
+ initializer 'rails_buddy.routing' do |app|
19
+ app.routes.append do
20
+ mount Engine => Buddy.config.prefix
21
+ end
22
+ end
23
+
24
+ initializer 'rails_buddy.precompile' do |app|
25
+ app.config.assets.precompile += %w[
26
+ rails/buddy/application.css
27
+ rails/buddy/application.js
28
+ ]
29
+ end
30
+
31
+ initializer 'rails_buddy.subscribe' do |_app|
32
+ require_relative 'subscribers/action_controller'
33
+ Subscribers::ActionController.subscribe
34
+ require_relative 'subscribers/active_record'
35
+ Subscribers::ActiveRecord.subscribe
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module Buddy
5
+ class TrackCurrentRequest
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ Buddy::Current.ignore = Buddy.config.ignore_request?(env)
12
+ return @app.call(env) if Buddy::Current.ignore?
13
+
14
+ Buddy::Current.new_request!(env)
15
+
16
+ @app.call(env).tap do |response|
17
+ save_request(response)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def save_request(response)
24
+ request = Buddy::Current.pop_request!
25
+ return unless request
26
+
27
+ request.status = response[0]
28
+ Buddy::RequestsBuffer.push(request)
29
+
30
+ Turbo::StreamsChannel.broadcast_append_to :requests,
31
+ target: :requests,
32
+ partial: 'rails/buddy/requests/request',
33
+ locals: { request: }
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module Buddy
5
+ class Request
6
+ class << self
7
+ attr_reader :request
8
+
9
+ def from_rack_env(rack_env)
10
+ @request = ::Rack::Request.new(rack_env)
11
+ new(path:, method:)
12
+ end
13
+
14
+ def path = request.fullpath
15
+
16
+ def method
17
+ request_method = request.env['REQUEST_METHOD'].downcase.to_sym
18
+ if request_method == :post
19
+ request.params['_method'].presence&.to_sym || request_method
20
+ else
21
+ request_method
22
+ end
23
+ end
24
+ end
25
+
26
+ attr_reader :request_id, :path, :method, :time, :models, :queries
27
+ attr_accessor :request, :response, :meta, :status
28
+
29
+ alias id request_id
30
+
31
+ def initialize(path:, method:)
32
+ @request_id = SecureRandom.uuid
33
+ @time = Time.current
34
+ @path = path
35
+ @method = method
36
+ @models = Hash.new(0)
37
+ @queries = []
38
+ end
39
+
40
+ def familly_status
41
+ case status.to_s[0]
42
+ when '2' then 'success'
43
+ when '3' then 'redirect'
44
+ when '4' then 'error'
45
+ when '5' then 'fatal_error'
46
+ else
47
+ 'UNKNOWN'
48
+ end
49
+ end
50
+
51
+ def add_model(model)
52
+ @models[model.class.to_s] += 1
53
+ end
54
+
55
+ def add_query(query)
56
+ @queries << query
57
+ end
58
+
59
+ def action_definition
60
+ return unless meta[:controller] && meta[:action]
61
+
62
+ meta[:controller].constantize.instance_method(meta[:action]).source_location.join(':')
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module Buddy
5
+ class RequestsBuffer
6
+ class << self
7
+ def init
8
+ @collection = []
9
+ end
10
+
11
+ def find(id)
12
+ @collection.find { |r| r.id == id }
13
+ end
14
+
15
+ def push(request)
16
+ @collection << request
17
+ @collection.shift if @collection.size > Buddy.config.buffer_size
18
+ true
19
+ end
20
+
21
+ def all
22
+ @collection
23
+ end
24
+
25
+ def clear!
26
+ @collection = []
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Rails
6
+ module Buddy
7
+ module Subscribers
8
+ class ActionController < Base
9
+ EVENTS = { 'process_action.action_controller' => :process_action }.freeze
10
+
11
+ class << self
12
+ def process_action(event)
13
+ return if prevent_processing?
14
+
15
+ meta = event.payload
16
+ meta.delete :headers
17
+ meta.merge!({
18
+ duration: event.duration,
19
+ cpu_time: event.cpu_time,
20
+ idle_time: event.idle_time,
21
+ allocations: event.allocations
22
+ })
23
+
24
+ save_meta_in_current_request(meta)
25
+ end
26
+
27
+ private
28
+
29
+ def prevent_processing? = Current.ignore? || Current.request.nil?
30
+
31
+ def save_meta_in_current_request(meta)
32
+ Current.request.request = meta.delete(:request)
33
+ Current.request.response = meta.delete(:response)
34
+ Current.request.meta = meta
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end