diff --git a/CHANGELOG.md b/CHANGELOG.md
index a966015..1ce4e0e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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).
diff --git a/Gemfile b/Gemfile
index d331517..20017d4 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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"
diff --git a/Gemfile.lock b/Gemfile.lock
index a2f19a1..22e65ae 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -178,6 +200,7 @@ 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)
@@ -185,11 +208,13 @@ GEM
PLATFORMS
arm64-darwin-22
+ arm64-darwin-23
x86_64-linux
DEPENDENCIES
eyeloupe!
pagy
+ rubocop
sprockets-rails
sqlite3
tailwindcss-rails (~> 2.0)
diff --git a/README.md b/README.md
index ab0b537..795d014 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
-
Eyeloupe (beta)
+Eyeloupe
The elegant Rails debug assistant. AI powered.
@@ -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**.
@@ -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)
diff --git a/app/controllers/concerns/eyeloupe/searchable.rb b/app/controllers/concerns/eyeloupe/searchable.rb
index 4ec09cf..efe6f6f 100644
--- a/app/controllers/concerns/eyeloupe/searchable.rb
+++ b/app/controllers/concerns/eyeloupe/searchable.rb
@@ -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
@@ -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
diff --git a/app/controllers/eyeloupe/jobs_controller.rb b/app/controllers/eyeloupe/jobs_controller.rb
new file mode 100644
index 0000000..7efbcac
--- /dev/null
+++ b/app/controllers/eyeloupe/jobs_controller.rb
@@ -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
diff --git a/app/helpers/eyeloupe/jobs_helper.rb b/app/helpers/eyeloupe/jobs_helper.rb
new file mode 100644
index 0000000..8d00951
--- /dev/null
+++ b/app/helpers/eyeloupe/jobs_helper.rb
@@ -0,0 +1,4 @@
+module Eyeloupe
+ module JobsHelper
+ end
+end
diff --git a/app/helpers/eyeloupe/request_helper.rb b/app/helpers/eyeloupe/request_helper.rb
new file mode 100644
index 0000000..e81c5a4
--- /dev/null
+++ b/app/helpers/eyeloupe/request_helper.rb
@@ -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
diff --git a/app/models/eyeloupe/job.rb b/app/models/eyeloupe/job.rb
new file mode 100644
index 0000000..10c0812
--- /dev/null
+++ b/app/models/eyeloupe/job.rb
@@ -0,0 +1,7 @@
+module Eyeloupe
+ class Job < ApplicationRecord
+ validates :job_id, uniqueness: true
+
+ enum status: [:enqueued, :running, :completed, :failed, :discarded]
+ end
+end
diff --git a/app/views/eyeloupe/in_requests/show.html.erb b/app/views/eyeloupe/in_requests/show.html.erb
index 14f0bf8..0e117bd 100644
--- a/app/views/eyeloupe/in_requests/show.html.erb
+++ b/app/views/eyeloupe/in_requests/show.html.erb
@@ -53,7 +53,7 @@
Payload
<% if @request.payload.present? %>
- <%= @request.payload %>
+ <%= format_payload @request %>
<% else %>
No payload
<% end %>
@@ -74,7 +74,7 @@
Response
- <%= @request.format == "application/json" ? JSON.pretty_generate(JSON.parse(@request.response || "{}")) : @request.response %>
+ <%= format_response @request %>
diff --git a/app/views/eyeloupe/jobs/_frame.html.erb b/app/views/eyeloupe/jobs/_frame.html.erb
new file mode 100644
index 0000000..6424888
--- /dev/null
+++ b/app/views/eyeloupe/jobs/_frame.html.erb
@@ -0,0 +1,46 @@
+<%= turbo_frame_tag "frame" do %>
+
+
+
+
+
+
+ Name
+ Queue
+ Adapter
+ Status
+ Retry
+ Enqueued at
+
+ Details
+
+
+
+
+ <% @jobs.each do |job| %>
+
+
+ <%= job.classname %>
+
+ <%= job.queue_name %>
+ <%= job.adapter %>
+
+ <%= render "eyeloupe/shared/job_status", job: job %>
+
+ <%= job.retry %>
+ <%= distance_of_time_in_words(job.created_at, DateTime.now) %>
+
+ <%= link_to "Details", job_path(job), class: "text-gray-600 hover:text-gray-900", data: {"turbo_frame": "_top"} %>
+
+
+ <% end %>
+
+
+
+
+
+<% end %>
\ No newline at end of file
diff --git a/app/views/eyeloupe/jobs/index.html.erb b/app/views/eyeloupe/jobs/index.html.erb
new file mode 100644
index 0000000..8950b55
--- /dev/null
+++ b/app/views/eyeloupe/jobs/index.html.erb
@@ -0,0 +1,18 @@
+
+
+
+
Jobs
+
All jobs running in your application
+
+
+
+
+
+
+ <%= turbo_frame_tag "frame", src: jobs_path(frame: true), data: {"eyeloupe--refresh-target": "frame", "eyeloupe--search-target": "frame"} do %><% end %>
+
+
diff --git a/app/views/eyeloupe/jobs/show.html.erb b/app/views/eyeloupe/jobs/show.html.erb
new file mode 100644
index 0000000..989c357
--- /dev/null
+++ b/app/views/eyeloupe/jobs/show.html.erb
@@ -0,0 +1,63 @@
+
+
+
Job details
+
+
+
+
+
Enqueued at
+
+ <%= @job.created_at.to_formatted_s(:long) %> (<%= distance_of_time_in_words(@job.created_at, DateTime.now) %>)
+
+
+
+
Duration
+
+ <%= (@job.completed_at - @job.created_at).round %> seconds
+
+
+
+
Name
+ <%= @job.classname %>
+
+
+
Adapter
+
+ <%= @job.adapter %>
+
+
+
+
Queue
+ <%= @job.queue_name %>
+
+
+
Job ID
+
+ <%= @job.job_id %>
+
+
+
+
Retry
+
+ <%= @job.retry %>
+
+
+
+
Status
+
+ <%= render "eyeloupe/shared/job_status", job: @job %>
+
+
+
+
Arguments
+
+ <% if @job.args.present? %>
+ <%= JSON.pretty_generate(JSON.parse(@job.args || "{}")) %>
+ <% else %>
+ No args
+ <% end %>
+
+
+
+
+
\ No newline at end of file
diff --git a/app/views/eyeloupe/out_requests/show.html.erb b/app/views/eyeloupe/out_requests/show.html.erb
index be5c3c6..3ef7cff 100644
--- a/app/views/eyeloupe/out_requests/show.html.erb
+++ b/app/views/eyeloupe/out_requests/show.html.erb
@@ -40,7 +40,7 @@
Payload
<% if @request.payload.present? %>
- <%= @request.payload %>
+ <%= format_payload @request %>
<% else %>
No payload
<% end %>
@@ -61,7 +61,7 @@
Response
- <%= @request.format == "application/json" ? JSON.pretty_generate(JSON.parse(@request.response || "{}")) : @request.response %>
+ <%= format_response @request %>
diff --git a/app/views/eyeloupe/shared/_job_status.html.erb b/app/views/eyeloupe/shared/_job_status.html.erb
new file mode 100644
index 0000000..1e0991a
--- /dev/null
+++ b/app/views/eyeloupe/shared/_job_status.html.erb
@@ -0,0 +1,21 @@
+<% if job.enqueued? %>
+
+ <%= job.status.capitalize %>
+
+<% elsif job.running? %>
+
+ <%= job.status.capitalize %>
+
+<% elsif job.completed? %>
+
+ <%= job.status.capitalize %>
+
+<% elsif job.failed? %>
+
+ <%= job.status.capitalize %>
+
+<% else %>
+
+ <%= job.status.capitalize %>
+
+<% end %>
\ No newline at end of file
diff --git a/app/views/layouts/eyeloupe/application.html.erb b/app/views/layouts/eyeloupe/application.html.erb
index 440c5b7..e6beeed 100644
--- a/app/views/layouts/eyeloupe/application.html.erb
+++ b/app/views/layouts/eyeloupe/application.html.erb
@@ -84,6 +84,16 @@
Exceptions
<% end %>
+
+ <%= link_to jobs_path, class:"#{request.path.include?('/jobs') ? 'bg-gray-200 text-red-500' : ''} hover:bg-gray-200 text-gray-500 group flex gap-x-3 rounded-md p-2 text-base leading-6 font-medium" do %>
+
+
+
+
+
+ Jobs
+ <% end %>
+
@@ -148,6 +158,16 @@
Exceptions
<% end %>
+
+ <%= link_to jobs_path, class:"#{request.path.include?('/jobs') ? 'bg-gray-200 text-red-500' : ''} hover:bg-gray-200 text-gray-500 group flex gap-x-3 rounded-md p-2 text-base leading-6 font-medium" do %>
+
+
+
+
+
+ Jobs
+ <% end %>
+
diff --git a/config/routes.rb b/config/routes.rb
index 2fa8fab..690d0a7 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -5,6 +5,7 @@
resources :in_requests, only: [:index, :show]
resources :out_requests, only: [:index, :show]
resources :exceptions, only: [:index, :show]
+ resources :jobs, only: [:index, :show]
resources :ai_assistant_responses, only: [:show]
resource :data, only: [:destroy]
diff --git a/db/migrate/20230827161224_create_eyeloupe_jobs.rb b/db/migrate/20230827161224_create_eyeloupe_jobs.rb
new file mode 100644
index 0000000..437f91b
--- /dev/null
+++ b/db/migrate/20230827161224_create_eyeloupe_jobs.rb
@@ -0,0 +1,19 @@
+class CreateEyeloupeJobs < ActiveRecord::Migration[7.0]
+ def change
+ create_table :eyeloupe_jobs do |t|
+ t.string :classname
+ t.string :job_id
+ t.string :queue_name
+ t.string :adapter
+ t.integer :status, default: 0
+ t.datetime :scheduled_at
+ t.datetime :executed_at
+ t.datetime :completed_at
+ t.integer :retry, default: 0
+ t.string :args
+ t.timestamps
+
+ t.index :job_id, unique: true
+ end
+ end
+end
diff --git a/doc/img/screen.png b/doc/img/screen.png
index f582673..8b6326c 100644
Binary files a/doc/img/screen.png and b/doc/img/screen.png differ
diff --git a/eyeloupe.gemspec b/eyeloupe.gemspec
index 3ab4b94..d26ff70 100644
--- a/eyeloupe.gemspec
+++ b/eyeloupe.gemspec
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
spec.add_dependency "importmap-rails", "~> 1.1"
spec.add_dependency "pagy", "~> 6.0"
spec.add_dependency "ruby-openai", "~> 4.1.0"
+ spec.add_dependency "nokogiri", "~> 1.15.4"
spec.add_development_dependency "sqlite3", "~> 1.3.6"
spec.add_development_dependency "tailwindcss-rails", "~> 2.0"
diff --git a/lib/eyeloupe.rb b/lib/eyeloupe.rb
index b8d0e81..dc6a91d 100644
--- a/lib/eyeloupe.rb
+++ b/lib/eyeloupe.rb
@@ -6,6 +6,7 @@
require 'eyeloupe/processors/in_request'
require 'eyeloupe/processors/out_request'
require 'eyeloupe/processors/exception'
+require 'eyeloupe/processors/job'
require 'eyeloupe/concerns/rescuable'
require 'pagy'
diff --git a/lib/eyeloupe/engine.rb b/lib/eyeloupe/engine.rb
index 25f8ba2..81bd879 100644
--- a/lib/eyeloupe/engine.rb
+++ b/lib/eyeloupe/engine.rb
@@ -15,6 +15,30 @@ class Engine < ::Rails::Engine
initializer 'eyeloupe.active_job' do
ActiveSupport.on_load(:active_job) do
include Eyeloupe::Concerns::Rescuable
+
+ ActiveSupport::Notifications.subscribe("enqueue_at.active_job") do |*args|
+ Eyeloupe::Processors::Job.instance.process(ActiveSupport::Notifications::Event.new(*args))
+ end
+
+ ActiveSupport::Notifications.subscribe("enqueue.active_job") do |*args|
+ Eyeloupe::Processors::Job.instance.process(ActiveSupport::Notifications::Event.new(*args))
+ end
+
+ ActiveSupport::Notifications.subscribe("perform_start.active_job") do |*args|
+ Eyeloupe::Processors::Job.instance.run(ActiveSupport::Notifications::Event.new(*args))
+ end
+
+ ActiveSupport::Notifications.subscribe("perform.active_job") do |*args|
+ Eyeloupe::Processors::Job.instance.complete(ActiveSupport::Notifications::Event.new(*args))
+ end
+
+ ActiveSupport::Notifications.subscribe("retry_stopped.active_job") do |*args|
+ Eyeloupe::Processors::Job.instance.failed(ActiveSupport::Notifications::Event.new(*args))
+ end
+
+ ActiveSupport::Notifications.subscribe("discard.active_job") do |*args|
+ Eyeloupe::Processors::Job.instance.discard(ActiveSupport::Notifications::Event.new(*args))
+ end
end
end
diff --git a/lib/eyeloupe/processors/job.rb b/lib/eyeloupe/processors/job.rb
new file mode 100644
index 0000000..210b098
--- /dev/null
+++ b/lib/eyeloupe/processors/job.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+module Eyeloupe
+ module Processors
+ class Job
+ include Singleton
+
+ # @return [Array]
+ attr_accessor :subs
+
+ def initialize
+ @subs = []
+ end
+
+ # @param [ActiveSupport::Notifications::Event] event The event object
+ def process(event)
+ job = event.payload[:job]
+
+ Eyeloupe::Job.create(
+ classname: job.class.name,
+ job_id: job.job_id,
+ queue_name: queue_name(event),
+ adapter: adapter_name(event),
+ scheduled_at: scheduled_at(event),
+ status: :enqueued,
+ args: (args_info(job) || {}).to_json
+ )
+ end
+
+ # @param [ActiveSupport::Notifications::Event] event The event object
+ def run(event)
+ job = event.payload[:job]
+
+ Eyeloupe::Job.where(job_id: job.job_id).update(status: :running, executed_at: Time.now.utc)
+ end
+
+ # @param [ActiveSupport::Notifications::Event] event The event object
+ def complete(event)
+ job = event.payload[:job]
+
+ existing = Eyeloupe::Job.where(job_id: job.job_id).first
+
+ if existing&.failed?
+ Eyeloupe::Job.where(job_id: job.job_id).update(completed_at: Time.now.utc, retry: existing.retry + 1)
+ else
+ Eyeloupe::Job.where(job_id: job.job_id).update(
+ status: :completed,
+ completed_at: Time.now.utc,
+ retry: (job.executions.zero? ? 1 : job.executions) - 1
+ )
+ end
+ end
+
+ # @param [ActiveSupport::Notifications::Event] event The event object
+ def failed(event)
+ job = event.payload[:job]
+
+ Eyeloupe::Job.where(job_id: job.job_id).update(status: :failed)
+ end
+
+ # @param [ActiveSupport::Notifications::Event] event The event object
+ def discard(event)
+ job = event.payload[:job]
+
+ Eyeloupe::Job.where(job_id: job.job_id).update(status: :discarded)
+ end
+
+ # @param [ActiveJob::Base] job The job object
+ # @return [Array, nil]
+ def args_info(job)
+ if job.class.log_arguments? && job.arguments.any?
+ job.arguments
+ end
+ end
+
+ private
+
+ # @param [ActiveSupport::Notifications::Event] event The event object
+ # @return [String] The name of the queue
+ def queue_name(event)
+ event.payload[:job].queue_name
+ end
+
+ # @param [ActiveSupport::Notifications::Event] event The event object
+ # @return [String] The name of the adapter
+ def adapter_name(event)
+ event.payload[:adapter].class.name.demodulize.remove("Adapter")
+ end
+
+ # @param [ActiveSupport::Notifications::Event] event The event object
+ # @return [Time, nil] The time the job was scheduled at
+ def scheduled_at(event)
+ return unless event.payload[:job].scheduled_at
+ Time.at(event.payload[:job].scheduled_at).utc
+ end
+
+ # @param [String] event The event to subscribe to
+ # @param [Proc] block The block to execute when the event is triggered
+ # @yield [ActiveSupport::Notifications::Event] The event object
+ def subscribe(event, &block)
+ @subs << ActiveSupport::Notifications.subscribe(event) do |*args|
+ block.call(ActiveSupport::Notifications::Event.new(*args))
+ end
+ end
+
+ def unsubscribe
+ @subs.each do |sub|
+ ActiveSupport::Notifications.unsubscribe(sub)
+ end
+ @subs = []
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/eyeloupe/version.rb b/lib/eyeloupe/version.rb
index 911d821..e85e5f7 100644
--- a/lib/eyeloupe/version.rb
+++ b/lib/eyeloupe/version.rb
@@ -1,4 +1,4 @@
module Eyeloupe
# @return [String]
- VERSION = "0.3.1"
+ VERSION = "0.4.0"
end
diff --git a/test/controllers/eyeloupe/in_requests_controller_test.rb b/test/controllers/eyeloupe/in_requests_controller_test.rb
index 63b26ea..0db3b5b 100644
--- a/test/controllers/eyeloupe/in_requests_controller_test.rb
+++ b/test/controllers/eyeloupe/in_requests_controller_test.rb
@@ -4,15 +4,19 @@
module Eyeloupe
class InRequestsControllerTest < ActionDispatch::IntegrationTest
- fixtures "eyeloupe/in_requests"
+ include Engine.routes.url_helpers
+
+ setup do
+ @in_request = eyeloupe_in_requests(:one)
+ end
test "should get index" do
- get eyeloupe.in_requests_url
+ get in_requests_url
assert_response :success
end
test "should get show" do
- get eyeloupe.in_request_url(eyeloupe_in_requests(:one))
+ get in_request_url(@in_request)
assert_response :success
end
end
diff --git a/test/controllers/eyeloupe/jobs_controller_test.rb b/test/controllers/eyeloupe/jobs_controller_test.rb
new file mode 100644
index 0000000..8b3cfba
--- /dev/null
+++ b/test/controllers/eyeloupe/jobs_controller_test.rb
@@ -0,0 +1,21 @@
+require "test_helper"
+
+module Eyeloupe
+ class JobsControllerTest < ActionDispatch::IntegrationTest
+ include Engine.routes.url_helpers
+
+ setup do
+ @job = eyeloupe_jobs(:one)
+ end
+
+ test "should get index" do
+ get jobs_url
+ assert_response :success
+ end
+
+ test "should get show" do
+ get jobs_url(@job)
+ assert_response :success
+ end
+ end
+end
diff --git a/test/dummy/app/helpers/application_helper.rb b/test/dummy/app/helpers/application_helper.rb
index 2ddd659..3a2b877 100644
--- a/test/dummy/app/helpers/application_helper.rb
+++ b/test/dummy/app/helpers/application_helper.rb
@@ -1,3 +1,2 @@
module ApplicationHelper
-
end
\ No newline at end of file
diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb
index 504da8f..69369ca 100644
--- a/test/dummy/db/schema.rb
+++ b/test/dummy/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2023_06_04_190442) do
+ActiveRecord::Schema[7.0].define(version: 2023_08_27_161224) do
create_table "eyeloupe_exceptions", force: :cascade do |t|
t.string "hostname"
t.string "kind"
@@ -49,6 +49,22 @@
t.datetime "updated_at", null: false
end
+ create_table "eyeloupe_jobs", force: :cascade do |t|
+ t.string "classname"
+ t.string "job_id"
+ t.string "queue_name"
+ t.string "adapter"
+ t.integer "status", default: 0
+ t.datetime "scheduled_at"
+ t.datetime "executed_at"
+ t.datetime "completed_at"
+ t.integer "retry", default: 0
+ t.string "args"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["job_id"], name: "index_eyeloupe_jobs_on_job_id", unique: true
+ end
+
create_table "eyeloupe_out_requests", force: :cascade do |t|
t.string "verb"
t.string "hostname"
diff --git a/test/fixtures/eyeloupe/jobs.yml b/test/fixtures/eyeloupe/jobs.yml
new file mode 100644
index 0000000..0b6ef49
--- /dev/null
+++ b/test/fixtures/eyeloupe/jobs.yml
@@ -0,0 +1,23 @@
+# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+one:
+ classname: MyString
+ job_id: MyString
+ queue_name: MyString
+ adapter: MyString
+ status: 0
+ retry: 0
+ executed_at: 2023-08-27 18:12:24
+ completed_at: 2023-08-27 18:12:24
+ scheduled_at: 2023-08-27 18:12:24
+
+two:
+ classname: MyString
+ job_id: MyString2
+ queue_name: MyString
+ adapter: MyString
+ status: 0
+ retry: 0
+ executed_at: 2023-08-27 18:12:24
+ completed_at: 2023-08-27 18:12:24
+ scheduled_at: 2023-08-27 18:12:24
diff --git a/test/lib/processors/job_processor_test.rb b/test/lib/processors/job_processor_test.rb
new file mode 100644
index 0000000..17a30e5
--- /dev/null
+++ b/test/lib/processors/job_processor_test.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require "test_helper"
+include ActiveJob::TestHelper
+
+class JobProcessorTest < ActiveSupport::TestCase
+ def setup
+ @job_processor = Eyeloupe::Processors::Job.instance
+
+ activejob = ActiveJob::Base.new
+ activejob.job_id = SecureRandom.uuid
+ @event = ActiveSupport::Notifications::Event.new("test.event", Time.now, Time.now, SecureRandom.uuid, {job: activejob})
+ end
+
+ test "should initialize job processor" do
+ assert_not_nil @job_processor
+ assert_equal [], @job_processor.subs
+ end
+
+ test "should process" do
+ job = @job_processor.process(@event)
+ assert_not_nil job
+ assert job.persisted?
+ end
+
+ test "should run" do
+ job = @job_processor.process(@event)
+ @job_processor.run(@event)
+ job.reload
+ assert_equal "running", job.status
+ end
+
+ test "should complete without fail" do
+ job = @job_processor.process(@event)
+ @job_processor.run(@event)
+ @job_processor.complete(@event)
+ job.reload
+ assert_equal "completed", job.status
+ assert_equal 0, job.retry
+ end
+
+ test "should complete with fail" do
+ job = @job_processor.process(@event)
+ @job_processor.run(@event)
+ job.update(status: :failed)
+ @job_processor.complete(@event)
+ job.reload
+ assert_equal "failed", job.status
+ assert_equal 1, job.retry
+ end
+
+ test "should be failed" do
+ job = @job_processor.process(@event)
+ @job_processor.failed(@event)
+ job.reload
+ assert_equal "failed", job.status
+ end
+
+ test "should be discarded" do
+ job = @job_processor.process(@event)
+ @job_processor.discard(@event)
+ job.reload
+ assert_equal "discarded", job.status
+ end
+
+end
diff --git a/test/models/eyeloupe/job_test.rb b/test/models/eyeloupe/job_test.rb
new file mode 100644
index 0000000..f5cdb4e
--- /dev/null
+++ b/test/models/eyeloupe/job_test.rb
@@ -0,0 +1,9 @@
+require "test_helper"
+
+module Eyeloupe
+ class JobTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+ end
+end
diff --git a/test/system/eyeloupe/out_requests_test.rb b/test/system/eyeloupe/out_requests_test.rb
deleted file mode 100644
index b0c5fda..0000000
--- a/test/system/eyeloupe/out_requests_test.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-require "application_system_test_case"
-
-module Eyeloupe
- class OutRequestsTest < ApplicationSystemTestCase
- setup do
- @out_request = eyeloupe_out_requests(:one)
- end
-
- test "visiting the index" do
- visit out_requests_url
- assert_selector "h1", text: "HTTP Client"
- end
- end
-end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 7230d9c..32a9028 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -6,6 +6,8 @@
ActiveRecord::Migrator.migrations_paths << File.expand_path("../db/migrate", __dir__)
require "rails/test_help"
+include Eyeloupe::RequestHelper
+
# Load fixtures from the engine
if ActiveSupport::TestCase.respond_to?(:fixture_path=)
ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__)