banter 0.8.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +8 -0
  4. data/Gemfile +2 -0
  5. data/Rakefile +29 -0
  6. data/lib/banter/subscriber.rb +18 -0
  7. data/lib/banter/version.rb +1 -1
  8. data/lib/banter/web.rb +6 -0
  9. data/lib/banter/web/application.rb +62 -13
  10. data/lib/banter/web/helpers.rb +6 -2
  11. data/lib/banter/web/models/banter_message.rb +45 -5
  12. data/lib/banter/web/models/banter_worker.rb +5 -0
  13. data/lib/banter/web/serializers/banter_message_serializer.rb +34 -0
  14. data/lib/banter/web/serializers/banter_worker_serializer.rb +30 -0
  15. data/lib/banter/web/serializers/subscriber_serializer.rb +26 -0
  16. data/spec/banter/subscriber_spec.rb +82 -1
  17. data/spec/banter/web/application_spec.rb +173 -2
  18. data/spec/factories/banter_messages.rb +28 -0
  19. data/spec/spec_helper.rb +0 -1
  20. data/web/assets/javascripts/banter.js +449 -0
  21. data/web/assets/javascripts/dashboard_app/app.coffee +4 -0
  22. data/web/assets/javascripts/dashboard_app/controllers/message_controller.coffee +27 -0
  23. data/web/assets/javascripts/dashboard_app/controllers/messages_controller.coffee +57 -0
  24. data/web/assets/javascripts/dashboard_app/controllers/subscribers_controller.coffee +25 -0
  25. data/web/assets/javascripts/dashboard_app/controllers/worker_controller.coffee +30 -0
  26. data/web/assets/javascripts/dashboard_app/controllers/workers_list_controller.coffee +28 -0
  27. data/web/assets/javascripts/dashboard_app/routes.coffee +82 -0
  28. data/web/assets/javascripts/dashboard_app/services/common_methods_service.coffee +6 -0
  29. data/web/assets/javascripts/dashboard_app/services/message_methods_service.coffee +18 -0
  30. data/web/assets/javascripts/dashboard_app/services/messages_service.coffee +17 -0
  31. data/web/assets/javascripts/dashboard_app/services/subscribers_service.coffee +8 -0
  32. data/web/assets/javascripts/dashboard_app/services/worker_methods_service.coffee +11 -0
  33. data/web/assets/javascripts/dashboard_app/services/worker_service.coffee +15 -0
  34. data/web/assets/javascripts/ng-prettyjson.js +15 -0
  35. data/web/assets/stylesheets/banter.css +13 -1
  36. data/web/views/_message.haml +59 -0
  37. data/web/views/_messages.haml +77 -0
  38. data/web/views/_subscribers.haml +22 -0
  39. data/web/views/_worker.haml +52 -0
  40. data/web/views/_workers_list.haml +35 -0
  41. data/web/views/index.haml +16 -29
  42. data/web/views/layout.haml +16 -10
  43. metadata +28 -7
  44. data/web/views/message.haml +0 -52
  45. data/web/views/messages.haml +0 -20
  46. data/web/views/subscribers.haml +0 -17
  47. data/web/views/worker.haml +0 -51
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 11d9e4fde9f27b5c99163b5abb6ddd4f8cb29607
4
- data.tar.gz: 754895e5e7af5773b072e7fb7f2024728bbaa555
3
+ metadata.gz: 51871d5d895dbf72454f67a614a14aa8af5cd6ee
4
+ data.tar.gz: 37321de3cd2605a5b7d99ffce5e979167b7597df
5
5
  SHA512:
6
- metadata.gz: c4418fba824671130e639689e5c9f158256a45f7dfff640366bbafa50eac3a98478a7daa0bb2d3170148a05a01acc7e92d14d89f32d9e0e7558d8a87621c5db7
7
- data.tar.gz: c15e928646a3f46129dfa639fb637a33fd3d0dfd98acc871ed17de0656dc4042a91c85e7dc396cda395efc4e8af15eeb939fd156bdad8cb0d8a03d867429ec30
6
+ metadata.gz: 9dba68030359665879dbd785c613523282f97a5b91a721824c91dd4375e87a8299e39f68af759595ffe5963b584b7981b526a7a5d0071f05b2a4a02cc1c95669
7
+ data.tar.gz: 16ba97865260c2e81463d374f1c4806cf17ad65628022aa6abc90da7e6d8af8741a0166afd67887f59a5d87c5f9f8dc53ccb989e7609f376e379212d5707173a
data/.gitignore CHANGED
@@ -19,4 +19,4 @@ test/version_tmp
19
19
  tmp
20
20
  .rspec
21
21
  .idea
22
- test_pubsub.log
22
+ *.log
@@ -8,4 +8,12 @@ services:
8
8
  before_script:
9
9
  - sleep 5
10
10
  - mongo mydb_test --eval 'db.addUser("travis", "test");'
11
+ notifications:
12
+ email:
13
+ - webadmin@honest.com
14
+ - tushar@honest.com
15
+ on_success: always
16
+ on_failure: always
17
+
18
+
11
19
 
data/Gemfile CHANGED
@@ -3,6 +3,8 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in banter.gemspec
4
4
  gemspec
5
5
 
6
+ gem 'coffee-script'
7
+ gem 'json'
6
8
  group :test do
7
9
  gem 'rack-test'
8
10
  gem 'sinatra'
data/Rakefile CHANGED
@@ -5,3 +5,32 @@ begin
5
5
  RSpec::Core::RakeTask.new(:spec)
6
6
  task :default => :spec
7
7
  end
8
+
9
+ begin
10
+ require 'coffee-script'
11
+
12
+ namespace :js do
13
+ def add_javascript(js_file, output_file)
14
+ js_file_souce = File.read("#{File.dirname(__FILE__)}/web/assets/javascripts/#{js_file}.js")
15
+ output_file.puts js_file_souce
16
+ end
17
+
18
+ desc "compile coffee-scripts from ./assets/javascripts"
19
+ task :compile do
20
+ source = "#{File.dirname(__FILE__)}/web/assets/javascripts/**/*.coffee"
21
+ target = "#{File.dirname(__FILE__)}/web/assets/javascripts/banter.js"
22
+ file = File.open(target, 'w+')
23
+
24
+ add_javascript("ng-prettyjson", file)
25
+
26
+ Dir.glob(source) do |cf|
27
+ puts cf
28
+ file.puts CoffeeScript.compile File.read(cf)
29
+ end
30
+
31
+ file.close
32
+ end
33
+ end
34
+
35
+
36
+ end
@@ -90,6 +90,24 @@ module Banter
90
90
 
91
91
  private
92
92
 
93
+ # @param [Integer] number The expected number of total objects that may be processed due to the message
94
+ def total(number)
95
+ return unless Banter::Configuration.web_enabled
96
+ BanterMessage.update_progress_total(number)
97
+ end
98
+
99
+ # @param [Integer] number The current progress of the execution of the message
100
+ def at(number)
101
+ return unless Banter::Configuration.web_enabled
102
+ BanterMessage.update_progress_at(number)
103
+ end
104
+
105
+ # @param [Object] object An object that indicates the current progress of the message
106
+ def progress(object)
107
+ return unless Banter::Configuration.web_enabled
108
+ BanterMessage.update_progress(object)
109
+ end
110
+
93
111
  def self.validate_routing_key_name(key)
94
112
  return true if key.blank?
95
113
  key.match(/\A([a-z]+\.?)*([a-z]+)\Z/).present?
@@ -1,3 +1,3 @@
1
1
  module Banter
2
- VERSION = "0.8.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -20,6 +20,12 @@ module Banter
20
20
  module Web
21
21
  autoload :Application, 'banter/web/application'
22
22
  autoload :Helpers, 'banter/web/helpers'
23
+
24
+ module Serializers
25
+ autoload :BanterWorkerSerializer, 'banter/web/serializers/banter_worker_serializer'
26
+ autoload :BanterMessageSerializer, 'banter/web/serializers/banter_message_serializer'
27
+ autoload :SubscriberSerializer, 'banter/web/serializers/subscriber_serializer'
28
+ end
23
29
  end
24
30
  end
25
31
 
@@ -8,29 +8,78 @@ module Banter
8
8
  helpers Banter::Web::Helpers
9
9
 
10
10
  get '/' do
11
- @workers = BanterWorker.running.ordered
12
- haml :index, locals: { workers: @workers }
11
+ redirect "#{root_path}dashboard"
13
12
  end
14
13
 
15
- get '/workers/:id' do
14
+ get '/dashboard*', provides: :html do
15
+ haml :index
16
+ end
17
+
18
+ get '/workers.json' do
19
+ content_type :json
20
+ workers = BanterWorker.running.ordered.map { |w| serialize_worker(w) }
21
+ { workers: workers }.to_json
22
+ end
23
+
24
+ get '/workers/:id.json' do
25
+ content_type :json
16
26
  worker = BanterWorker.find(params[:id])
17
- haml :worker, locals: { worker: worker }
27
+ { worker: serialize_worker(worker) }.to_json
28
+ end
29
+
30
+ get '/messages/:message_id.json' do
31
+ content_type :json
32
+ message = BanterMessage.find(params[:message_id])
33
+ {
34
+ message: serialize_message(message, true)
35
+ }.to_json
36
+ end
37
+
38
+ get '/messages.json' do
39
+ content_type :json
40
+ offset = 0
41
+ worker = nil
42
+ offset = 50 * (params[:page].to_i - 1) if params[:page]
43
+ messages = BanterMessage.ordered.limit(50).offset(offset)
44
+ if params[:worker_id].present?
45
+ worker = BanterWorker.find(params[:worker_id])
46
+ messages = messages.where(worker_id: worker.id).ordered
47
+ end
48
+
49
+ if params[:subscriber].present?
50
+ messages = messages.where(subscriber_class: params[:subscriber])
51
+ end
52
+
53
+ hash = {
54
+ messages: messages.map { |m| serialize_message(m, true) }
55
+ }
56
+ hash[:worker] = serialize_worker(worker) if worker
57
+ hash.to_json
18
58
  end
19
59
 
20
- get '/messages/:id' do
21
- message = BanterMessage.find(params[:id])
22
- haml :message, locals: { message: message }
60
+ get '/subscribers.json' do
61
+ content_type :json
62
+ {
63
+ subscribers: BanterWorker.subscribers.map { |s| serialize_subscriber(s) }
64
+ }.to_json
23
65
  end
24
66
 
25
- get '/workers/:worker_id/messages' do
26
- messages = BanterMessage.where(worker_id: Moped::BSON::ObjectId.from_string(params[:worker_id])).ordered
27
- worker = BanterWorker.find(params[:worker_id])
28
- haml :messages, locals: { messages: messages, worker: worker }
67
+ private
68
+
69
+ def serialize_message(message, include_worker = false)
70
+ Banter::Web::Serializers::BanterMessageSerializer.new(message).to_hash.tap do |hash|
71
+ hash[:worker] = serialize_worker(message.worker) if include_worker
72
+ end
29
73
  end
30
74
 
31
- get '/subscribers' do
32
- haml :subscribers
75
+ def serialize_worker(worker)
76
+ Banter::Web::Serializers::BanterWorkerSerializer.new(worker).to_hash
33
77
  end
78
+
79
+ def serialize_subscriber(subscriber)
80
+ Banter::Web::Serializers::SubscriberSerializer.new(subscriber).to_hash
81
+ end
82
+
34
83
  end
35
84
  end
36
85
  end
@@ -1,8 +1,12 @@
1
1
  module Banter
2
2
  module Web
3
3
  module Helpers
4
- def root_path
5
- "#{env['SCRIPT_NAME']}/"
4
+ def root_path(path = nil)
5
+ "#{env['SCRIPT_NAME']}/#{path}"
6
+ end
7
+
8
+ def dashboard_path(path = nil)
9
+ root_path("dashboard/#{path}")
6
10
  end
7
11
  end
8
12
  end
@@ -14,17 +14,29 @@ class BanterMessage
14
14
  field :status, type: String
15
15
  field :error_message, type: String
16
16
  field :worker_id
17
- # embeds_one :worker, class_name: 'BanterWorker'
17
+ field :progress_at, type: Integer
18
+ field :progress_total, type: Integer
19
+ field :progress
18
20
 
19
- STATUS_STARTED = 'started'
20
- STATUS_SUCCESS = 'success'
21
- STATUS_ERROR = 'error'
22
- STATUS_VALIDATION_FAILED = 'validation_failed'
21
+ STATUS_STARTED = 'started'
22
+ STATUS_SUCCESS = 'success'
23
+ STATUS_ERROR = 'error'
24
+ STATUS_VALIDATION_FAILED = 'validation_failed'
23
25
 
24
26
  cattr_reader :current
25
27
 
26
28
  scope :ordered, ->() { order_by(:started_at.desc) }
29
+ scope :for_subscriber, ->(subscriber) { where(subscriber_class: subscriber) }
30
+ scope :successful, ->() { where(status: STATUS_SUCCESS) }
31
+ scope :errored, ->() { where(status: STATUS_ERROR) }
32
+ scope :validation_failed, ->() { where(status: STATUS_VALIDATION_FAILED) }
33
+ scope :started, ->() { where(status: STATUS_STARTED) }
27
34
 
35
+ # Creates a BanterMessage to record a message that is received by a subscriber.
36
+ # It records the created message as BanterMessage.current
37
+ # @param [Subscriber] subscriber_class
38
+ # @param [Banter::Server::ClientQueueListener] worker
39
+ # @param [Object] payload
28
40
  def self.message_received(subscriber_class, worker, payload)
29
41
  return unless BanterWorker.current
30
42
 
@@ -47,6 +59,7 @@ class BanterMessage
47
59
  BanterWorker.record_current!(obj)
48
60
  end
49
61
 
62
+ # Records the current message as successful. Clears the BanterMessage.current
50
63
  def self.message_successful
51
64
  current.status = STATUS_SUCCESS
52
65
  current.done_at = Time.now
@@ -55,6 +68,9 @@ class BanterMessage
55
68
  @@current = nil
56
69
  end
57
70
 
71
+ # Records the current message as errored or validation failed with error message. Clears the BanterMessage.current
72
+ # @param [String] error_message
73
+ # @param [String] status
58
74
  def self.message_failed(error_message, status)
59
75
  current.status = status
60
76
  current.done_at = Time.now
@@ -64,6 +80,29 @@ class BanterMessage
64
80
  @@current = nil
65
81
  end
66
82
 
83
+ # Records the current progress as a number for the current message.
84
+ # @param [Integer] num Current progress
85
+ def self.update_progress_at(num)
86
+ return unless current
87
+ current.progress_at = num
88
+ current.save!
89
+ end
90
+
91
+ # Records the number of objects expected to be processed for the current message.
92
+ # @param [Integer] num Total number of objects expected to be processed.
93
+ def self.update_progress_total(num)
94
+ return unless current
95
+ current.progress_total = num
96
+ current.save!
97
+ end
98
+
99
+ # @param [Object] object Any object that indicates the progress of the message
100
+ def self.update_progress(object)
101
+ return unless current
102
+ current.progress = object
103
+ current.save!
104
+ end
105
+
67
106
  def executing?
68
107
  self.status == STATUS_STARTED
69
108
  end
@@ -76,6 +115,7 @@ class BanterMessage
76
115
  self.status == STATUS_ERROR || self.status == STATUS_VALIDATION_FAILED
77
116
  end
78
117
 
118
+ # @return [BanterWorker] The worker that is responsible for executing the current message
79
119
  def worker
80
120
  @worker ||= BanterWorker.find(self['worker_id'].to_s)
81
121
  end
@@ -37,6 +37,11 @@ class BanterWorker
37
37
  @@current = nil
38
38
  end
39
39
 
40
+ # @return [Array] Unique subscriber classes
41
+ def self.subscribers
42
+ distinct("worker_classes")
43
+ end
44
+
40
45
  # @param [BanterMessage] banter_message Message that is currently being executed
41
46
  def self.record_current!(banter_message)
42
47
  current.current_message = banter_message.attributes
@@ -0,0 +1,34 @@
1
+ module Banter
2
+ module Web
3
+
4
+ module Serializers
5
+ class BanterMessageSerializer
6
+ attr_accessor :message
7
+
8
+ def initialize(message)
9
+ @message = message
10
+ end
11
+
12
+ def to_hash
13
+ {
14
+ id: message.id.to_s,
15
+ subscriber_class: message.subscriber_class,
16
+ queue_name: message.queue_name,
17
+ subscribed_key: message.subscribed_key,
18
+ payload_key: message.payload_key,
19
+ payload: message.payload,
20
+ context: message.context,
21
+ started_at: message.started_at,
22
+ done_at: message.done_at,
23
+ status: message.status,
24
+ error_message: message.error_message,
25
+ worker_id: message.worker_id,
26
+ progress_total: message.progress_total,
27
+ progress_at: message.progress_at,
28
+ progress: message.progress
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ module Banter
2
+ module Web
3
+
4
+ module Serializers
5
+ class BanterWorkerSerializer
6
+ attr_accessor :worker
7
+
8
+ def initialize(worker)
9
+ @worker = worker
10
+ end
11
+
12
+ def to_hash
13
+ {
14
+ id: worker.id.to_s,
15
+ process_name: worker.process_name,
16
+ pid: worker.pid,
17
+ started_at: worker.started_at,
18
+ stopped_at: worker.stopped_at,
19
+ worker_classes: Array.wrap(worker.worker_classes),
20
+ hostname: worker.hostname,
21
+ current_message: worker.current_message,
22
+ job_count: worker.job_count,
23
+ success_count: worker.success_count,
24
+ failed_count: worker.failed_count
25
+ }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,26 @@
1
+ module Banter
2
+ module Web
3
+
4
+ module Serializers
5
+ class SubscriberSerializer
6
+ attr_accessor :subscriber_class_name
7
+
8
+ def initialize(subscriber_class_name)
9
+ @subscriber_class_name = subscriber_class_name
10
+ end
11
+
12
+ def to_hash
13
+ messages = BanterMessage.for_subscriber(subscriber_class_name)
14
+ {
15
+ class_name: subscriber_class_name,
16
+ total: messages.count,
17
+ success_count: messages.successful.count,
18
+ started_count: messages.started.count,
19
+ errored_count: messages.errored.count,
20
+ validation_failed_count: messages.validation_failed.count
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -16,7 +16,88 @@ describe Banter::Subscriber do
16
16
  end
17
17
  end
18
18
 
19
- describe "self.validates_payload_with" do
19
+ describe '#at' do
20
+ let(:subscriber) { Banter::Subscriber.new({}, {}, {}) }
21
+ context 'web not enabled' do
22
+ before do
23
+ allow(Banter::Configuration).to receive(:web_enabled).and_return(false)
24
+ subscriber.send(:at, 123)
25
+ end
26
+
27
+ it 'doesnt do a thing' do
28
+ expect(BanterMessage.count).to eq(0)
29
+ end
30
+ end
31
+
32
+ context 'web enabled' do
33
+ let(:banter_message) { FactoryGirl.create(:banter_message)}
34
+ before do
35
+ allow(Banter::Configuration).to receive(:web_enabled).and_return(true)
36
+ allow(BanterMessage).to receive(:current).and_return(banter_message)
37
+ subscriber.send(:at, 123)
38
+ end
39
+
40
+ it 'updates progress_at on current message' do
41
+ expect(banter_message.progress_at).to eq(123)
42
+ end
43
+ end
44
+ end
45
+
46
+ describe '#total' do
47
+ let(:subscriber) { Banter::Subscriber.new({}, {}, {}) }
48
+ context 'web not enabled' do
49
+ before do
50
+ allow(Banter::Configuration).to receive(:web_enabled).and_return(false)
51
+ subscriber.send(:total, 1230)
52
+ end
53
+
54
+ it 'doesnt do a thing' do
55
+ expect(BanterMessage.count).to eq(0)
56
+ end
57
+ end
58
+
59
+ context 'web enabled' do
60
+ let(:banter_message) { FactoryGirl.create(:banter_message)}
61
+ before do
62
+ allow(Banter::Configuration).to receive(:web_enabled).and_return(true)
63
+ allow(BanterMessage).to receive(:current).and_return(banter_message)
64
+ subscriber.send(:total, 1230)
65
+ end
66
+
67
+ it 'updates progress_at on current message' do
68
+ expect(banter_message.progress_total).to eq(1230)
69
+ end
70
+ end
71
+ end
72
+
73
+ describe '#progress' do
74
+ let(:subscriber) { Banter::Subscriber.new({}, {}, {}) }
75
+ context 'web not enabled' do
76
+ before do
77
+ allow(Banter::Configuration).to receive(:web_enabled).and_return(false)
78
+ subscriber.send(:progress, foo: 'bar')
79
+ end
80
+
81
+ it 'doesnt do a thing' do
82
+ expect(BanterMessage.count).to eq(0)
83
+ end
84
+ end
85
+
86
+ context 'web enabled' do
87
+ let(:banter_message) { FactoryGirl.create(:banter_message)}
88
+ before do
89
+ allow(Banter::Configuration).to receive(:web_enabled).and_return(true)
90
+ allow(BanterMessage).to receive(:current).and_return(banter_message)
91
+ subscriber.send(:progress, foo: 'bar')
92
+ end
93
+
94
+ it 'updates progress_at on current message' do
95
+ expect(banter_message.progress).to eq(foo: 'bar')
96
+ end
97
+ end
98
+ end
99
+
100
+ describe ".validates_payload_with" do
20
101
  before do
21
102
  validators.each do |validator|
22
103
  Klass.validates_payload_with validator