Display workflow as ASCII table

master
aeris 2 years ago
parent c232e1c173
commit 11a932fc2a
  1. 14
      Gemfile.lock
  2. 28
      lib/sidekiq/workflow.rb
  3. 5
      lib/sidekiq/workflow/client.rb
  4. 8
      lib/sidekiq/workflow/job.rb
  5. 45
      lib/sidekiq/workflow/overview.rb
  6. 1
      lib/sidekiq/workflow/worker.rb
  7. 6
      sidekiq-workflow.gemspec
  8. 70
      spec/job_spec.rb
  9. 2
      spec/spec_helper.rb

@ -2,15 +2,23 @@ PATH
remote: .
specs:
sidekiq-workflow (0.0.0)
colorize (~> 0.8, >= 0.8.1)
redis (~> 4.3, >= 4.3.1)
redlock (~> 1.2, >= 1.2.1)
sidekiq (~> 6.2, >= 6.2.1)
terminal-table (~> 3.0, >= 3.0.1)
GEM
remote: https://rubygems.org/
specs:
coderay (1.1.3)
colorize (0.8.1)
connection_pool (2.2.5)
diff-lcs (1.4.4)
method_source (1.0.0)
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
rack (2.2.3)
redis (4.3.1)
redlock (1.2.1)
@ -32,13 +40,19 @@ GEM
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
terminal-table (3.0.1)
unicode-display_width (>= 1.1.1, < 3)
timecop (0.9.4)
unicode-display_width (2.0.0)
PLATFORMS
x86_64-linux
DEPENDENCIES
pry (~> 0.14, >= 0.14.1)
rspec (~> 3.10, >= 3.10.0)
sidekiq-workflow!
timecop (~> 0.9, >= 0.9.4)
BUNDLED WITH
2.2.16

@ -1,14 +1,14 @@
require 'sidekiq'
class Sidekiq::Workflow
attr_reader :id
attr_reader :jobs, :depends
attr_reader :klass, :id, :jobs, :depends
def self.configure(config)
Client.instance.configure config
end
def initialize(id, jobs = {})
def initialize(klass, id, jobs = {})
@klass = klass
@id = id
@jobs = jobs
@depends = []
@ -23,9 +23,8 @@ class Sidekiq::Workflow
Client.instance.persist_workflow self
end
def self.start!(...)
workflow = self.create
workflow.configure(...)
def self.create!(...)
workflow = self.create(...)
depends = workflow.depends
jobs = workflow.jobs
@ -35,6 +34,11 @@ class Sidekiq::Workflow
end
workflow.persist!
workflow
end
def self.start!(...)
workflow = self.create!(...)
workflow.start!
workflow
end
@ -47,17 +51,20 @@ class Sidekiq::Workflow
return :failed if @jobs.any? { |_, j| j.failed? }
return :error if @jobs.any? { |_, j| j.error? }
return :finished if @jobs.all? { |_, j| j.finished? }
return :started if @jobs.any? { |_, j| j.started? }
return :pending
end
private
def self.create
self.new SecureRandom.uuid
def self.create(...)
workflow = self.new self.name, SecureRandom.uuid
workflow.configure(...)
workflow
end
def job(*args, before: nil, after: nil)
job = Job.create self, *args, {}
def job(klass, *args, before: nil, after: nil)
job = Job.create self, klass.name, *args, {}
id = job.id
@jobs[id] = job
@ -75,3 +82,4 @@ end
require 'sidekiq/workflow/client'
require 'sidekiq/workflow/worker'
require 'sidekiq/workflow/job'
require 'sidekiq/workflow/overview'

@ -30,9 +30,10 @@ class Sidekiq::Workflow::Client
key = "workflow::#{id}"
raise "Workflow #{id} not found" unless redis.exists? key
workflow = redis.hgetall key
klass = workflow.fetch 'class'
jobs = workflow['jobs'].split "\n"
jobs = self.load_jobs redis, jobs
Sidekiq::Workflow.new id, jobs
Sidekiq::Workflow.new klass, id, jobs
end
end
@ -122,7 +123,7 @@ class Sidekiq::Workflow::Client
raise "Job #{id} not found" unless redis.exists? key
job = redis.hgetall key
workflow = job.fetch 'workflow'
klass = Object.const_get job.fetch 'class'
klass = job.fetch 'class'
args = job['args']
args = args ? JSON.load(args) : []
before = job.fetch('before', '').split(',')

@ -37,8 +37,8 @@ class Sidekiq::Workflow::Job
def status
return :failed if self.failed?
return :error if self.error?
return :finished if self.finished?
return :error if self.error?
return :started if self.started?
return :enqueued if self.enqueued?
:pending
@ -53,8 +53,8 @@ class Sidekiq::Workflow::Job
!self.have_after?
end
def self.create(workflow, *args)
self.new workflow.id, SecureRandom.uuid, *args
def self.create(workflow, klass, *args)
self.new workflow.id, SecureRandom.uuid, klass, *args
end
def persist!
@ -66,6 +66,6 @@ class Sidekiq::Workflow::Job
end
def perform
@klass.perform_async self.id, *@args
Object.const_get(@klass).perform_async self.id, *@args
end
end

@ -0,0 +1,45 @@
require 'terminal-table'
require 'colorize'
class Sidekiq::Workflow::Overview
def initialize(workflow)
@workflow = workflow
end
STATUS = {
pending: { color: :cyan, label: 'Pending', symbol: '💤' },
enqueued: { color: :blue, label: 'Enqueued', symbol: '🕓' },
running: { color: :yellow, label: 'Running', symbol: '⚙' },
error: { color: :magenta, label: 'Retrying', symbol: '🔁' },
failed: { color: :red, label: 'Failed', symbol: '❌' },
finished: { color: :green, label: 'Succeeded', symbol: '✅' }
}.freeze
def status(status)
color, label, symbol = STATUS.fetch(status.to_sym).values_at :color, :label, :symbol
"#{symbol} #{label.colorize(color)}"
end
def ascii
errors = {}
jobs = Terminal::Table.new do |t|
@workflow.jobs.each do |_, job|
t << [job.id, job.klass, self.status(job.status)]
e = job.errors
errors[job] = e unless e.empty?
end
end
errors = errors.empty? ? nil : Terminal::Table.new do |t|
errors.each do |job, es|
t << ["#{job.klass} #{job.id}", es]
end
end
Terminal::Table.new do |t|
t << ['ID', @workflow.id]
t << ['Name', @workflow.klass]
t << ['Status', self.status(@workflow.status)]
t << ['Jobs', jobs]
t << ['Errors', errors] if errors
end
end
end

@ -7,7 +7,6 @@ module Sidekiq::Workflow::Worker
result, exception = nil, nil
begin
result = super *args, **kwargs
@job.error_at = nil
@job.finished_at = Time.now
rescue => e
exception = e

@ -6,7 +6,7 @@ Gem::Specification.new do |spec|
spec.authors = ['aeris']
spec.email = ['aeris@cozycloud.cc']
spec.files = %w(README.md LICENSE) + Dir.glob('lib/**/*', base: __dir__)
spec.executables = Dir.glob('bin/**/*', base: File.join(__dir__, spec.bindir))
# spec.executables = Dir.glob('bin/**/*', base: File.join(__dir__, spec.bindir))
spec.test_files = Dir.glob('spec/**/*', base: __dir__)
spec.homepage = 'https://rubygems.org/gems/sidekiq-workflow'
spec.license = 'AGPL-3.0+'
@ -14,6 +14,10 @@ Gem::Specification.new do |spec|
spec.add_dependency 'sidekiq', '~> 6.2', '>= 6.2.1'
spec.add_dependency 'redis', '~> 4.3', '>= 4.3.1'
spec.add_dependency 'redlock', '~> 1.2', '>= 1.2.1'
spec.add_dependency 'terminal-table', '~> 3.0', '>= 3.0.1'
spec.add_dependency 'colorize', '~> 0.8', '>= 0.8.1'
spec.add_development_dependency 'pry', '~> 0.14', '>= 0.14.1'
spec.add_development_dependency 'rspec', '~> 3.10', '>= 3.10.0'
spec.add_development_dependency 'timecop', '~> 0.9', '>= 0.9.4'
end

@ -53,12 +53,78 @@ RSpec.describe Sidekiq::Workflow::Job do
expect(job.status).to eq :enqueued
job.started_at = Time.now
expect(job.status).to eq :started
job.finished_at = Time.now
expect(job.status).to eq :finished
job.error_at = Time.now
expect(job.status).to eq :error
job.finished_at = Time.now
expect(job.status).to eq :finished
job.failed_at = Time.now
expect(job.status).to eq :failed
end
end
describe '#state' do
it 'must flag the job as finished in case of success' do
class TestJob
include Sidekiq::Workflow::Worker
def perform; end
end
workflow = Sidekiq::Workflow.new Class, 'workflow_1'
job = Sidekiq::Workflow::Job.create workflow, TestJob.name
allow(Sidekiq::Workflow::Job).to receive(:find).with(job.id) { job }
expect(job.enqueued_at).to be_nil
expect(job.finished_at).to be_nil
expect(job.error_at).to be_nil
expect(job.failed_at).to be_nil
job.perform
expect(job.enqueued_at).to_not be_nil
expect(job.finished_at).to be_nil
expect(job.error_at).to be_nil
expect(job.failed_at).to be_nil
TestJob.perform_one
expect(job.enqueued_at).to_not be_nil
expect(job.finished_at).to_not be_nil
expect(job.error_at).to be_nil
expect(job.failed_at).to be_nil
end
it 'must flag the job as errored in case of error' do
class TestJob
include Sidekiq::Workflow::Worker
def self.message=(message)
@@message = message
end
def perform
raise @@message if @@message
end
end
workflow = Sidekiq::Workflow.new Class, 'workflow_1'
job = Sidekiq::Workflow::Job.create workflow, TestJob.name
allow(Sidekiq::Workflow::Job).to receive(:find).with(job.id) { job }
job.perform
expect(job.error_at).to be_nil
expect(job.errors).to be_empty
expect(job.finished_at).to be_nil
TestJob.message = 'perform_error'
Timecop.freeze do |now|
expect { TestJob.perform_one }.to raise_error 'perform_error'
expect(job.error_at).to eq now
expect(job.errors).to eq([{ date: now, error: 'perform_error' }])
end
expect(job.finished_at).to be_nil
job.perform
TestJob.message = nil
TestJob.perform_one
expect(job.error_at).to_not be_nil
expect(job.finished_at).to_not be_nil
end
end
end

@ -1,5 +1,5 @@
require 'sidekiq/workflow'
require 'pry-byebug'
require 'timecop'
redis_url = ENV.fetch 'REDIS_URL', 'redis://localhost/'
Sidekiq::Workflow.configure url: redis_url

Loading…
Cancel
Save