bumbleworks-api 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +25 -0
- data/Rakefile +9 -0
- data/bumbleworks-api.gemspec +32 -0
- data/config.ru +13 -0
- data/lib/bumbleworks/api.rb +15 -0
- data/lib/bumbleworks/api/config/application.rb +10 -0
- data/lib/bumbleworks/api/config/routes.rb +34 -0
- data/lib/bumbleworks/api/controllers/application_controller.rb +6 -0
- data/lib/bumbleworks/api/controllers/entities_controller.rb +25 -0
- data/lib/bumbleworks/api/controllers/errors_controller.rb +32 -0
- data/lib/bumbleworks/api/controllers/expressions_controller.rb +29 -0
- data/lib/bumbleworks/api/controllers/processes_controller.rb +29 -0
- data/lib/bumbleworks/api/controllers/tasks_controller.rb +43 -0
- data/lib/bumbleworks/api/controllers/trackers_controller.rb +13 -0
- data/lib/bumbleworks/api/controllers/workers_controller.rb +51 -0
- data/lib/bumbleworks/api/lib/presenter.rb +32 -0
- data/lib/bumbleworks/api/lib/presenters/entity_class_presenter.rb +19 -0
- data/lib/bumbleworks/api/lib/presenters/entity_presenter.rb +13 -0
- data/lib/bumbleworks/api/lib/presenters/error_presenter.rb +15 -0
- data/lib/bumbleworks/api/lib/presenters/expression_presenter.rb +17 -0
- data/lib/bumbleworks/api/lib/presenters/process_presenter.rb +32 -0
- data/lib/bumbleworks/api/lib/presenters/task_presenter.rb +15 -0
- data/lib/bumbleworks/api/lib/presenters/tracker_presenter.rb +16 -0
- data/lib/bumbleworks/api/lib/presenters/worker_presenter.rb +30 -0
- data/lib/bumbleworks/api/lib/time_support.rb +23 -0
- data/lib/bumbleworks/api/version.rb +5 -0
- data/playground_setup.rb +30 -0
- data/spec/controllers/entities_controller_spec.rb +32 -0
- data/spec/controllers/errors_controller_spec.rb +42 -0
- data/spec/controllers/expressions_controller_spec.rb +40 -0
- data/spec/controllers/processes_controller_spec.rb +50 -0
- data/spec/controllers/tasks_controller_spec.rb +82 -0
- data/spec/controllers/trackers_controller_spec.rb +25 -0
- data/spec/controllers/workers_controller_spec.rb +113 -0
- data/spec/fixtures/bumbleworks_config.rb +10 -0
- data/spec/fixtures/entities/mock_entity.rb +38 -0
- data/spec/fixtures/entities/widget.rb +9 -0
- data/spec/fixtures/entities/widgety_fidget.rb +5 -0
- data/spec/fixtures/participants.rb +3 -0
- data/spec/fixtures/participants/naughty_participant.rb +16 -0
- data/spec/fixtures/processes/error_process.rb +3 -0
- data/spec/fixtures/processes/task_process.rb +9 -0
- data/spec/fixtures/processes/waiting_process.rb +8 -0
- data/spec/lib/presenter_spec.rb +29 -0
- data/spec/lib/presenters/entity_class_presenter_spec.rb +17 -0
- data/spec/lib/presenters/entity_presenter_spec.rb +15 -0
- data/spec/lib/presenters/error_presenter_spec.rb +18 -0
- data/spec/lib/presenters/expression_presenter_spec.rb +33 -0
- data/spec/lib/presenters/process_presenter_spec.rb +42 -0
- data/spec/lib/presenters/task_presenter_spec.rb +18 -0
- data/spec/lib/presenters/tracker_presenter_spec.rb +18 -0
- data/spec/lib/presenters/worker_presenter_spec.rb +30 -0
- data/spec/lib/time_support_spec.rb +42 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/api_helper.rb +23 -0
- data/spec/support/process_helpers.rb +11 -0
- metadata +261 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
module Bumbleworks
|
2
|
+
module Api
|
3
|
+
class Presenter
|
4
|
+
attr_reader :presented
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def present(presented)
|
8
|
+
if presented.is_a?(Array)
|
9
|
+
from_array(presented)
|
10
|
+
else
|
11
|
+
new(presented)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def from_array(array)
|
16
|
+
array.map { |presented|
|
17
|
+
new(presented, in_collection: true)
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(presented, in_collection: false)
|
23
|
+
@presented = presented
|
24
|
+
@in_collection = in_collection
|
25
|
+
end
|
26
|
+
|
27
|
+
def in_collection?
|
28
|
+
@in_collection == true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Bumbleworks
|
2
|
+
module Api
|
3
|
+
class EntityClassPresenter < Presenter
|
4
|
+
def to_hash
|
5
|
+
{
|
6
|
+
:class => presented.name,
|
7
|
+
:count => presented.count,
|
8
|
+
:registered_processes => registered_processes
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def registered_processes
|
13
|
+
presented.processes.map { |name, attributes|
|
14
|
+
attributes.merge(:name => name)
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Bumbleworks
|
2
|
+
module Api
|
3
|
+
class ErrorPresenter < Presenter
|
4
|
+
def to_hash
|
5
|
+
{
|
6
|
+
:process_id => presented.wfid,
|
7
|
+
:expression_id => presented.expression.expid,
|
8
|
+
:error_class_name => presented.error_class_name,
|
9
|
+
:message => presented.message,
|
10
|
+
:backtrace => presented.backtrace
|
11
|
+
}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Bumbleworks
|
2
|
+
module Api
|
3
|
+
class ExpressionPresenter < Presenter
|
4
|
+
def to_hash
|
5
|
+
hash = {
|
6
|
+
:process_id => presented.process.id,
|
7
|
+
:expression_id => presented.expid,
|
8
|
+
:tree => presented.tree
|
9
|
+
}
|
10
|
+
if presented.error
|
11
|
+
hash.merge!(:error => ErrorPresenter.present(presented.error).to_hash)
|
12
|
+
end
|
13
|
+
hash
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Bumbleworks
|
2
|
+
module Api
|
3
|
+
class ProcessPresenter < Presenter
|
4
|
+
def to_hash
|
5
|
+
{
|
6
|
+
:id => presented.id,
|
7
|
+
:definition_name => presented.definition_name,
|
8
|
+
:subscribed_events => presented.subscribed_events,
|
9
|
+
:entity_name => presented.entity_name
|
10
|
+
}.merge(extras)
|
11
|
+
end
|
12
|
+
|
13
|
+
def extras
|
14
|
+
if in_collection?
|
15
|
+
{}
|
16
|
+
else
|
17
|
+
{
|
18
|
+
:original_tree => presented.original_tree
|
19
|
+
}.merge(entity_hash)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def entity_hash
|
24
|
+
if presented.entity
|
25
|
+
{ :entity => EntityPresenter.present(presented.entity).to_hash }
|
26
|
+
else
|
27
|
+
{}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Bumbleworks
|
2
|
+
module Api
|
3
|
+
class TaskPresenter < Presenter
|
4
|
+
def to_hash
|
5
|
+
{
|
6
|
+
:id => presented.id,
|
7
|
+
:name => presented.to_s,
|
8
|
+
:role => presented.role,
|
9
|
+
:claimant => presented.claimant,
|
10
|
+
:process_id => presented.wfid
|
11
|
+
}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Bumbleworks
|
2
|
+
module Api
|
3
|
+
class TrackerPresenter < Presenter
|
4
|
+
def to_hash
|
5
|
+
{
|
6
|
+
:id => presented.id,
|
7
|
+
:waiting_expression => presented.waiting_expression,
|
8
|
+
:original_hash => presented.original_hash,
|
9
|
+
:action => presented.action,
|
10
|
+
:conditions => presented.conditions,
|
11
|
+
:process_id => presented.wfid
|
12
|
+
}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Bumbleworks
|
2
|
+
module Api
|
3
|
+
class WorkerPresenter < Presenter
|
4
|
+
def to_hash
|
5
|
+
{
|
6
|
+
:id => presented.id,
|
7
|
+
:pid => presented.pid,
|
8
|
+
:name => presented.name,
|
9
|
+
:state => presented.state,
|
10
|
+
:ip => presented.ip,
|
11
|
+
:hostname => presented.hostname,
|
12
|
+
:system => presented.system,
|
13
|
+
:launched_at => presented.launched_at,
|
14
|
+
:updated_at => presented.updated_at,
|
15
|
+
:worker_class => presented.worker_class_name,
|
16
|
+
:uptime => presented.uptime,
|
17
|
+
:uptime_in_words => uptime_in_words,
|
18
|
+
:processed_last_minute => presented.processed_last_minute,
|
19
|
+
:wait_time_last_minute => presented.wait_time_last_minute,
|
20
|
+
:processed_last_hour => presented.processed_last_hour,
|
21
|
+
:wait_time_last_hour => presented.wait_time_last_hour
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def uptime_in_words
|
26
|
+
TimeSupport.seconds_to_units_in_words(presented.uptime)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module TimeSupport
|
2
|
+
class << self
|
3
|
+
def seconds_to_units(seconds, include_zeros: false, round_seconds: true)
|
4
|
+
seconds = seconds.to_i if round_seconds
|
5
|
+
units = Hash[
|
6
|
+
[:days, :hours, :minutes, :seconds].zip(
|
7
|
+
[60, 60, 24].inject([seconds]) {|result, unitsize|
|
8
|
+
result.unshift(*result.shift.divmod(unitsize))
|
9
|
+
result
|
10
|
+
}
|
11
|
+
)
|
12
|
+
]
|
13
|
+
units.reject! { |k,v| v == 0 } unless include_zeros
|
14
|
+
units
|
15
|
+
end
|
16
|
+
|
17
|
+
def seconds_to_units_in_words(seconds, **options)
|
18
|
+
seconds_to_units(seconds, options).map { |unit, num|
|
19
|
+
"#{num} #{unit}"
|
20
|
+
}.join(', ')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/playground_setup.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
ENV['RORY_ENV'] ||= ENV['RACK_ENV'] || 'development'
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'bumbleworks/api'
|
5
|
+
require_relative 'spec/fixtures/bumbleworks_config'
|
6
|
+
require_relative 'spec/support/process_helpers'
|
7
|
+
include ProcessHelpers
|
8
|
+
|
9
|
+
Bumbleworks.start_worker!
|
10
|
+
|
11
|
+
Widget.truncate!
|
12
|
+
WidgetyFidget.truncate!
|
13
|
+
|
14
|
+
widget_processes = 20.times.collect do |i|
|
15
|
+
Widget.new(i).launch_process('task_process')
|
16
|
+
end
|
17
|
+
|
18
|
+
5.times do |i|
|
19
|
+
WidgetyFidget.new(i)
|
20
|
+
end
|
21
|
+
|
22
|
+
wp = Bumbleworks.launch!('waiting_process')
|
23
|
+
|
24
|
+
ProcessHelpers.wait_until(:timeout => 30) do
|
25
|
+
wp.reload.trackers.count == 4
|
26
|
+
end
|
27
|
+
|
28
|
+
widget_processes.first(3).each do |p|
|
29
|
+
p.tasks.map(&:complete)
|
30
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
describe Bumbleworks::Api::EntitiesController do
|
2
|
+
describe "#types" do
|
3
|
+
it "returns entity types" do
|
4
|
+
get "/entities"
|
5
|
+
expect(last_response.body).to eq(
|
6
|
+
json_presentation_of(Bumbleworks.entity_classes)
|
7
|
+
)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#index" do
|
12
|
+
it "returns all existing instances of a given entity type" do
|
13
|
+
foo, bar, baz = Widget.new('foo'),
|
14
|
+
Widget.new('bar'),
|
15
|
+
WidgetyFidget.new('baz')
|
16
|
+
get "/entities/widget"
|
17
|
+
expect(last_response.body).to eq(
|
18
|
+
json_presentation_of([foo, bar], :as => 'Entity')
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#show" do
|
24
|
+
it "returns requested instance" do
|
25
|
+
foo = Widget.new(130)
|
26
|
+
get "/entities/widget/130"
|
27
|
+
expect(last_response.body).to eq(
|
28
|
+
json_presentation_of(foo, :as => 'Entity')
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
describe Bumbleworks::Api::ErrorsController do
|
2
|
+
let(:process) { Bumbleworks.launch!('error_process') }
|
3
|
+
let(:error) { Bumbleworks.errors.first }
|
4
|
+
before(:each) do
|
5
|
+
wait_until { process.reload.errors.count > 0 }
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#index" do
|
9
|
+
it "returns all errors" do
|
10
|
+
Bumbleworks.launch!('error_process')
|
11
|
+
wait_until { Bumbleworks.errors.count == 2 }
|
12
|
+
get "/errors"
|
13
|
+
expect(last_response.body).to eq(
|
14
|
+
json_presentation_of(Bumbleworks.errors, :as => 'Error')
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#show" do
|
20
|
+
it "returns requested error" do
|
21
|
+
get "/processes/#{process.id}/expressions/#{error.expression.expid}/error"
|
22
|
+
expect(last_response.body).to eq(
|
23
|
+
json_presentation_of(error, :as => 'Error')
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#replay" do
|
29
|
+
after(:each) do
|
30
|
+
NaughtyParticipant.naughty_is_ok = false
|
31
|
+
end
|
32
|
+
|
33
|
+
it "attempts to replay error and returns success" do
|
34
|
+
NaughtyParticipant.naughty_is_ok = true
|
35
|
+
put "/processes/#{process.id}/expressions/#{error.expression.expid}/error/replay"
|
36
|
+
wait_until { !process.reload.running? }
|
37
|
+
expect(last_response.body).to eq(
|
38
|
+
{ :status => 'replayed' }.to_json
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
describe Bumbleworks::Api::ExpressionsController do
|
2
|
+
let(:process) { Bumbleworks.launch!('task_process') }
|
3
|
+
let(:expression) { process.expression_at_position('0_0_1') }
|
4
|
+
before(:each) do
|
5
|
+
wait_until { process.reload.tasks.count == 2 }
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#show" do
|
9
|
+
it "returns requested expression" do
|
10
|
+
get "/processes/#{process.id}/expressions/#{expression.expid}"
|
11
|
+
expect(last_response.body).to eq(
|
12
|
+
json_presentation_of(expression)
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#cancel" do
|
18
|
+
it "attempts to cancel expression and returns success" do
|
19
|
+
allow_any_instance_of(described_class).to receive(:expression).
|
20
|
+
and_return(expression)
|
21
|
+
expect(expression).to receive(:cancel!)
|
22
|
+
delete "/processes/#{process.id}/expressions/#{expression.expid}/cancel"
|
23
|
+
expect(last_response.body).to eq(
|
24
|
+
{ :status => 'cancelled' }.to_json
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#kill" do
|
30
|
+
it "attempts to kill expression and returns success" do
|
31
|
+
allow_any_instance_of(described_class).to receive(:expression).
|
32
|
+
and_return(expression)
|
33
|
+
expect(expression).to receive(:kill!)
|
34
|
+
delete "/processes/#{process.id}/expressions/#{expression.expid}/kill"
|
35
|
+
expect(last_response.body).to eq(
|
36
|
+
{ :status => 'killed' }.to_json
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
describe Bumbleworks::Api::ProcessesController do
|
2
|
+
describe "#index" do
|
3
|
+
before(:each) do
|
4
|
+
allow(Bumbleworks::Api::ProcessPresenter).to receive(:present).
|
5
|
+
with(:processes).
|
6
|
+
and_return(:presented_processes)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "returns requested page with requested number of processes" do
|
10
|
+
allow(Bumbleworks::Process).to receive(:all).
|
11
|
+
with(:limit => 12, :offset => 24).
|
12
|
+
and_return(:processes)
|
13
|
+
get "/processes?page=3&limit=12"
|
14
|
+
expect(last_response.body).to eq(
|
15
|
+
json_presentation_of(:processes, :as => 'Process')
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "defaults to limit 10" do
|
20
|
+
allow(Bumbleworks::Process).to receive(:all).
|
21
|
+
with(:limit => 10, :offset => 10).
|
22
|
+
and_return(:processes)
|
23
|
+
get "/processes?page=2"
|
24
|
+
expect(last_response.body).to eq(
|
25
|
+
json_presentation_of(:processes, :as => 'Process')
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "defaults to page 1" do
|
30
|
+
allow(Bumbleworks::Process).to receive(:all).
|
31
|
+
with(:limit => 3, :offset => 0).
|
32
|
+
and_return(:processes)
|
33
|
+
get "/processes?limit=3"
|
34
|
+
expect(last_response.body).to eq(
|
35
|
+
json_presentation_of(:processes, :as => 'Process')
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#show" do
|
41
|
+
it "returns requested instance" do
|
42
|
+
process = Bumbleworks.launch!('task_process', :entity => Widget.new(41))
|
43
|
+
wait_until { process.tasks.count == 2 }
|
44
|
+
get "/processes/#{process.id}"
|
45
|
+
expect(last_response.body).to eq(
|
46
|
+
json_presentation_of(process)
|
47
|
+
)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
describe Bumbleworks::Api::TasksController do
|
2
|
+
let(:process) { Bumbleworks.launch!('task_process') }
|
3
|
+
before(:each) do
|
4
|
+
wait_until do
|
5
|
+
process.tasks.count == 2
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#index" do
|
10
|
+
it "returns all tasks" do
|
11
|
+
tasks = process.tasks.all
|
12
|
+
get "/tasks"
|
13
|
+
expect(last_response.body).to eq(
|
14
|
+
json_presentation_of(tasks)
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#show" do
|
20
|
+
it "returns requested task" do
|
21
|
+
task = process.tasks.first
|
22
|
+
get "/tasks/#{task.id}"
|
23
|
+
expect(last_response.body).to eq(
|
24
|
+
json_presentation_of(task)
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#claim" do
|
30
|
+
it "claims and returns task" do
|
31
|
+
task = process.tasks.first
|
32
|
+
put "/tasks/#{task.id}/claim", :claimant => "horatio"
|
33
|
+
expect(task.reload.claimant).to eq("horatio")
|
34
|
+
expect(last_response.body).to eq(
|
35
|
+
json_presentation_of(task)
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#release" do
|
41
|
+
it "releases and returns task" do
|
42
|
+
task = process.tasks.first
|
43
|
+
task.claim("whiskey")
|
44
|
+
put "/tasks/#{task.id}/release"
|
45
|
+
expect(task.reload.claimant).to be_nil
|
46
|
+
expect(last_response.body).to eq(
|
47
|
+
json_presentation_of(task)
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#complete" do
|
53
|
+
let(:task) { process.tasks.first }
|
54
|
+
before(:each) do
|
55
|
+
allow(Bumbleworks::Task).to receive(:find_by_id).
|
56
|
+
with(task.id).
|
57
|
+
and_return(task)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "completes task and returns success" do
|
61
|
+
expect(task).to receive(:complete).with(:foo => "bar", "foo" => "bar")
|
62
|
+
put "/tasks/#{task.id}/complete", :foo => :bar
|
63
|
+
expect(last_response.body).to eq(
|
64
|
+
{ :status => 'completed' }.to_json
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns 422 with error message if task not completable" do
|
69
|
+
allow(task).to receive(:complete).and_raise(
|
70
|
+
Bumbleworks::Task::NotCompletable, "uh oh you can't do that"
|
71
|
+
)
|
72
|
+
put "/tasks/#{task.id}/complete", :foo => :bar
|
73
|
+
expect(last_response.body).to eq(
|
74
|
+
{
|
75
|
+
:status => "not_completable",
|
76
|
+
:message => "uh oh you can't do that"
|
77
|
+
}.to_json
|
78
|
+
)
|
79
|
+
expect(last_response.status).to eq(422)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|