Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
# Conflicts:
#	Gemfile.lock
  • Loading branch information
alxlion committed Oct 20, 2023
2 parents d9f455f + d1706d5 commit fa1d1c8
Show file tree
Hide file tree
Showing 33 changed files with 583 additions and 37 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.4.0

- Add support for ActiveJob
- Fix pretty formatting of payloads

## 0.3.1

- Add optional database config (thanks to @kiskoza).
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

gem 'rubocop', group: 'development', require: false
gem "sqlite3"
gem "sprockets-rails"
gem "pagy"
Expand Down
31 changes: 28 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
PATH
remote: .
specs:
eyeloupe (0.3.1)
eyeloupe (0.4.0)
importmap-rails (~> 1.1)
nokogiri (~> 1.15.4)
pagy (~> 6.0)
rails (~> 7.0)
ruby-openai (~> 4.1.0)
Expand Down Expand Up @@ -76,6 +77,7 @@ GEM
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
ast (2.4.2)
builder (3.2.4)
concurrent-ruby (1.2.2)
crass (1.0.6)
Expand All @@ -94,6 +96,7 @@ GEM
importmap-rails (1.1.6)
actionpack (>= 6.0.0)
railties (>= 6.0.0)
json (2.6.2)
loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
Expand All @@ -117,11 +120,14 @@ GEM
net-smtp (0.3.3)
net-protocol
nio4r (2.5.9)
nokogiri (1.14.2-arm64-darwin)
nokogiri (1.15.4-arm64-darwin)
racc (~> 1.4)
nokogiri (1.14.2-x86_64-linux)
nokogiri (1.15.4-x86_64-linux)
racc (~> 1.4)
pagy (6.0.4)
parallel (1.22.1)
parser (3.1.2.0)
ast (~> 2.4.1)
racc (1.6.2)
rack (2.2.7)
rack-test (2.1.0)
Expand Down Expand Up @@ -152,10 +158,26 @@ GEM
rake (>= 12.2)
thor (~> 1.0)
zeitwerk (~> 2.5)
rainbow (3.1.1)
rake (13.0.6)
regexp_parser (2.8.0)
rexml (3.2.5)
rubocop (1.31.2)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.1.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.18.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.19.1)
parser (>= 3.1.1.0)
ruby-openai (4.1.0)
faraday (>= 1)
faraday-multipart (>= 1)
ruby-progressbar (1.11.0)
ruby2_keywords (0.0.5)
sprockets (4.2.0)
concurrent-ruby (~> 1.0)
Expand All @@ -178,18 +200,21 @@ GEM
railties (>= 6.0.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.2.0)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
zeitwerk (2.6.7)

PLATFORMS
arm64-darwin-22
arm64-darwin-23
x86_64-linux

DEPENDENCIES
eyeloupe!
pagy
rubocop
sprockets-rails
sqlite3
tailwindcss-rails (~> 2.0)
Expand Down
11 changes: 2 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<img src="app/assets/images/eyeloupe/logo.png" width=120 alt="Logo" >
</a>

<h3 align="center">Eyeloupe (beta)</h3>
<h3 align="center">Eyeloupe</h3>

<p align="center">
The elegant Rails debug assistant. AI powered.
Expand Down Expand Up @@ -141,13 +141,6 @@ Eyeloupe is not a performance-oriented tool, the request time is the same you ca

Yes, Eyeloupe is inspired by Laravel Telescope. A lot of people coming from Laravel are missing Telescope or looking for something similar, so Eyeloupe is here to fill this gap.

## Roadmap

- [x] Exceptions - Track all the exceptions thrown by your application
- [x] AI assistant - Use OpenAI API to help you to solve your exceptions
- [ ] Custom links to the menu - To access all of your debug tool in one place (Sidekiq web, Mailhog, etc.)
- [ ] Refactoring / clean code - To make the code more readable and maintainable

## Contributing
Contributions are what makes the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.

Expand All @@ -165,7 +158,7 @@ The gem is available as open source under the terms of the [MIT License](https:/

## Contact

[![](https://img.shields.io/badge/@alxlion__-1DA1F2?style=for-the-badge&logo=twitter&logoColor=white)](https://twitter.com/alxlion_)
[![](https://img.shields.io/badge/@alxlion__-000000?style=for-the-badge&logo=x&logoColor=white)](https://x.com/alxlion_)

Project Link: [https://github.com/alxlion/eyeloupe](https://github.com/alxlion/eyeloupe)

Expand Down
6 changes: 5 additions & 1 deletion app/controllers/concerns/eyeloupe/searchable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ module Eyeloupe
module Searchable
extend ActiveSupport::Concern

path_models = %w[InRequest OutRequest]
name_models = %w[Job]

included do
before_action :set_query, only: [:index]
end
Expand All @@ -12,7 +15,8 @@ module Searchable

def set_query
model = ("Eyeloupe::" + controller_name.classify).constantize
@query = params[:q].present? ? model.where('path LIKE ?', "%#{params[:q].strip}%").order(created_at: :desc)
where = model.attribute_names.include?("path") ? 'path' : 'classname'
@query = params[:q].present? ? model.where("#{where} LIKE ?", "%#{params[:q].strip}%").order(created_at: :desc)
: model.all.order(created_at: :desc)
end
end
Expand Down
22 changes: 22 additions & 0 deletions app/controllers/eyeloupe/jobs_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module Eyeloupe
class JobsController < ApplicationController
include Searchable

before_action :set_job, only: %i[ show ]

def index
@pagy, @jobs = pagy(@query, items: 50)

render partial: 'frame' if params[:frame].present?
end

def show
end

private
# Use callbacks to share common setup or constraints between actions.
def set_job
@job = Job.find(params[:id])
end
end
end
4 changes: 4 additions & 0 deletions app/helpers/eyeloupe/jobs_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module Eyeloupe
module JobsHelper
end
end
32 changes: 32 additions & 0 deletions app/helpers/eyeloupe/request_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

module Eyeloupe
module RequestHelper
# @param [Eyeloupe::InRequest, Eyeloupe::OutRequest] request The request object
# @return [String] The formatted response
def format_response(request)
type = request.format.to_s != '*/*' ? request.format.to_s : request&.headers
format(type, request.response)
end

# @param [Eyeloupe::InRequest, Eyeloupe::OutRequest] request The request object
# @return [String] The formatted payload
def format_payload(request)
type = request.format.to_s != '*/*' ? request.format.to_s : request&.headers
format(type, request.payload)
end

private

def format(format, str)
case format
when /json/
JSON.pretty_generate(JSON.parse(str || '{}'))
when /xml/
Nokogiri::XML(str || '<></>').to_xml(indent: 2)
else
str
end
end
end
end
7 changes: 7 additions & 0 deletions app/models/eyeloupe/job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Eyeloupe
class Job < ApplicationRecord
validates :job_id, uniqueness: true

enum status: [:enqueued, :running, :completed, :failed, :discarded]
end
end
4 changes: 2 additions & 2 deletions app/views/eyeloupe/in_requests/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
<dt class="text-base font-medium leading-6 text-gray-900">Payload</dt>
<dd class="mt-1 text-base leading-6 sm:col-span-2 sm:mt-0 rounded-md bg-black text-white overflow-x-auto">
<% if @request.payload.present? %>
<pre class="p-2"><%= @request.payload %></pre>
<pre class="p-2"><%= format_payload @request %></pre>
<% else %>
<p class="text-gray-400 p-2">No payload</p>
<% end %>
Expand All @@ -74,7 +74,7 @@
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-base font-medium leading-6 text-gray-900">Response</dt>
<dd class="mt-1 text-base leading-6 sm:col-span-2 sm:mt-0 rounded-md bg-black text-white overflow-x-auto">
<pre class="p-2"><%= @request.format == "application/json" ? JSON.pretty_generate(JSON.parse(@request.response || "{}")) : @request.response %></pre>
<pre class="p-2"><%= format_response @request %></pre>
</dd>
</div>
</dl>
Expand Down
46 changes: 46 additions & 0 deletions app/views/eyeloupe/jobs/_frame.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<%= turbo_frame_tag "frame" do %>
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">

<table class="min-w-full divide-y divide-gray-300">
<thead>
<tr>
<th scope="col" class="py-3 pl-4 pr-3 text-left text-sm font-medium uppercase tracking-wide text-gray-500 sm:pl-0">Name</th>
<th scope="col" class="px-3 py-3 text-left text-sm font-medium uppercase tracking-wide text-gray-500">Queue</th>
<th scope="col" class="px-3 py-3 text-left text-sm font-medium uppercase tracking-wide text-gray-500">Adapter</th>
<th scope="col" class="px-3 py-3 text-left text-sm font-medium uppercase tracking-wide text-gray-500">Status</th>
<th scope="col" class="px-3 py-3 text-left text-sm font-medium uppercase tracking-wide text-gray-500">Retry</th>
<th scope="col" class="px-3 py-3 text-left text-sm font-medium uppercase tracking-wide text-gray-500">Enqueued at</th>
<th scope="col" class="relative py-3 pl-3 pr-4 sm:pr-0">
<span class="sr-only">Details</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<% @jobs.each do |job| %>
<tr>
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-base font-medium text-gray-900 sm:pl-0">
<%= job.classname %>
</td>
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500"><%= job.queue_name %></td>
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500"><%= job.adapter %></td>
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500">
<%= render "eyeloupe/shared/job_status", job: job %>
</td>
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500"><%= job.retry %></td>
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500"><%= distance_of_time_in_words(job.created_at, DateTime.now) %></td>
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-base font-medium sm:pr-0">
<%= link_to "Details", job_path(job), class: "text-gray-600 hover:text-gray-900", data: {"turbo_frame": "_top"} %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<aside class="mt-4 px-4 py-3 flex items-center justify-center sm:px-6" aria-label="Pagination">
<div class="flex-1 flex justify-center">
<%== pagy_nav(@pagy) %>
</div>
</aside>
</div>
<% end %>
18 changes: 18 additions & 0 deletions app/views/eyeloupe/jobs/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<div data-controller="eyeloupe--search" class="px-4 sm:px-6 lg:px-8 bg-white rounded-md shadow-md py-5">
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h1 class="text-xl font-semibold leading-6 text-gray-900">Jobs</h1>
<p class="mt-2 text-sm text-gray-700">All jobs running in your application</p>
</div>
<div>
<form method="get" data-action="eyeloupe--search#submit">
<input type="hidden" name="frame" value="frame" />
<label for="q" class="sr-only">Search</label>
<input type="text" id="q" name="q" value="<%= params[:q] %>" placeholder="Search for job class" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-red-500 focus:border-red-500 sm:text-sm" />
</form>
</div>
</div>
<div class="mt-8 flow-root">
<%= turbo_frame_tag "frame", src: jobs_path(frame: true), data: {"eyeloupe--refresh-target": "frame", "eyeloupe--search-target": "frame"} do %><% end %>
</div>
</div>
63 changes: 63 additions & 0 deletions app/views/eyeloupe/jobs/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<div class="px-4 sm:px-6 lg:px-8 bg-white rounded-md shadow-md py-5">
<div class="px-4 sm:px-0">
<h3 class="text-xl font-semibold leading-7 text-gray-900">Job details</h3>
</div>
<div class="mt-6 border-t border-gray-100">
<dl class="divide-y divide-gray-100">
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-base font-medium leading-6 text-gray-900">Enqueued at</dt>
<dd class="mt-1 text-base leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
<%= @job.created_at.to_formatted_s(:long) %> (<%= distance_of_time_in_words(@job.created_at, DateTime.now) %>)
</dd>
</div>
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-base font-medium leading-6 text-gray-900">Duration</dt>
<dd class="mt-1 text-base leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
<%= (@job.completed_at - @job.created_at).round %> seconds
</dd>
</div>
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-base font-medium leading-6 text-gray-900">Name</dt>
<dd class="mt-1 text-base leading-6 text-gray-700 sm:col-span-2 sm:mt-0"><%= @job.classname %></dd>
</div>
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-base font-medium leading-6 text-gray-900">Adapter</dt>
<dd class="mt-1 text-base leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
<%= @job.adapter %>
</dd>
</div>
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-base font-medium leading-6 text-gray-900">Queue</dt>
<dd class="mt-1 text-base leading-6 text-gray-700 sm:col-span-2 sm:mt-0"><%= @job.queue_name %></dd>
</div>
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-base font-medium leading-6 text-gray-900">Job ID</dt>
<dd class="mt-1 text-base leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
<%= @job.job_id %>
</dd>
</div>
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-base font-medium leading-6 text-gray-900">Retry</dt>
<dd class="mt-1 text-base leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
<%= @job.retry %>
</dd>
</div>
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-base font-medium leading-6 text-gray-900">Status</dt>
<dd class="mt-1 text-base leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
<%= render "eyeloupe/shared/job_status", job: @job %>
</dd>
</div>
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-base font-medium leading-6 text-gray-900">Arguments</dt>
<dd class="mt-1 text-base leading-6 sm:col-span-2 sm:mt-0 rounded-md bg-black text-white overflow-x-auto">
<% if @job.args.present? %>
<pre class="p-2"><%= JSON.pretty_generate(JSON.parse(@job.args || "{}")) %></pre>
<% else %>
<p class="text-gray-400 p-2">No args</p>
<% end %>
</dd>
</div>
</dl>
</div>
</div>
4 changes: 2 additions & 2 deletions app/views/eyeloupe/out_requests/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
<dt class="text-base font-medium leading-6 text-gray-900">Payload</dt>
<dd class="mt-1 text-base leading-6 sm:col-span-2 sm:mt-0 rounded-md bg-black text-white overflow-x-auto">
<% if @request.payload.present? %>
<pre class="p-2"><%= @request.payload %></pre>
<pre class="p-2"><%= format_payload @request %></pre>
<% else %>
<p class="text-gray-400 p-2">No payload</p>
<% end %>
Expand All @@ -61,7 +61,7 @@
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-base font-medium leading-6 text-gray-900">Response</dt>
<dd class="mt-1 text-base leading-6 sm:col-span-2 sm:mt-0 rounded-md bg-black text-white overflow-x-auto">
<pre class="p-2"><%= @request.format == "application/json" ? JSON.pretty_generate(JSON.parse(@request.response || "{}")) : @request.response %></pre>
<pre class="p-2"><%= format_response @request %></pre>
</dd>
</div>
</dl>
Expand Down

0 comments on commit fa1d1c8

Please sign in to comment.