mirror of
https://github.com/plashchynski/crono.git
synced 2026-05-16 13:06:59 +02:00
Compare commits
59 Commits
v0.6.1
...
next_jobs_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4ad8fb953 | ||
|
|
d075a55f03 | ||
|
|
2d72020ac4 | ||
|
|
cde8a2d214 | ||
|
|
2ec9cfa829 | ||
|
|
63c86c8cd9 | ||
|
|
e10daec9c6 | ||
|
|
f72c288ce8 | ||
|
|
78ce578484 | ||
|
|
0c77c490bd | ||
|
|
d889b9380d | ||
|
|
f75bdf352b | ||
|
|
fa97f573e0 | ||
|
|
dc70212f9d | ||
|
|
7328bea24c | ||
|
|
f57d440424 | ||
|
|
95a237aeb5 | ||
|
|
6508197f26 | ||
|
|
a0c612fb27 | ||
|
|
a3c4ec87f5 | ||
|
|
9b85c8b8c3 | ||
|
|
1af691ef24 | ||
|
|
8a89a9a8eb | ||
|
|
7e2e65e21b | ||
|
|
4205b12fe1 | ||
|
|
bb9f62a69b | ||
|
|
a9798acb35 | ||
|
|
1aa27baca8 | ||
|
|
af1e1959ac | ||
|
|
7af68aa591 | ||
|
|
6d2e3fbb75 | ||
|
|
46fe2548d9 | ||
|
|
6859d1f09a | ||
|
|
6674bca0be | ||
|
|
5a5c29c52a | ||
|
|
a24389f6cc | ||
|
|
54a1b53af9 | ||
|
|
eedff96d39 | ||
|
|
bb9ab77c1d | ||
|
|
1b5479044f | ||
|
|
9133a664bd | ||
|
|
a667c6ca24 | ||
|
|
b95c480a8f | ||
|
|
ba57834f68 | ||
|
|
4fb45724d6 | ||
|
|
bb03a562cc | ||
|
|
c0b25b2a7c | ||
|
|
f43ae4b1b1 | ||
|
|
00e51604ae | ||
|
|
b4d15f7909 | ||
|
|
b3920fa2ee | ||
|
|
5652d19e62 | ||
|
|
dc1f55e13b | ||
|
|
98f058767f | ||
|
|
5db94543d1 | ||
|
|
3e5363f560 | ||
|
|
6d90cb3233 | ||
|
|
368cdde296 | ||
|
|
84717493a8 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
pkg/*
|
pkg/*
|
||||||
*.gem
|
*.gem
|
||||||
.bundle
|
.bundle
|
||||||
|
tmp/*.sqlite3
|
||||||
|
|||||||
28
Changes.md
28
Changes.md
@@ -1,19 +1,35 @@
|
|||||||
0.5.0
|
0.8.0
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
- Initial release!
|
- Added `on` (day of week) option to cronotab.rb semantic
|
||||||
|
- Added job health check and job health indicator to the Web UI
|
||||||
|
|
||||||
0.5.1
|
|
||||||
|
0.7.0
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
- Added -e/--environment ENV option to set the daemon rails environment.
|
- Added simple Web UI
|
||||||
|
|
||||||
|
|
||||||
|
0.6.1
|
||||||
|
-----------
|
||||||
|
|
||||||
|
- Persist job state to your database.
|
||||||
|
|
||||||
|
|
||||||
0.5.2
|
0.5.2
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
- Fix: Scheduled time now related to the last performing time.
|
- Fix: Scheduled time now related to the last performing time.
|
||||||
|
|
||||||
0.6.1
|
|
||||||
|
0.5.1
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
- Persist job state to your database.
|
- Added -e/--environment ENV option to set the daemon rails environment.
|
||||||
|
|
||||||
|
|
||||||
|
0.5.0
|
||||||
|
-----------
|
||||||
|
|
||||||
|
- Initial release!
|
||||||
|
|||||||
43
Gemfile.lock
43
Gemfile.lock
@@ -1,7 +1,7 @@
|
|||||||
PATH
|
PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
crono (0.6.1)
|
crono (0.8.1)
|
||||||
activejob (~> 4.0)
|
activejob (~> 4.0)
|
||||||
activerecord (~> 4.0)
|
activerecord (~> 4.0)
|
||||||
activesupport (~> 4.0)
|
activesupport (~> 4.0)
|
||||||
@@ -9,17 +9,17 @@ PATH
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
activejob (4.2.0)
|
activejob (4.2.1)
|
||||||
activesupport (= 4.2.0)
|
activesupport (= 4.2.1)
|
||||||
globalid (>= 0.3.0)
|
globalid (>= 0.3.0)
|
||||||
activemodel (4.2.0)
|
activemodel (4.2.1)
|
||||||
activesupport (= 4.2.0)
|
activesupport (= 4.2.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
activerecord (4.2.0)
|
activerecord (4.2.1)
|
||||||
activemodel (= 4.2.0)
|
activemodel (= 4.2.1)
|
||||||
activesupport (= 4.2.0)
|
activesupport (= 4.2.1)
|
||||||
arel (~> 6.0)
|
arel (~> 6.0)
|
||||||
activesupport (4.2.0)
|
activesupport (4.2.1)
|
||||||
i18n (~> 0.7)
|
i18n (~> 0.7)
|
||||||
json (~> 1.7, >= 1.7.7)
|
json (~> 1.7, >= 1.7.7)
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
@@ -27,12 +27,25 @@ GEM
|
|||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
arel (6.0.0)
|
arel (6.0.0)
|
||||||
builder (3.2.2)
|
builder (3.2.2)
|
||||||
|
byebug (3.5.1)
|
||||||
|
columnize (~> 0.8)
|
||||||
|
debugger-linecache (~> 1.2)
|
||||||
|
slop (~> 3.6)
|
||||||
|
columnize (0.9.0)
|
||||||
|
debugger-linecache (1.2.0)
|
||||||
diff-lcs (1.2.5)
|
diff-lcs (1.2.5)
|
||||||
globalid (0.3.3)
|
globalid (0.3.3)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
|
haml (4.0.6)
|
||||||
|
tilt
|
||||||
i18n (0.7.0)
|
i18n (0.7.0)
|
||||||
json (1.8.2)
|
json (1.8.2)
|
||||||
minitest (5.5.1)
|
minitest (5.5.1)
|
||||||
|
rack (1.6.0)
|
||||||
|
rack-protection (1.5.3)
|
||||||
|
rack
|
||||||
|
rack-test (0.6.3)
|
||||||
|
rack (>= 1.0)
|
||||||
rake (10.4.2)
|
rake (10.4.2)
|
||||||
rspec (3.2.0)
|
rspec (3.2.0)
|
||||||
rspec-core (~> 3.2.0)
|
rspec-core (~> 3.2.0)
|
||||||
@@ -47,8 +60,14 @@ GEM
|
|||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.2.0)
|
rspec-support (~> 3.2.0)
|
||||||
rspec-support (3.2.2)
|
rspec-support (3.2.2)
|
||||||
|
sinatra (1.4.5)
|
||||||
|
rack (~> 1.4)
|
||||||
|
rack-protection (~> 1.4)
|
||||||
|
tilt (~> 1.3, >= 1.3.4)
|
||||||
|
slop (3.6.0)
|
||||||
sqlite3 (1.3.10)
|
sqlite3 (1.3.10)
|
||||||
thread_safe (0.3.4)
|
thread_safe (0.3.5)
|
||||||
|
tilt (1.4.1)
|
||||||
timecop (0.7.3)
|
timecop (0.7.3)
|
||||||
tzinfo (1.2.2)
|
tzinfo (1.2.2)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
@@ -58,8 +77,12 @@ PLATFORMS
|
|||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
bundler (>= 1.0.0)
|
bundler (>= 1.0.0)
|
||||||
|
byebug
|
||||||
crono!
|
crono!
|
||||||
|
haml
|
||||||
|
rack-test
|
||||||
rake (~> 10.0)
|
rake (~> 10.0)
|
||||||
rspec (~> 3.0)
|
rspec (~> 3.0)
|
||||||
|
sinatra
|
||||||
sqlite3
|
sqlite3
|
||||||
timecop (~> 0.7)
|
timecop (~> 0.7)
|
||||||
|
|||||||
87
README.md
87
README.md
@@ -9,9 +9,11 @@ Crono — Job scheduler for Rails
|
|||||||
Crono is a time-based background job scheduler daemon (just like Cron) for Ruby on Rails.
|
Crono is a time-based background job scheduler daemon (just like Cron) for Ruby on Rails.
|
||||||
|
|
||||||
|
|
||||||
## The Idea
|
## The Purpose
|
||||||
|
|
||||||
Currently there is no such thing as Cron in Ruby for Rails. Well, there's [Whenever](https://github.com/javan/whenever) but it works on top of Unix Cron, so you have no total control of it from Ruby. Crono is pure Ruby. It doesn't use Unix Cron and other platform-dependent things. So you can use it on all platforms supported by Ruby. It persists job state to your database using Active Record. You have full control of jobs performing process. You have Ruby code, so you can understand and modify it to fit your needs.
|
Currently there is no such thing as Ruby Cron for Rails. Well, there's [Whenever](https://github.com/javan/whenever) but it works on top of Unix Cron, so you haven't control of it from Ruby. Crono is pure Ruby. It doesn't use Unix Cron and other platform-dependent things. So you can use it on all platforms supported by Ruby. It persists job states to your database using Active Record. You have full control of jobs performing process. It's Ruby, so you can understand and modify it to fit your needs.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
@@ -24,7 +26,9 @@ Other versions are untested but might work fine.
|
|||||||
|
|
||||||
Add the following line to your application's Gemfile:
|
Add the following line to your application's Gemfile:
|
||||||
|
|
||||||
gem 'crono'
|
```ruby
|
||||||
|
gem 'crono'
|
||||||
|
```
|
||||||
|
|
||||||
Run the `bundle` command to install it.
|
Run the `bundle` command to install it.
|
||||||
After you install Crono, you can run the generator:
|
After you install Crono, you can run the generator:
|
||||||
@@ -45,42 +49,53 @@ Now you are ready to move forward to create a job and schedule it.
|
|||||||
|
|
||||||
Crono can use Active Job jobs from `app/jobs/`. The only requirements is that the `perform` method should take no arguments.
|
Crono can use Active Job jobs from `app/jobs/`. The only requirements is that the `perform` method should take no arguments.
|
||||||
|
|
||||||
Here's an example of a test job:
|
Here's an example of a job:
|
||||||
|
|
||||||
# app/jobs/test_job.rb
|
```ruby
|
||||||
class TestJob < ActiveJob::Base
|
# app/jobs/test_job.rb
|
||||||
def perform
|
class TestJob < ActiveJob::Base
|
||||||
# put you scheduled code here
|
def perform
|
||||||
# Comments.deleted.clean_up...
|
# put you scheduled code here
|
||||||
end
|
# Comments.deleted.clean_up...
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
The ActiveJob jobs is convenient because you can use one job in both periodic and enqueued ways. But Active Job is not required. Any class can be used as a crono job if it implements a method `perform` without arguments:
|
The ActiveJob jobs is convenient because you can use one job in both periodic and enqueued ways. But Active Job is not required. Any class can be used as a crono job if it implements a method `perform` without arguments:
|
||||||
|
|
||||||
class TestJob # This is not an Active Job job, but pretty legal Crono job.
|
```ruby
|
||||||
def perform
|
class TestJob # This is not an Active Job job, but pretty legal Crono job.
|
||||||
# put you scheduled code here
|
def perform
|
||||||
# Comments.deleted.clean_up...
|
# put you scheduled code here
|
||||||
end
|
# Comments.deleted.clean_up...
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
_Please note that crono uses threads, so your code should be thread-safe_
|
||||||
|
|
||||||
#### Job Schedule
|
#### Job Schedule
|
||||||
|
|
||||||
The schedule described in the configuration file `config/cronotab.rb`, that created using `crono:install` or manually. The semantic is pretty straightforward:
|
Schedule list is defined in the file `config/cronotab.rb`, that created using `crono:install`. The semantic is pretty straightforward:
|
||||||
|
|
||||||
# config/cronotab.rb
|
```ruby
|
||||||
Crono.perform(TestJob).every 2.days, at: "15:30"
|
# config/cronotab.rb
|
||||||
|
Crono.perform(TestJob).every 2.days, at: {hour: 15, min: 30}
|
||||||
|
Crono.perform(TestJob).every 1.week, on: :monday, at: "15:30"
|
||||||
|
```
|
||||||
|
|
||||||
You can schedule one job a few times, if you want a job to be performed a few times a day:
|
You can schedule one job a few times, if you want the job to be performed a few times a day or a week:
|
||||||
|
|
||||||
Crono.perform(TestJob).every 1.day, at: "00:00"
|
```ruby
|
||||||
Crono.perform(TestJob).every 1.day, at: "12:00"
|
Crono.perform(TestJob).every 1.week, on: :monday
|
||||||
|
Crono.perform(TestJob).every 1.week, on: :thursday
|
||||||
|
```
|
||||||
|
|
||||||
The `at` can be a Hash:
|
The `at` can be a Hash:
|
||||||
|
|
||||||
Crono.perform(TestJob).every 1.day, at: {hour: 12, min: 15}
|
```ruby
|
||||||
|
Crono.perform(TestJob).every 1.day, at: {hour: 12, min: 15}
|
||||||
|
```
|
||||||
|
|
||||||
#### Run daemon
|
#### Run daemon
|
||||||
|
|
||||||
@@ -98,6 +113,28 @@ Usage: crono [options]
|
|||||||
-e, --environment ENV Application environment (Default: development)
|
-e, --environment ENV Application environment (Default: development)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Web UI
|
||||||
|
|
||||||
|
Crono comes with a Sinatra application that can display the current state of Crono jobs.
|
||||||
|
Add `sinatra` and `haml` to your Gemfile
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gam 'haml'
|
||||||
|
gem 'sinatra', require: nil
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the following to your `config/routes.rb`:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
Rails.application.routes.draw do
|
||||||
|
mount Crono::Web, at: '/crono'
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Access management and other questions described in the [wiki](https://github.com/plashchynski/crono/wiki/Web-UI).
|
||||||
|
|
||||||
|
|
||||||
## Capistrano
|
## Capistrano
|
||||||
|
|
||||||
Use the `capistrano-crono` gem ([github](https://github.com/plashchynski/capistrano-crono/)).
|
Use the `capistrano-crono` gem ([github](https://github.com/plashchynski/capistrano-crono/)).
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
||||||
|
|
||||||
require "crono/cli"
|
require 'crono/cli'
|
||||||
|
|
||||||
begin
|
begin
|
||||||
Crono::CLI.instance.run
|
Crono::CLI.instance.run
|
||||||
|
|||||||
@@ -2,27 +2,31 @@
|
|||||||
require File.expand_path('../lib/crono/version', __FILE__)
|
require File.expand_path('../lib/crono/version', __FILE__)
|
||||||
|
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = "crono"
|
s.name = 'crono'
|
||||||
s.version = Crono::VERSION
|
s.version = Crono::VERSION
|
||||||
s.authors = ["Dzmitry Plashchynski"]
|
s.authors = ['Dzmitry Plashchynski']
|
||||||
s.email = ["plashchynski@gmail.com"]
|
s.email = ['plashchynski@gmail.com']
|
||||||
s.homepage = "https://github.com/plashchynski/crono"
|
s.homepage = 'https://github.com/plashchynski/crono'
|
||||||
s.description = s.summary = "Job scheduler for Rails"
|
s.description = s.summary = 'Job scheduler for Rails'
|
||||||
s.license = "Apache-2.0"
|
s.license = 'Apache-2.0'
|
||||||
|
|
||||||
s.required_rubygems_version = ">= 1.3.6"
|
s.required_rubygems_version = '>= 1.3.6'
|
||||||
s.rubyforge_project = "crono"
|
s.rubyforge_project = 'crono'
|
||||||
|
|
||||||
s.add_runtime_dependency "activejob", "~> 4.0"
|
s.add_runtime_dependency 'activejob', '~> 4.0'
|
||||||
s.add_runtime_dependency "activesupport", "~> 4.0"
|
s.add_runtime_dependency 'activesupport', '~> 4.0'
|
||||||
s.add_runtime_dependency "activerecord", "~> 4.0"
|
s.add_runtime_dependency 'activerecord', '~> 4.0'
|
||||||
s.add_development_dependency "rake", "~> 10.0"
|
s.add_development_dependency 'rake', '~> 10.0'
|
||||||
s.add_development_dependency "bundler", ">= 1.0.0"
|
s.add_development_dependency 'bundler', '>= 1.0.0'
|
||||||
s.add_development_dependency "rspec", "~> 3.0"
|
s.add_development_dependency 'rspec', '~> 3.0'
|
||||||
s.add_development_dependency "timecop", "~> 0.7"
|
s.add_development_dependency 'timecop', '~> 0.7'
|
||||||
s.add_development_dependency "sqlite3"
|
s.add_development_dependency 'sqlite3'
|
||||||
|
s.add_development_dependency 'byebug'
|
||||||
|
s.add_development_dependency 'sinatra'
|
||||||
|
s.add_development_dependency 'haml'
|
||||||
|
s.add_development_dependency 'rack-test'
|
||||||
|
|
||||||
s.files = `git ls-files`.split("\n")
|
s.files = `git ls-files`.split("\n")
|
||||||
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
s.executables = ['crono']
|
||||||
s.require_path = 'lib'
|
s.require_path = 'lib'
|
||||||
end
|
end
|
||||||
|
|||||||
BIN
examples/crono_web_ui.png
Normal file
BIN
examples/crono_web_ui.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
@@ -1,15 +1,14 @@
|
|||||||
# cronotab.rb — Crono configuration example file
|
# cronotab.rb - Crono configuration file
|
||||||
#
|
#
|
||||||
# Here you can specify periodic jobs and their schedule.
|
# Here you can specify periodic jobs and schedule.
|
||||||
# You can specify a periodic job as a ActiveJob class in `app/jobs/`
|
# You can use ActiveJob's jobs from `app/jobs/`
|
||||||
# Actually you can use any class. The only requirement is that
|
# You can use any class. The only requirement is that
|
||||||
# the class should implement a method `perform` without arguments.
|
# class should have a method `perform` without arguments.
|
||||||
#
|
#
|
||||||
|
|
||||||
class TestJob
|
class TestJob
|
||||||
def perform
|
def perform
|
||||||
puts "Test!"
|
puts 'Test!'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Crono.perform(TestJob).every 5.second
|
Crono.perform(TestJob).every 2.days, at: '15:30'
|
||||||
|
|||||||
22
lib/crono.rb
22
lib/crono.rb
@@ -1,12 +1,16 @@
|
|||||||
|
# Crono main module
|
||||||
module Crono
|
module Crono
|
||||||
end
|
end
|
||||||
|
|
||||||
require "active_support/all"
|
require 'active_support/all'
|
||||||
require "crono/version.rb"
|
require 'crono/version'
|
||||||
require "crono/logging.rb"
|
require 'crono/logging'
|
||||||
require "crono/period.rb"
|
require 'crono/period'
|
||||||
require "crono/job.rb"
|
require 'crono/job'
|
||||||
require "crono/scheduler.rb"
|
require 'crono/scheduler'
|
||||||
require "crono/config.rb"
|
require 'crono/config'
|
||||||
require "crono/performer_proxy.rb"
|
require 'crono/performer_proxy'
|
||||||
require "crono/orm/active_record/crono_job.rb"
|
require 'crono/orm/active_record/crono_job'
|
||||||
|
require 'crono/railtie' if defined?(Rails)
|
||||||
|
|
||||||
|
Crono.autoload :Web, 'crono/web'
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ require 'crono'
|
|||||||
require 'optparse'
|
require 'optparse'
|
||||||
|
|
||||||
module Crono
|
module Crono
|
||||||
mattr_accessor :scheduler
|
# Crono::CLI - The main class for the crono daemon exacutable `bin/crono`
|
||||||
|
|
||||||
class CLI
|
class CLI
|
||||||
include Singleton
|
include Singleton
|
||||||
include Logging
|
include Logging
|
||||||
@@ -18,12 +17,7 @@ module Crono
|
|||||||
def run
|
def run
|
||||||
parse_options(ARGV)
|
parse_options(ARGV)
|
||||||
|
|
||||||
if config.daemonize
|
setup_log
|
||||||
set_log_to(config.logfile)
|
|
||||||
daemonize
|
|
||||||
else
|
|
||||||
set_log_to(STDOUT)
|
|
||||||
end
|
|
||||||
|
|
||||||
write_pid
|
write_pid
|
||||||
load_rails
|
load_rails
|
||||||
@@ -33,7 +27,17 @@ module Crono
|
|||||||
start_working_loop
|
start_working_loop
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def setup_log
|
||||||
|
if config.daemonize
|
||||||
|
self.logfile = config.logfile
|
||||||
|
daemonize
|
||||||
|
else
|
||||||
|
self.logfile = STDOUT
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def daemonize
|
def daemonize
|
||||||
::Process.daemon(true, true)
|
::Process.daemon(true, true)
|
||||||
|
|
||||||
@@ -42,7 +46,7 @@ module Crono
|
|||||||
io.sync = true
|
io.sync = true
|
||||||
end
|
end
|
||||||
|
|
||||||
$stdin.reopen("/dev/null")
|
$stdin.reopen('/dev/null')
|
||||||
end
|
end
|
||||||
|
|
||||||
def write_pid
|
def write_pid
|
||||||
@@ -54,31 +58,31 @@ module Crono
|
|||||||
logger.info "Loading Crono #{Crono::VERSION}"
|
logger.info "Loading Crono #{Crono::VERSION}"
|
||||||
logger.info "Running in #{RUBY_DESCRIPTION}"
|
logger.info "Running in #{RUBY_DESCRIPTION}"
|
||||||
|
|
||||||
logger.info "Jobs:"
|
logger.info 'Jobs:'
|
||||||
Crono.scheduler.jobs.each do |job|
|
Crono.scheduler.jobs.each do |job|
|
||||||
logger.info job.description
|
logger.info "'#{job.performer}' with rule '#{job.period.description}'"\
|
||||||
|
"next time will perform at #{job.next}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_rails
|
def load_rails
|
||||||
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = config.environment
|
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = config.environment
|
||||||
require 'rails'
|
require 'rails'
|
||||||
require File.expand_path("config/environment.rb")
|
require File.expand_path('config/environment.rb')
|
||||||
::Rails.application.eager_load!
|
::Rails.application.eager_load!
|
||||||
require File.expand_path(config.cronotab)
|
require File.expand_path(config.cronotab)
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_jobs
|
def check_jobs
|
||||||
if Crono.scheduler.jobs.empty?
|
return if Crono.scheduler.jobs.present?
|
||||||
logger.error "You have no jobs defined in you cronotab file #{config.cronotab}"
|
logger.error "You have no jobs in you cronotab file #{config.cronotab}"
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def start_working_loop
|
def start_working_loop
|
||||||
Thread.abort_on_exception = true
|
while true
|
||||||
while job = Crono.scheduler.next do
|
next_time, jobs = Crono.scheduler.next_jobs
|
||||||
sleep(job.next - Time.now)
|
sleep(next_time - Time.now)
|
||||||
job.perform
|
jobs.each(&:perform)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
module Crono
|
module Crono
|
||||||
|
# Crono::Config stores Crono configuration
|
||||||
class Config
|
class Config
|
||||||
CRONOTAB = "config/cronotab.rb"
|
CRONOTAB = 'config/cronotab.rb'
|
||||||
LOGFILE = "log/crono.log"
|
LOGFILE = 'log/crono.log'
|
||||||
PIDFILE = "tmp/pids/crono.pid"
|
PIDFILE = 'tmp/pids/crono.pid'
|
||||||
|
|
||||||
attr_accessor :cronotab
|
attr_accessor :cronotab, :logfile, :pidfile, :daemonize, :environment
|
||||||
attr_accessor :logfile
|
|
||||||
attr_accessor :pidfile
|
|
||||||
attr_accessor :daemonize
|
|
||||||
attr_accessor :environment
|
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
self.cronotab = CRONOTAB
|
self.cronotab = CRONOTAB
|
||||||
self.logfile = LOGFILE
|
self.logfile = LOGFILE
|
||||||
self.pidfile = PIDFILE
|
self.pidfile = PIDFILE
|
||||||
self.daemonize = false
|
self.daemonize = false
|
||||||
self.environment = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development"
|
self.environment = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,18 +1,23 @@
|
|||||||
|
require 'stringio'
|
||||||
|
require 'logger'
|
||||||
|
|
||||||
module Crono
|
module Crono
|
||||||
|
# Crono::Job represents a Crono job
|
||||||
class Job
|
class Job
|
||||||
include Logging
|
include Logging
|
||||||
|
|
||||||
attr_accessor :performer
|
attr_accessor :performer, :period, :last_performed_at, :job_log,
|
||||||
attr_accessor :period
|
:job_logger, :healthy
|
||||||
attr_accessor :last_performed_at
|
|
||||||
|
|
||||||
def initialize(performer, period)
|
def initialize(performer, period)
|
||||||
self.performer, self.period = performer, period
|
self.performer, self.period = performer, period
|
||||||
|
self.job_log = StringIO.new
|
||||||
|
self.job_logger = Logger.new(job_log)
|
||||||
|
@semaphore = Mutex.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def next
|
def next
|
||||||
next_time = period.next(since: last_performed_at)
|
period.next(since: last_performed_at)
|
||||||
next_time.past? ? period.next : next_time
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def description
|
def description
|
||||||
@@ -24,24 +29,72 @@ module Crono
|
|||||||
end
|
end
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
logger.info "Perform #{performer}"
|
log "Perform #{performer}"
|
||||||
self.last_performed_at = Time.now
|
self.last_performed_at = Time.now
|
||||||
save
|
|
||||||
Thread.new do
|
Thread.new { perform_job }
|
||||||
performer.new.perform
|
|
||||||
logger.info "Finished #{performer} in %.2f seconds" % (Time.now - last_performed_at)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def save
|
def save
|
||||||
model.update(last_performed_at: last_performed_at)
|
@semaphore.synchronize do
|
||||||
|
update_model
|
||||||
|
clear_job_log
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def load
|
def load
|
||||||
self.last_performed_at = model.last_performed_at
|
self.last_performed_at = model.last_performed_at
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def clear_job_log
|
||||||
|
job_log.truncate(job_log.rewind)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_model
|
||||||
|
saved_log = model.reload.log || ''
|
||||||
|
log_to_save = saved_log + job_log.string
|
||||||
|
model.update(last_performed_at: last_performed_at, log: log_to_save,
|
||||||
|
healthy: healthy)
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform_job
|
||||||
|
performer_instance = performer.new
|
||||||
|
performer_instance.instance_variable_set(:@_crono_job, self)
|
||||||
|
performer_instance.perform
|
||||||
|
finished_time_sec = format('%.2f', Time.now - last_performed_at)
|
||||||
|
rescue StandardError => e
|
||||||
|
handle_job_fail(e, finished_time_sec)
|
||||||
|
else
|
||||||
|
handle_job_success(finished_time_sec)
|
||||||
|
ensure
|
||||||
|
save
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_job_fail(exception, finished_time_sec)
|
||||||
|
self.healthy = false
|
||||||
|
log_error "Finished #{performer} in #{finished_time_sec} seconds"\
|
||||||
|
"with error: #{exception.message}"
|
||||||
|
log_error exception.backtrace.join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_job_success(finished_time_sec)
|
||||||
|
self.healthy = true
|
||||||
|
log "Finished #{performer} in #{finished_time_sec} seconds"
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_error(message)
|
||||||
|
log(message, Logger::ERROR)
|
||||||
|
end
|
||||||
|
|
||||||
|
def log(message, severity = Logger::INFO)
|
||||||
|
@semaphore.synchronize do
|
||||||
|
logger.log severity, message
|
||||||
|
job_logger.log severity, message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def model
|
def model
|
||||||
@model ||= Crono::CronoJob.find_or_create_by(job_id: job_id)
|
@model ||= Crono::CronoJob.find_or_create_by(job_id: job_id)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
module Crono
|
module Crono
|
||||||
mattr_accessor :logger
|
mattr_accessor :logger
|
||||||
|
|
||||||
|
# Crono::Logging is a standart Ruby logger wrapper
|
||||||
module Logging
|
module Logging
|
||||||
def set_log_to(logfile)
|
def logfile=(logfile)
|
||||||
Crono.logger = Logger.new(logfile)
|
Crono.logger = Logger.new(logfile)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
require 'active_record'
|
require 'active_record'
|
||||||
|
|
||||||
module Crono
|
module Crono
|
||||||
|
# Crono::CronoJob is a ActiveRecord model to store job state
|
||||||
class CronoJob < ActiveRecord::Base
|
class CronoJob < ActiveRecord::Base
|
||||||
self.table_name = "crono_jobs"
|
self.table_name = 'crono_jobs'
|
||||||
validates :job_id, presence: true, uniqueness: true
|
validates :job_id, presence: true, uniqueness: true
|
||||||
|
|
||||||
|
def self.outdated
|
||||||
|
self
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
module Crono
|
module Crono
|
||||||
|
# Crono::PerformerProxy is a proxy used in cronotab.rb semantic
|
||||||
class PerformerProxy
|
class PerformerProxy
|
||||||
def initialize(performer, scheduler)
|
def initialize(performer, scheduler)
|
||||||
@performer = performer
|
@performer = performer
|
||||||
|
|||||||
@@ -1,22 +1,53 @@
|
|||||||
module Crono
|
module Crono
|
||||||
|
# Period describe frequency of performing a task
|
||||||
class Period
|
class Period
|
||||||
def initialize(period, at: nil)
|
DAYS = [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday,
|
||||||
|
:sunday]
|
||||||
|
|
||||||
|
def initialize(period, at: nil, on: nil)
|
||||||
@period = period
|
@period = period
|
||||||
@at_hour, @at_min = parse_at(at) if at
|
@at_hour, @at_min = parse_at(at) if at
|
||||||
|
@on = parse_on(on) if on
|
||||||
end
|
end
|
||||||
|
|
||||||
def next(since: nil)
|
def next(since: nil)
|
||||||
since ||= Time.now
|
return initial_next unless since
|
||||||
@period.since(since).change({hour: @at_hour, min: @at_min}.compact)
|
@next = @period.since(since)
|
||||||
|
@next = @next.beginning_of_week.advance(days: @on) if @on
|
||||||
|
@next.change(time_atts)
|
||||||
end
|
end
|
||||||
|
|
||||||
def description
|
def description
|
||||||
desc = "every #{@period.inspect}"
|
desc = "every #{@period.inspect}"
|
||||||
desc += " at %.2i:%.2i" % [@at_hour, @at_min] if @at_hour && @at_min
|
desc += format(' at %.2i:%.2i', @at_hour, @at_min) if @at_hour && @at_min
|
||||||
|
desc += " on #{DAYS[@on].capitalize}" if @on
|
||||||
desc
|
desc
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def initial_next
|
||||||
|
next_time = initial_day.change(time_atts)
|
||||||
|
return next_time if next_time.future?
|
||||||
|
@period.from_now.change(time_atts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def initial_day
|
||||||
|
return Time.now unless @on
|
||||||
|
day = Time.now.beginning_of_week.advance(days: @on)
|
||||||
|
return day if day.future?
|
||||||
|
@period.from_now.beginning_of_week.advance(days: @on)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_on(on)
|
||||||
|
day_number = DAYS.index(on)
|
||||||
|
fail "Wrong 'on' day" unless day_number
|
||||||
|
fail "period should be at least 1 week to use 'on'" if @period < 1.week
|
||||||
|
day_number
|
||||||
|
end
|
||||||
|
|
||||||
def parse_at(at)
|
def parse_at(at)
|
||||||
|
fail "period should be at least 1 day to use 'at'" if @period < 1.day
|
||||||
case at
|
case at
|
||||||
when String
|
when String
|
||||||
time = Time.parse(at)
|
time = Time.parse(at)
|
||||||
@@ -24,8 +55,12 @@ module Crono
|
|||||||
when Hash
|
when Hash
|
||||||
return at[:hour], at[:min]
|
return at[:hour], at[:min]
|
||||||
else
|
else
|
||||||
raise "Unknown 'at' format"
|
fail "Unknown 'at' format"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def time_atts
|
||||||
|
{ hour: @at_hour, min: @at_min }.compact
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
9
lib/crono/railtie.rb
Normal file
9
lib/crono/railtie.rb
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
module Crono
|
||||||
|
class Railtie < ::Rails::Railtie
|
||||||
|
rake_tasks do
|
||||||
|
Dir[File.join(File.dirname(__FILE__), '../tasks/*.rake')].each do |file|
|
||||||
|
load file
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
module Crono
|
module Crono
|
||||||
|
# Scheduler is a container for job list and queue
|
||||||
class Scheduler
|
class Scheduler
|
||||||
attr_accessor :jobs
|
attr_accessor :jobs
|
||||||
|
|
||||||
@@ -11,13 +12,10 @@ module Crono
|
|||||||
jobs << job
|
jobs << job
|
||||||
end
|
end
|
||||||
|
|
||||||
def next
|
def next_jobs
|
||||||
queue.first
|
jobs.group_by(&:next).sort_by {|time,_| time }.first
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def queue
|
|
||||||
jobs.sort_by(&:next)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
mattr_accessor :scheduler
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module Crono
|
module Crono
|
||||||
VERSION = '0.6.1'
|
VERSION = '0.8.1'
|
||||||
end
|
end
|
||||||
|
|||||||
22
lib/crono/web.rb
Normal file
22
lib/crono/web.rb
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
require 'haml'
|
||||||
|
require 'sinatra/base'
|
||||||
|
require 'crono'
|
||||||
|
|
||||||
|
module Crono
|
||||||
|
# Web is a Web UI Sinatra app
|
||||||
|
class Web < Sinatra::Base
|
||||||
|
set :root, File.expand_path(File.dirname(__FILE__) + '/../../web')
|
||||||
|
set :public_folder, proc { "#{root}/assets" }
|
||||||
|
set :views, proc { "#{root}/views" }
|
||||||
|
|
||||||
|
get '/' do
|
||||||
|
@jobs = Crono::CronoJob.all
|
||||||
|
haml :dashboard, format: :html5
|
||||||
|
end
|
||||||
|
|
||||||
|
get '/job/:id' do
|
||||||
|
@job = Crono::CronoJob.find(params[:id])
|
||||||
|
haml :job
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -4,6 +4,7 @@ require 'rails/generators/active_record'
|
|||||||
|
|
||||||
module Crono
|
module Crono
|
||||||
module Generators
|
module Generators
|
||||||
|
# rails generate crono:install
|
||||||
class InstallGenerator < ::Rails::Generators::Base
|
class InstallGenerator < ::Rails::Generators::Base
|
||||||
include Rails::Generators::Migration
|
include Rails::Generators::Migration
|
||||||
|
|
||||||
@@ -11,15 +12,16 @@ module Crono
|
|||||||
ActiveRecord::Generators::Base.next_migration_number(path)
|
ActiveRecord::Generators::Base.next_migration_number(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Installs crono and generates the necessary configuration files"
|
desc 'Installs crono and generates the necessary configuration files'
|
||||||
source_root File.expand_path("../templates", __FILE__)
|
source_root File.expand_path('../templates', __FILE__)
|
||||||
|
|
||||||
def copy_config
|
def copy_config
|
||||||
template 'cronotab.rb.erb', 'config/cronotab.rb'
|
template 'cronotab.rb.erb', 'config/cronotab.rb'
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_migrations
|
def create_migrations
|
||||||
migration_template 'migrations/create_crono_jobs.rb', 'db/migrate/create_crono_jobs.rb'
|
migration_template 'migrations/create_crono_jobs.rb',
|
||||||
|
'db/migrate/create_crono_jobs.rb'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
# cronotab.rb — Crono configuration file
|
# cronotab.rb — Crono configuration file
|
||||||
#
|
#
|
||||||
# Here you can specify periodic jobs and their schedule.
|
# Here you can specify periodic jobs and schedule.
|
||||||
# You can specify a periodic job as a ActiveJob class in `app/jobs/`
|
# You can use ActiveJob's jobs from `app/jobs/`
|
||||||
# Actually you can use any class. The only requirement is that
|
# You can use any class. The only requirement is that
|
||||||
# the class should implement a method `perform` without arguments.
|
# class should have a method `perform` without arguments.
|
||||||
#
|
#
|
||||||
# Crono.perform(TestJob).every 2.days, at: "15:30"
|
# class TestJob
|
||||||
|
# def perform
|
||||||
|
# puts 'Test!'
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Crono.perform(TestJob).every 2.days, at: '15:30'
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ class CreateCronoJobs < ActiveRecord::Migration
|
|||||||
t.string :job_id, null: false
|
t.string :job_id, null: false
|
||||||
t.text :log
|
t.text :log
|
||||||
t.datetime :last_performed_at
|
t.datetime :last_performed_at
|
||||||
t.timestamps
|
t.boolean :healthy
|
||||||
|
t.timestamps null: false
|
||||||
end
|
end
|
||||||
add_index :crono_jobs, [:job_id], unique: true
|
add_index :crono_jobs, [:job_id], unique: true
|
||||||
end
|
end
|
||||||
|
|||||||
26
lib/tasks/crono_tasks.rake
Normal file
26
lib/tasks/crono_tasks.rake
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
module Crono
|
||||||
|
def self.load_cronotab
|
||||||
|
cronotab_path = ENV['CRONOTAB'] || (defined?(Rails) &&
|
||||||
|
File.join(Rails.root, cronotab_path))
|
||||||
|
fail 'No cronotab defined' unless cronotab_path
|
||||||
|
puts "Load cronotab #{cronotab_path}"
|
||||||
|
require cronotab_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
namespace :crono do
|
||||||
|
desc 'Clean unused job stats from DB'
|
||||||
|
task clean: :environment do
|
||||||
|
Crono.scheduler = Crono::Scheduler.new
|
||||||
|
Crono.load_cronotab
|
||||||
|
current_job_ids = Crono.scheduler.jobs.map(&:job_id)
|
||||||
|
Crono::CronoJob.where.not(job_id: current_job_ids).destroy_all
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Check cronotab.rb syntax'
|
||||||
|
task check: :environment do
|
||||||
|
Crono.scheduler = Crono::Scheduler.new
|
||||||
|
Crono.load_cronotab
|
||||||
|
puts 'Syntax ok'
|
||||||
|
end
|
||||||
|
end
|
||||||
12
spec/assets/bad_cronotab.rb
Normal file
12
spec/assets/bad_cronotab.rb
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# This is an example of a bad cronotab for tests
|
||||||
|
|
||||||
|
class TestJob
|
||||||
|
def perform
|
||||||
|
puts 'Test!'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This is an error, because you can use `on` options with
|
||||||
|
# a period less than 7 days.
|
||||||
|
|
||||||
|
Crono.perform(TestJob).every 5.days, on: :sunday
|
||||||
9
spec/assets/good_cronotab.rb
Normal file
9
spec/assets/good_cronotab.rb
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# This is an example of a good cronotab for tests
|
||||||
|
|
||||||
|
class TestJob
|
||||||
|
def perform
|
||||||
|
puts 'Test!'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Crono.perform(TestJob).every 5.seconds
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
require "spec_helper"
|
require 'spec_helper'
|
||||||
require 'crono/cli'
|
require 'crono/cli'
|
||||||
|
|
||||||
class TestJob
|
|
||||||
def perform;end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe Crono::CLI do
|
describe Crono::CLI do
|
||||||
let(:cli) { Crono::CLI.instance }
|
let(:cli) { Crono::CLI.instance }
|
||||||
|
|
||||||
describe "#run" do
|
describe '#run' do
|
||||||
it "should try to initialize rails with #load_rails and start working loop" do
|
it 'should initialize rails with #load_rails and start working loop' do
|
||||||
expect(cli).to receive(:load_rails)
|
expect(cli).to receive(:load_rails)
|
||||||
expect(cli).to receive(:start_working_loop)
|
expect(cli).to receive(:start_working_loop)
|
||||||
expect(cli).to receive(:parse_options)
|
expect(cli).to receive(:parse_options)
|
||||||
@@ -18,34 +14,30 @@ describe Crono::CLI do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#start_working_loop" do
|
describe '#parse_options' do
|
||||||
it "should start working loop"
|
it 'should set cronotab' do
|
||||||
end
|
cli.send(:parse_options, ['--cronotab', '/tmp/cronotab.rb'])
|
||||||
|
expect(cli.config.cronotab).to be_eql '/tmp/cronotab.rb'
|
||||||
describe "#parse_options" do
|
|
||||||
it "should set cronotab" do
|
|
||||||
cli.send(:parse_options, ["--cronotab", "/tmp/cronotab.rb"])
|
|
||||||
expect(cli.config.cronotab).to be_eql "/tmp/cronotab.rb"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should set logfile" do
|
it 'should set logfile' do
|
||||||
cli.send(:parse_options, ["--logfile", "log/crono.log"])
|
cli.send(:parse_options, ['--logfile', 'log/crono.log'])
|
||||||
expect(cli.config.logfile).to be_eql "log/crono.log"
|
expect(cli.config.logfile).to be_eql 'log/crono.log'
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should set pidfile" do
|
it 'should set pidfile' do
|
||||||
cli.send(:parse_options, ["--pidfile", "tmp/pids/crono.0.log"])
|
cli.send(:parse_options, ['--pidfile', 'tmp/pids/crono.0.log'])
|
||||||
expect(cli.config.pidfile).to be_eql "tmp/pids/crono.0.log"
|
expect(cli.config.pidfile).to be_eql 'tmp/pids/crono.0.log'
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should set daemonize" do
|
it 'should set daemonize' do
|
||||||
cli.send(:parse_options, ["--daemonize"])
|
cli.send(:parse_options, ['--daemonize'])
|
||||||
expect(cli.config.daemonize).to be true
|
expect(cli.config.daemonize).to be true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should set environment" do
|
it 'should set environment' do
|
||||||
cli.send(:parse_options, ["--environment", "production"])
|
cli.send(:parse_options, ['--environment', 'production'])
|
||||||
expect(cli.config.environment).to be_eql("production")
|
expect(cli.config.environment).to be_eql('production')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
require "spec_helper"
|
require 'spec_helper'
|
||||||
|
|
||||||
describe Crono::Config do
|
describe Crono::Config do
|
||||||
describe "#initialize" do
|
describe '#initialize' do
|
||||||
it "should initialize with default configuration options" do
|
it 'should initialize with default configuration options' do
|
||||||
ENV["RAILS_ENV"] = "test"
|
ENV['RAILS_ENV'] = 'test'
|
||||||
@config = Crono::Config.new
|
@config = Crono::Config.new
|
||||||
expect(@config.cronotab).to be Crono::Config::CRONOTAB
|
expect(@config.cronotab).to be Crono::Config::CRONOTAB
|
||||||
expect(@config.logfile).to be Crono::Config::LOGFILE
|
expect(@config.logfile).to be Crono::Config::LOGFILE
|
||||||
expect(@config.pidfile).to be Crono::Config::PIDFILE
|
expect(@config.pidfile).to be Crono::Config::PIDFILE
|
||||||
expect(@config.daemonize).to be false
|
expect(@config.daemonize).to be false
|
||||||
expect(@config.environment).to be_eql ENV["RAILS_ENV"]
|
expect(@config.environment).to be_eql ENV['RAILS_ENV']
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
104
spec/job_spec.rb
104
spec/job_spec.rb
@@ -1,62 +1,120 @@
|
|||||||
require "spec_helper"
|
require 'spec_helper'
|
||||||
|
|
||||||
class TestJob
|
|
||||||
def perform;end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe Crono::Job do
|
describe Crono::Job do
|
||||||
let(:period) { Crono::Period.new(2.day) }
|
let(:period) { Crono::Period.new(2.day, at: '15:00') }
|
||||||
let(:job) { Crono::Job.new(TestJob, period) }
|
let(:job) { Crono::Job.new(TestJob, period) }
|
||||||
|
let(:failing_job) { Crono::Job.new(TestFailingJob, period) }
|
||||||
|
|
||||||
it "should contain performer and period" do
|
it 'should contain performer and period' do
|
||||||
expect(job.performer).to be TestJob
|
expect(job.performer).to be TestJob
|
||||||
expect(job.period).to be period
|
expect(job.period).to be period
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#perform" do
|
describe '#next' do
|
||||||
it "should run performer in separate thread" do
|
it 'should return next performing time according to period' do
|
||||||
|
expect(job.next).to be_eql period.next
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
after { job.send(:model).destroy }
|
||||||
|
|
||||||
|
it 'should run performer in separate thread' do
|
||||||
|
expect(job).to receive(:save)
|
||||||
thread = job.perform.join
|
thread = job.perform.join
|
||||||
expect(thread).to be_stop
|
expect(thread).to be_stop
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should call Job#save after run" do
|
it 'should save performin errors to log' do
|
||||||
expect(job).to receive(:save)
|
thread = failing_job.perform.join
|
||||||
|
expect(thread).to be_stop
|
||||||
|
saved_log = Crono::CronoJob.find_by(job_id: failing_job.job_id).log
|
||||||
|
expect(saved_log).to include 'Some error'
|
||||||
|
end
|
||||||
|
|
||||||
|
xit 'should set Job#healthy to true if perform ok' do
|
||||||
|
class TestJob
|
||||||
|
def perform
|
||||||
|
@_crono_job
|
||||||
|
end
|
||||||
|
end
|
||||||
job.perform.join
|
job.perform.join
|
||||||
job.send(:model).destroy
|
end
|
||||||
|
|
||||||
|
it 'should set Job#healthy to false if perform with error' do
|
||||||
|
failing_job.perform.join
|
||||||
|
expect(failing_job.healthy).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
xit 'should set @_crono_job variable to instance' do
|
||||||
|
job.perform
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#description" do
|
describe '#description' do
|
||||||
it "should return job identificator" do
|
it 'should return job identificator' do
|
||||||
expect(job.description).to be_eql("Perform TestJob every 2 days")
|
expect(job.description).to be_eql('Perform TestJob every 2 days at 15:00')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#save" do
|
describe '#save' do
|
||||||
it "should save new job to DB" do
|
it 'should save new job to DB' do
|
||||||
expect(Crono::CronoJob.where(job_id: job.job_id)).to_not exist
|
expect(Crono::CronoJob.where(job_id: job.job_id)).to_not exist
|
||||||
job.save
|
job.save
|
||||||
expect(Crono::CronoJob.where(job_id: job.job_id)).to exist
|
expect(Crono::CronoJob.where(job_id: job.job_id)).to exist
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should update saved job" do
|
it 'should update saved job' do
|
||||||
job.last_performed_at = Time.now
|
job.last_performed_at = Time.now
|
||||||
|
job.healthy = true
|
||||||
job.save
|
job.save
|
||||||
@crono_job = Crono::CronoJob.find_by(job_id: job.job_id)
|
@crono_job = Crono::CronoJob.find_by(job_id: job.job_id)
|
||||||
expect(@crono_job.last_performed_at).to be_eql(job.last_performed_at)
|
expect(@crono_job.last_performed_at.utc.to_s).to be_eql job.last_performed_at.utc.to_s
|
||||||
|
expect(@crono_job.healthy).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should save and truncate job log' do
|
||||||
|
message = 'test message'
|
||||||
|
job.send(:log, message)
|
||||||
|
job.save
|
||||||
|
expect(job.send(:model).reload.log).to include message
|
||||||
|
expect(job.job_log.string).to be_empty
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#load" do
|
describe '#load' do
|
||||||
before do
|
before do
|
||||||
@saved_last_performed_at = job.last_performed_at = Time.now
|
@saved_last_performed_at = job.last_performed_at = Time.now
|
||||||
job.save
|
job.save
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should load info from DB" do
|
it 'should load last_performed_at from DB' do
|
||||||
@job = Crono::Job.new(TestJob, period)
|
@job = Crono::Job.new(TestJob, period)
|
||||||
@job.load
|
@job.load
|
||||||
expect(@job.last_performed_at).to be_eql @saved_last_performed_at
|
expect(@job.last_performed_at.utc.to_s).to be_eql @saved_last_performed_at.utc.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#log' do
|
||||||
|
it 'should write log messages to both common and job log' do
|
||||||
|
message = 'Test message'
|
||||||
|
expect(job.logger).to receive(:log).with(Logger::INFO, message)
|
||||||
|
expect(job.job_logger).to receive(:log).with(Logger::INFO, message)
|
||||||
|
job.send(:log, message)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should write job log to Job#job_log' do
|
||||||
|
message = 'Test message'
|
||||||
|
job.send(:log, message)
|
||||||
|
expect(job.job_log.string).to include(message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#log_error' do
|
||||||
|
it 'should call log with ERROR severity' do
|
||||||
|
message = 'Test message'
|
||||||
|
expect(job).to receive(:log).with(message, Logger::ERROR)
|
||||||
|
job.send(:log_error, message)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
require "spec_helper"
|
require 'spec_helper'
|
||||||
|
|
||||||
describe Crono::CronoJob do
|
describe Crono::CronoJob do
|
||||||
let(:valid_attrs) do
|
let(:valid_attrs) do
|
||||||
{
|
{
|
||||||
job_id: "Perform TestJob every 3 days"
|
job_id: 'Perform TestJob every 3 days'
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should validate presence of job_id" do
|
it 'should validate presence of job_id' do
|
||||||
@crono_job = Crono::CronoJob.new()
|
@crono_job = Crono::CronoJob.new
|
||||||
expect(@crono_job).not_to be_valid
|
expect(@crono_job).not_to be_valid
|
||||||
expect(@crono_job.errors.added?(:job_id, :blank)).to be true
|
expect(@crono_job.errors.added?(:job_id, :blank)).to be true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should validate uniqueness of job_id" do
|
it 'should validate uniqueness of job_id' do
|
||||||
Crono::CronoJob.create!(job_id: "TestJob every 2 days")
|
Crono::CronoJob.create!(job_id: 'TestJob every 2 days')
|
||||||
@crono_job = Crono::CronoJob.create(job_id: "TestJob every 2 days")
|
@crono_job = Crono::CronoJob.create(job_id: 'TestJob every 2 days')
|
||||||
expect(@crono_job).not_to be_valid
|
expect(@crono_job).not_to be_valid
|
||||||
expect(@crono_job.errors.added?(:job_id, :taken)).to be true
|
expect(@crono_job.errors.added?(:job_id, :taken)).to be true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should save job_id to DB" do
|
it 'should save job_id to DB' do
|
||||||
Crono::CronoJob.create!(valid_attrs)
|
Crono::CronoJob.create!(valid_attrs)
|
||||||
@crono_job = Crono::CronoJob.find_by(job_id: valid_attrs[:job_id])
|
@crono_job = Crono::CronoJob.find_by(job_id: valid_attrs[:job_id])
|
||||||
expect(@crono_job).to be_present
|
expect(@crono_job).to be_present
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
require "spec_helper"
|
require 'spec_helper'
|
||||||
|
|
||||||
class TestJob
|
|
||||||
def perform;end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe Crono::PerformerProxy do
|
describe Crono::PerformerProxy do
|
||||||
it "should add job to schedule" do
|
it 'should add job to schedule' do
|
||||||
expect(Crono.scheduler).to receive(:add_job).with(kind_of(Crono::Job))
|
expect(Crono.scheduler).to receive(:add_job).with(kind_of(Crono::Job))
|
||||||
Crono.perform(TestJob).every(2.days, at: "15:30")
|
Crono.perform(TestJob).every(2.days, at: '15:30')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
require "spec_helper"
|
require 'spec_helper'
|
||||||
|
|
||||||
describe Crono::Period do
|
describe Crono::Period do
|
||||||
around(:each) do |example|
|
around(:each) do |example|
|
||||||
@@ -7,41 +7,94 @@ describe Crono::Period do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#description" do
|
describe '#description' do
|
||||||
it "should return period description" do
|
it 'should return period description' do
|
||||||
@period = Crono::Period.new(2.day, at: "15:20")
|
@period = Crono::Period.new(1.week, on: :monday, at: '15:20')
|
||||||
expect(@period.description).to be_eql("every 2 days at 15:20")
|
expect(@period.description).to be_eql('every 7 days at 15:20 on Monday')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#next" do
|
describe '#next' do
|
||||||
context "in daily basis" do
|
context 'in weakly basis' do
|
||||||
it "should return the time 2 days from now" do
|
it "should raise error if 'on' is wrong" do
|
||||||
|
expect { @period = Crono::Period.new(7.days, on: :bad_day) }
|
||||||
|
.to raise_error("Wrong 'on' day")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should raise error when period is less than 1 week' do
|
||||||
|
expect { @period = Crono::Period.new(6.days, on: :monday) }
|
||||||
|
.to raise_error("period should be at least 1 week to use 'on'")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return a 'on' day" do
|
||||||
|
@period = Crono::Period.new(1.week, on: :thursday, at: '15:30')
|
||||||
|
current_week = Time.now.beginning_of_week
|
||||||
|
last_run_time = current_week.advance(days: 1) # last run on the tuesday
|
||||||
|
next_run_at = Time.now.next_week.advance(days: 3)
|
||||||
|
.change(hour: 15, min: 30)
|
||||||
|
expect(@period.next(since: last_run_time)).to be_eql(next_run_at)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return a next week day 'on'" do
|
||||||
|
@period = Crono::Period.new(1.week, on: :thursday)
|
||||||
|
Timecop.freeze(Time.now.beginning_of_week.advance(days: 4)) do
|
||||||
|
expect(@period.next).to be_eql(Time.now.next_week.advance(days: 3))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return a current week day on the first run if not too late' do
|
||||||
|
@period = Crono::Period.new(7.days, on: :tuesday)
|
||||||
|
beginning_of_the_week = Time.now.beginning_of_week
|
||||||
|
tuesday = beginning_of_the_week.advance(days: 1)
|
||||||
|
Timecop.freeze(beginning_of_the_week) do
|
||||||
|
expect(@period.next).to be_eql(tuesday)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'in daily basis' do
|
||||||
|
it 'should return the time 2 days from now' do
|
||||||
@period = Crono::Period.new(2.day)
|
@period = Crono::Period.new(2.day)
|
||||||
expect(@period.next).to be_eql(2.day.from_now)
|
expect(@period.next).to be_eql(2.day.from_now)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should set time to 'at' time as a string" do
|
it "should set time to 'at' time as a string" do
|
||||||
@period = Crono::Period.new(2.day, at: "15:20")
|
time = 10.minutes.ago
|
||||||
expect(@period.next).to be_eql(2.day.from_now.change(hour: 15, min: 20))
|
at = [time.hour, time.min].join(':')
|
||||||
|
@period = Crono::Period.new(2.day, at: at)
|
||||||
|
expect(@period.next).to be_eql(2.day.from_now.change(hour: time.hour, min: time.min))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should set time to 'at' time as a hash" do
|
it "should set time to 'at' time as a hash" do
|
||||||
at = {hour: 18, min: 45}
|
time = 10.minutes.ago
|
||||||
|
at = { hour: time.hour, min: time.min }
|
||||||
@period = Crono::Period.new(2.day, at: at)
|
@period = Crono::Period.new(2.day, at: at)
|
||||||
expect(@period.next).to be_eql(2.day.from_now.change(at))
|
expect(@period.next).to be_eql(2.day.from_now.change(at))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should raise error when 'at' is wrong" do
|
it "should raise error when 'at' is wrong" do
|
||||||
expect {
|
expect {
|
||||||
@period = Crono::Period.new(2.day, at: 1)
|
Crono::Period.new(2.day, at: 1)
|
||||||
}.to raise_error("Unknown 'at' format")
|
}.to raise_error("Unknown 'at' format")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return time in relation to last time" do
|
it 'should raise error when period is less than 1 day' do
|
||||||
|
expect {
|
||||||
|
Crono::Period.new(5.hours, at: '15:30')
|
||||||
|
}.to raise_error("period should be at least 1 day to use 'at'")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return time in relation to last time' do
|
||||||
@period = Crono::Period.new(2.day)
|
@period = Crono::Period.new(2.day)
|
||||||
expect(@period.next(since: 1.day.ago)).to be_eql(1.day.from_now)
|
expect(@period.next(since: 1.day.ago)).to be_eql(1.day.from_now)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'should return today time if it is first run and not too late' do
|
||||||
|
time = 10.minutes.from_now
|
||||||
|
at = { hour: time.hour, min: time.min }
|
||||||
|
@period = Crono::Period.new(2.day, at: at)
|
||||||
|
expect(@period.next.utc.to_s).to be_eql(Time.now.change(at).utc.to_s)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,35 +1,38 @@
|
|||||||
require "spec_helper"
|
require 'spec_helper'
|
||||||
|
|
||||||
class TestJob
|
|
||||||
def perform;end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe Crono::Scheduler do
|
describe Crono::Scheduler do
|
||||||
before(:each) do
|
let(:scheduler) { Crono::Scheduler.new }
|
||||||
@scheduler = Crono::Scheduler.new
|
|
||||||
@jobs = [
|
|
||||||
Crono::Period.new(3.day, at: "18:55"),
|
|
||||||
Crono::Period.new(1.day, at: "15:30"),
|
|
||||||
Crono::Period.new(7.day, at: "06:05")
|
|
||||||
].map { |period| Crono::Job.new(TestJob, period) }
|
|
||||||
@scheduler.jobs = @jobs
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "#add_job" do
|
describe '#add_job' do
|
||||||
it "should call Job#load on Job" do
|
it 'should call Job#load on Job' do
|
||||||
@job = Crono::Job.new(TestJob, Crono::Period.new(10.day, at: "04:05"))
|
@job = Crono::Job.new(TestJob, Crono::Period.new(10.day, at: '04:05'))
|
||||||
expect(@job).to receive(:load)
|
expect(@job).to receive(:load)
|
||||||
@scheduler.add_job(@job)
|
scheduler.add_job(@job)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#next" do
|
describe '#next_jobs' do
|
||||||
it "should return next job in schedule" do
|
it 'should return next job in schedule' do
|
||||||
expect(@scheduler.next).to be @jobs[1]
|
scheduler.jobs = jobs = [
|
||||||
|
Crono::Period.new(3.days, at: 10.minutes.from_now.strftime('%H:%M')),
|
||||||
|
Crono::Period.new(1.day, at: 20.minutes.from_now.strftime('%H:%M')),
|
||||||
|
Crono::Period.new(7.days, at: 40.minutes.from_now.strftime('%H:%M'))
|
||||||
|
].map { |period| Crono::Job.new(TestJob, period) }
|
||||||
|
|
||||||
|
time, jobs = scheduler.next_jobs
|
||||||
|
expect(jobs).to be_eql [jobs[0]]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return next based on last" do
|
it 'should return an array of jobs scheduled at same time' do
|
||||||
expect(@scheduler.next)
|
time = 5.minutes.from_now
|
||||||
|
scheduler.jobs = jobs = [
|
||||||
|
Crono::Period.new(1.day, at: time.strftime('%H:%M')),
|
||||||
|
Crono::Period.new(1.day, at: time.strftime('%H:%M')),
|
||||||
|
Crono::Period.new(1.day, at: 10.minutes.from_now.strftime('%H:%M'))
|
||||||
|
].map { |period| Crono::Job.new(TestJob, period) }
|
||||||
|
|
||||||
|
time, jobs = scheduler.next_jobs
|
||||||
|
expect(jobs).to be_eql [jobs[0], jobs[1]]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,12 +2,25 @@ require 'bundler/setup'
|
|||||||
Bundler.setup
|
Bundler.setup
|
||||||
|
|
||||||
require 'timecop'
|
require 'timecop'
|
||||||
|
require 'byebug'
|
||||||
require 'crono'
|
require 'crono'
|
||||||
require 'generators/crono/install/templates/migrations/create_crono_jobs.rb'
|
require 'generators/crono/install/templates/migrations/create_crono_jobs.rb'
|
||||||
|
|
||||||
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
ActiveRecord::Base.establish_connection(
|
||||||
|
adapter: 'sqlite3',
|
||||||
|
database: 'file::memory:?cache=shared'
|
||||||
|
)
|
||||||
|
|
||||||
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
||||||
CreateCronoJobs.up
|
CreateCronoJobs.up
|
||||||
|
|
||||||
RSpec.configure do |config|
|
class TestJob
|
||||||
|
def perform
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TestFailingJob
|
||||||
|
def perform
|
||||||
|
fail 'Some error'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
23
spec/tasks/crono_tasks_spec.rb
Normal file
23
spec/tasks/crono_tasks_spec.rb
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
require 'rake'
|
||||||
|
|
||||||
|
load 'tasks/crono_tasks.rake'
|
||||||
|
Rake::Task.define_task(:environment)
|
||||||
|
|
||||||
|
describe 'rake' do
|
||||||
|
describe 'crono:clean' do
|
||||||
|
it 'should clean unused tasks from DB' do
|
||||||
|
Crono::CronoJob.create!(job_id: 'used_job')
|
||||||
|
ENV['CRONOTAB'] = File.expand_path('../../assets/good_cronotab.rb', __FILE__)
|
||||||
|
Rake::Task['crono:clean'].invoke
|
||||||
|
expect(Crono::CronoJob.where(job_id: 'used_job')).not_to exist
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'crono:check' do
|
||||||
|
it 'should check cronotab syntax' do
|
||||||
|
ENV['CRONOTAB'] = File.expand_path('../../assets/bad_cronotab.rb', __FILE__)
|
||||||
|
expect { Rake::Task['crono:check'].invoke }.to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
49
spec/web_spec.rb
Normal file
49
spec/web_spec.rb
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
require 'rack/test'
|
||||||
|
include Rack::Test::Methods
|
||||||
|
|
||||||
|
describe Crono::Web do
|
||||||
|
let(:app) { Crono::Web }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Crono::CronoJob.destroy_all
|
||||||
|
@test_job_id = 'Perform TestJob every 5 seconds'
|
||||||
|
@test_job_log = 'All runs ok'
|
||||||
|
@test_job = Crono::CronoJob.create!(
|
||||||
|
job_id: @test_job_id,
|
||||||
|
log: @test_job_log
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
after { @test_job.destroy }
|
||||||
|
|
||||||
|
describe '/' do
|
||||||
|
it 'should show all jobs' do
|
||||||
|
get '/'
|
||||||
|
expect(last_response).to be_ok
|
||||||
|
expect(last_response.body).to include @test_job_id
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should show a error mark when a job is unhealthy' do
|
||||||
|
@test_job.update(healthy: false)
|
||||||
|
get '/'
|
||||||
|
expect(last_response.body).to include 'Error'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '/job/:id' do
|
||||||
|
it 'should show job log' do
|
||||||
|
get "/job/#{@test_job.id}"
|
||||||
|
expect(last_response).to be_ok
|
||||||
|
expect(last_response.body).to include @test_job_id
|
||||||
|
expect(last_response.body).to include @test_job_log
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should show a message about the unhealthy job' do
|
||||||
|
message = 'An error occurs during the last execution of this job'
|
||||||
|
@test_job.update(healthy: false)
|
||||||
|
get "/job/#{@test_job.id}"
|
||||||
|
expect(last_response.body).to include message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
0
tmp/.gitkeep
Normal file
0
tmp/.gitkeep
Normal file
20
web/assets/custom.css
Normal file
20
web/assets/custom.css
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.container {
|
||||||
|
background-color: #CFD8DC;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #455A64;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
border-bottom: 1px solid #B6B6B6;
|
||||||
|
}
|
||||||
|
|
||||||
|
#job_list td,#job_list th {
|
||||||
|
border-top: 1px solid #B6B6B6;
|
||||||
|
color: #212121;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
}
|
||||||
22
web/views/dashboard.haml
Normal file
22
web/views/dashboard.haml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
%ol.breadcrumb
|
||||||
|
%li.active Home
|
||||||
|
|
||||||
|
%h3 Running Jobs
|
||||||
|
%table.table#job_list
|
||||||
|
%tr
|
||||||
|
%th Job
|
||||||
|
%th Last performed at
|
||||||
|
%th
|
||||||
|
%th
|
||||||
|
- @jobs.each do |job|
|
||||||
|
%tr
|
||||||
|
%td= job.job_id
|
||||||
|
%td= job.last_performed_at || '-'
|
||||||
|
%td
|
||||||
|
- if job.healthy == false
|
||||||
|
%a{ href: url("/job/#{job.id}") }
|
||||||
|
%span.label.label-danger Error
|
||||||
|
%td
|
||||||
|
%a{ href: url("/job/#{job.id}") }
|
||||||
|
Log
|
||||||
|
%span.glyphicon.glyphicon-menu-right
|
||||||
14
web/views/job.haml
Normal file
14
web/views/job.haml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
%ol.breadcrumb
|
||||||
|
%li
|
||||||
|
%a{ href: url('/') } Home
|
||||||
|
%li.active= @job.job_id
|
||||||
|
|
||||||
|
%h2
|
||||||
|
"#{@job.job_id}" Log:
|
||||||
|
|
||||||
|
- if @job.healthy == false
|
||||||
|
.alert.alert-danger{ role: 'alert' }
|
||||||
|
An error occurs during the last execution of this job.
|
||||||
|
Check the log below for details.
|
||||||
|
|
||||||
|
%pre= @job.log
|
||||||
25
web/views/layout.haml
Normal file
25
web/views/layout.haml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
!!! 5
|
||||||
|
%html{ lang: 'en' }
|
||||||
|
%head
|
||||||
|
%meta{ charset: 'utf-8' }
|
||||||
|
%meta{ 'http-equiv' => 'X-UA-Compatible', content: 'IE=edge' }
|
||||||
|
%meta{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
|
||||||
|
|
||||||
|
%title Crono Dashboard
|
||||||
|
|
||||||
|
%link{ href: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css', rel: 'stylesheet' }
|
||||||
|
%link{ href: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css', rel: 'stylesheet' }
|
||||||
|
%link{ href: "#{env['SCRIPT_NAME']}/custom.css", rel: 'stylesheet' }
|
||||||
|
|
||||||
|
%body
|
||||||
|
%br
|
||||||
|
%br
|
||||||
|
.container
|
||||||
|
.page-header
|
||||||
|
%h1
|
||||||
|
Crono #{Crono::VERSION}
|
||||||
|
%small Dashboard
|
||||||
|
= yield
|
||||||
|
|
||||||
|
%script{ src: 'https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js' }
|
||||||
|
%script{ src: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js' }
|
||||||
Reference in New Issue
Block a user