dboard 1.0.5 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 066c3ae2d4574bad0b7b56e0c5a784fe36b396d5
4
- data.tar.gz: 49aa1e783781c16fabee8416c2bdc97e9eceb0d1
2
+ SHA256:
3
+ metadata.gz: 4e3f07d3f503da759e56a7e315c0a59c3a1e7fa9a4bd3cdc2ac6533294c4d6df
4
+ data.tar.gz: 6f5d28ba5299fa8d219c4f1581635c78db27052ddc9fc06c33e06bd9f4507613
5
5
  SHA512:
6
- metadata.gz: 779322a76e7fd4521fc8c30cd6e394444e2ee25ee16849a7958a2fc04cb883f5d7c7b7e2dd32376f4edc51e4ac0a1ab6d146dc17e642defd532ae2e74bbbd319
7
- data.tar.gz: 14c30edbffe96773c1d783865e7e97581c286d06eff18c87c078d63c33eeca4c49dfbd1cc69591435fa0ca1e652d99021bd89b9dd1c73d724ceb208f8bb16074
6
+ metadata.gz: 3a7d9df2e8c2271a83fc757f9564dd41c5662a3780a01a5c4d5024421035ed3b2eca88f5379b96c7c39a151821be2152cb3d6e11269a5ec3f8db0e5ddf78af07
7
+ data.tar.gz: 004c14e13bc762c8fe3aae9618696a568048c139a0e0ab30c7b49e603f680d0f79d5251d55ad49e058edce6b97315a09981c627bc1dec68d44eea84ed6db4089
@@ -0,0 +1,26 @@
1
+ name: Ruby CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ test:
11
+
12
+ runs-on: ubuntu-latest
13
+
14
+ strategy:
15
+ matrix:
16
+ ruby-version: ["3.0", "2.7", "2.6"]
17
+
18
+ steps:
19
+ - uses: actions/checkout@v2
20
+ - name: Set up Ruby ${{ matrix.ruby-version }}
21
+ uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: ${{ matrix.ruby-version }}
24
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
25
+ - name: Run tests
26
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -3,3 +3,4 @@
3
3
  Gemfile.lock
4
4
  pkg/*
5
5
  todo.txt
6
+ /tmp
data/.rubocop.yml ADDED
@@ -0,0 +1,5 @@
1
+ AllCops:
2
+ Exclude:
3
+ - "tmp/**/*"
4
+ inherit_gem:
5
+ barsoom_utils: shared_rubocop.yml
data/Gemfile CHANGED
@@ -1,2 +1,11 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
  gemspec
3
+
4
+ gem "net_http_timeout_errors"
5
+
6
+ group :development, :test do
7
+ gem "barsoom_utils"
8
+ gem "rspec"
9
+ gem "rubocop"
10
+ gem "webmock"
11
+ end
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Ruby CI](https://github.com/barsoom/dboard/actions/workflows/ci.yml/badge.svg)](https://github.com/barsoom/dboard/actions/workflows/ci.yml)
2
+
1
3
  A dashboard framework.
2
4
 
3
5
  It handles collecting data from user defined sources (simple ruby classes) and provides a simple API to poll for updates. See the [example app](https://github.com/joakimk/dboard_example) for information on how to use it.
data/Rakefile CHANGED
@@ -1,5 +1,5 @@
1
- require 'bundler'
2
- require 'rspec/core/rake_task'
1
+ require "bundler"
2
+ require "rspec/core/rake_task"
3
3
 
4
4
  Bundler::GemHelper.install_tasks
5
5
 
@@ -7,4 +7,4 @@ desc "Run specs"
7
7
  RSpec::Core::RakeTask.new do |t|
8
8
  end
9
9
 
10
- task :default => :spec
10
+ task default: :spec
data/dboard.gemspec CHANGED
@@ -6,22 +6,20 @@ Gem::Specification.new do |s|
6
6
  s.name = "dboard"
7
7
  s.version = Dboard::VERSION
8
8
  s.platform = Gem::Platform::RUBY
9
- s.authors = ["Joakim Kolsjö"]
10
- s.email = ["joakim.kolsjo@gmail.com"]
9
+ s.authors = [ "Joakim Kolsjö" ]
10
+ s.email = [ "joakim.kolsjo@gmail.com" ]
11
11
  s.homepage = ""
12
12
  s.summary = %q{Dashboard framework}
13
13
  s.description = %q{Dashboard framework}
14
14
  s.license = "MIT"
15
+ s.metadata = { "rubygems_mfa_required" => "true" }
15
16
 
16
17
  s.files = `git ls-files`.split("\n")
17
- s.test_files = `git ls-files -- {spec}/*`.split("\n")
18
- s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
19
- s.require_paths = ["lib"]
18
+ s.require_paths = [ "lib" ]
20
19
 
20
+ s.add_dependency "dalli"
21
21
  s.add_dependency "httparty"
22
- s.add_dependency "rake"
23
22
  s.add_dependency "json"
24
- s.add_dependency "dalli"
23
+ s.add_dependency "rake"
25
24
  s.add_dependency "sinatra"
26
- s.add_development_dependency "rspec", "2.6.0"
27
25
  end
data/lib/api.rb CHANGED
@@ -1,8 +1,9 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), 'cache'))
2
- require File.expand_path(File.join(File.dirname(__FILE__), 'collector.rb'))
3
- require 'digest/md5'
4
- require 'json'
5
- require 'httparty'
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "cache"))
2
+ require File.expand_path(File.join(File.dirname(__FILE__), "collector.rb"))
3
+ require "digest/md5"
4
+ require "json"
5
+ require "httparty"
6
+ require "net_http_timeout_errors"
6
7
 
7
8
  module Dboard
8
9
  class Api
@@ -11,16 +12,37 @@ module Dboard
11
12
 
12
13
  class Client
13
14
  include HTTParty
15
+
16
+ def self.post(*args, **kwargs)
17
+ with_retries { super }
18
+ end
19
+
20
+ private
21
+
22
+ private_class_method \
23
+ def self.with_retries
24
+ remaining_attempts = 3
25
+
26
+ begin
27
+ remaining_attempts -= 1
28
+ yield
29
+ rescue *NetHttpTimeoutErrors.all
30
+ raise if remaining_attempts == 0
31
+
32
+ sleep((ENV["RACK_ENV"] == "test") ? 0 : 5)
33
+ retry
34
+ end
35
+ end
14
36
  end
15
37
 
16
38
  def self.get(params)
17
39
  types = {}
18
- params[:types].split(',').each do |type|
40
+ params[:types].split(",").each do |type|
19
41
  raw_data = CACHE.get("dashboard::source::#{type}")
20
42
  data = raw_data ? JSON.parse(raw_data) : {}
21
- types.merge!(type => { :data => data, :checksum => Digest::MD5.hexdigest(data.inspect) })
43
+ types.merge!(type => { data: data, checksum: Digest::MD5.hexdigest(data.inspect) })
22
44
  end
23
- { :version => (@@version || ENV["COMMIT_HASH"] || "unversioned"), :sources => types }.to_json
45
+ { version: (@@version || ENV["COMMIT_HASH"] || "unversioned"), sources: types }.to_json
24
46
  end
25
47
 
26
48
  def self.version=(version)
data/lib/cache.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'dalli'
1
+ require "dalli"
2
2
 
3
3
  module Dboard
4
4
  if Config.config[:memcache]
data/lib/collector.rb CHANGED
@@ -1,5 +1,5 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), 'publisher'))
2
- require 'singleton'
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "publisher"))
2
+ require "singleton"
3
3
 
4
4
  module Dboard
5
5
  class Collector
@@ -15,6 +15,10 @@ module Dboard
15
15
  Collector.instance.register_after_update_callback(callback)
16
16
  end
17
17
 
18
+ def self.register_error_callback(callback)
19
+ Collector.instance.register_error_callback(callback)
20
+ end
21
+
18
22
  def self.start
19
23
  instance.start
20
24
  end
@@ -22,6 +26,7 @@ module Dboard
22
26
  def initialize
23
27
  @sources = {}
24
28
  @after_update_callback = lambda {}
29
+ @error_callback = lambda { |exception| }
25
30
  end
26
31
 
27
32
  def start
@@ -45,6 +50,10 @@ module Dboard
45
50
  @after_update_callback = callback
46
51
  end
47
52
 
53
+ def register_error_callback(callback)
54
+ @error_callback = callback
55
+ end
56
+
48
57
  # Public because the old tests depend on it
49
58
  def update_source(source, instance)
50
59
  begin
@@ -56,6 +65,7 @@ module Dboard
56
65
  rescue Exception => ex
57
66
  puts "Failed to update #{source}: #{ex.message}"
58
67
  puts ex.backtrace
68
+ @error_callback.call(ex)
59
69
  end
60
70
 
61
71
  private
data/lib/dboard.rb CHANGED
@@ -1,3 +1,3 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), 'config.rb'))
2
- require File.expand_path(File.join(File.dirname(__FILE__), 'api.rb'))
3
- require File.expand_path(File.join(File.dirname(__FILE__), 'collector.rb'))
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "config.rb"))
2
+ require File.expand_path(File.join(File.dirname(__FILE__), "api.rb"))
3
+ require File.expand_path(File.join(File.dirname(__FILE__), "collector.rb"))
data/lib/publisher.rb CHANGED
@@ -1,9 +1,9 @@
1
- require 'json'
1
+ require "json"
2
2
 
3
3
  module Dboard
4
4
  class Publisher
5
5
  def self.publish(source, data)
6
- Api::Client.post("/sources/#{source}", :body => { :data => data.to_json }, :timeout => 10000)
6
+ Api::Client.post("/sources/#{source}", body: { data: data.to_json }, timeout: 10000)
7
7
  rescue SocketError => ex
8
8
  puts "SocketError: #{ex.message}"
9
9
  end
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dboard
2
- VERSION = "1.0.5"
2
+ VERSION = "2.0.1"
3
3
  end
@@ -1,30 +1,46 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
2
2
 
3
- describe Dboard::Collector, "register_source" do
3
+ describe Dboard::Collector, ".register_source" do
4
4
  before do
5
5
  Dboard::Collector.instance.sources.clear
6
6
  end
7
7
 
8
8
  it "can register a source" do
9
- new_relic = mock
10
- new_relic.stub!(:update_interval).and_return(5)
9
+ new_relic = double
10
+ allow(new_relic).to receive(:update_interval).and_return(5)
11
11
  Dboard::Collector.register_source :new_relic, new_relic
12
- Dboard::Collector.instance.sources.should == { :new_relic => new_relic }
12
+ expect(Dboard::Collector.instance.sources).to eq({ new_relic: new_relic })
13
13
  end
14
14
 
15
15
  it "can register an after update callback" do
16
- new_relic = mock
17
- new_relic.stub!(:fetch).and_return({ :db => "100%" })
18
- callback = mock
16
+ new_relic = double
17
+ allow(new_relic).to receive(:fetch).and_return({ db: "100%" })
18
+ callback = double
19
19
  Dboard::Collector.register_after_update_callback callback
20
20
 
21
- callback.should_receive(:call)
22
- Dboard::Publisher.stub!(:publish)
21
+ expect(callback).to receive(:call)
22
+ allow(Dboard::Publisher).to receive(:publish)
23
23
  Dboard::Collector.instance.update_source(:new_relic, new_relic)
24
24
 
25
25
  # since it is a singleton, and this callbacks leaks into the other tests
26
26
  Dboard::Collector.register_after_update_callback(lambda {})
27
27
  end
28
+
29
+ it "can register an error callback" do
30
+ new_relic = double
31
+ error = RuntimeError.new("error")
32
+ allow(new_relic).to receive(:fetch).and_raise(error)
33
+ callback = double
34
+ Dboard::Collector.register_error_callback callback
35
+
36
+ expect(callback).to receive(:call).with(error)
37
+ allow(Dboard::Publisher).to receive(:publish)
38
+ allow_any_instance_of(Dboard::Collector).to receive(:puts)
39
+ Dboard::Collector.instance.update_source(:new_relic, new_relic)
40
+
41
+ # since it is a singleton, and this callbacks leaks into the other tests
42
+ Dboard::Collector.register_error_callback(lambda { |_| })
43
+ end
28
44
  end
29
45
 
30
46
  describe Dboard::Collector, "update_source" do
@@ -32,17 +48,17 @@ describe Dboard::Collector, "update_source" do
32
48
  Dboard::Collector.instance.sources.clear
33
49
  end
34
50
 
35
- it "should collect and publish data from sources" do
36
- new_relic = mock
37
- new_relic.stub!(:fetch).and_return({ :db => "100%" })
38
- Dboard::Publisher.should_receive(:publish).with(:new_relic, { :db => "100%" })
51
+ it "collects and publishes data from sources" do
52
+ new_relic = double
53
+ allow(new_relic).to receive(:fetch).and_return({ db: "100%" })
54
+ expect(Dboard::Publisher).to receive(:publish).with(:new_relic, { db: "100%" })
39
55
  Dboard::Collector.instance.update_source(:new_relic, new_relic)
40
56
  end
41
57
 
42
- it "should print out debugging info" do
43
- new_relic = mock
44
- new_relic.stub!(:fetch).and_raise(Exception.new("some error"))
45
- Dboard::Collector.instance.should_receive(:puts).twice
58
+ it "prints out debugging info" do
59
+ new_relic = double
60
+ allow(new_relic).to receive(:fetch).and_raise(Exception.new("some error"))
61
+ expect(Dboard::Collector.instance).to receive(:puts).twice
46
62
  Dboard::Collector.instance.update_source(:new_relic, new_relic)
47
63
  end
48
64
  end
@@ -1,16 +1,33 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
2
2
 
3
- describe "Publisher", "publish" do
4
-
5
- it "should send data to the dashboard server" do
6
- Dboard::Api::Client.should_receive(:post).with("/sources/new_relic", :body => { :data => { :db => "80%" }.to_json }, :timeout => 10000)
7
- Dboard::Publisher.publish(:new_relic, { :db => "80%" })
3
+ describe "Publisher", ".publish" do
4
+ it "sends data to the dashboard server" do
5
+ expect(Dboard::Api::Client).to receive(:post).with("/sources/new_relic", body: { data: { db: "80%" }.to_json }, timeout: 10000)
6
+ Dboard::Publisher.publish(:new_relic, { db: "80%" })
8
7
  end
9
8
 
10
- it "should handle and log socket errors" do
11
- Dboard::Api::Client.should_receive(:post).and_raise(SocketError.new("failed to connect"))
12
- Dboard::Publisher.should_receive(:puts).with("SocketError: failed to connect")
9
+ it "retries network errors" do
10
+ stub_request(:post, "http://api.example/sources/new_relic").
11
+ to_timeout.times(2).
12
+ to_return({ body: "OK!" })
13
+
13
14
  Dboard::Publisher.publish(:new_relic, {})
14
15
  end
15
16
 
17
+ it "raises network errors if we run out of retries" do
18
+ stub_request(:post, "http://api.example/sources/new_relic").
19
+ to_timeout.times(3).
20
+ to_return({ body: "OK!" })
21
+
22
+ expect {
23
+ Dboard::Publisher.publish(:new_relic, {})
24
+ }.to raise_error(Net::OpenTimeout)
25
+ end
26
+
27
+ # 2021-12-07: No idea why we've treated this one specially, but keeping it for now.
28
+ it "logs socket errors if we run out of retries" do
29
+ expect(Dboard::Api::Client).to receive(:post).and_raise(SocketError.new("failed to connect"))
30
+ expect(Dboard::Publisher).to receive(:puts).with("SocketError: failed to connect")
31
+ Dboard::Publisher.publish(:new_relic, {})
32
+ end
16
33
  end
data/spec/spec_helper.rb CHANGED
@@ -1,11 +1,12 @@
1
1
  require "rspec"
2
+ require 'webmock/rspec'
2
3
 
3
- ENV["RACK_ENV"] ||= 'test'
4
- ENV['API_URL'] = "http://localhost:20843"
5
- ENV['API_USER'] = 'test'
6
- ENV['API_PASSWORD'] = 'test'
4
+ ENV["RACK_ENV"] ||= "test"
5
+ ENV["API_URL"] = "http://api.example"
6
+ ENV["API_USER"] = "test"
7
+ ENV["API_PASSWORD"] = "test"
7
8
 
8
- require File.expand_path(File.join(File.dirname(__FILE__), '../lib/dboard'))
9
+ require File.expand_path(File.join(File.dirname(__FILE__), "../lib/dboard"))
9
10
 
10
- Dboard::Api::Client.basic_auth(ENV['API_USER'], ENV['API_PASSWORD'])
11
- Dboard::Api::Client.base_uri(ENV['API_URL'])
11
+ Dboard::Api::Client.basic_auth(ENV["API_USER"], ENV["API_PASSWORD"])
12
+ Dboard::Api::Client.base_uri(ENV["API_URL"])
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dboard
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.5
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joakim Kolsjö
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-08 00:00:00.000000000 Z
11
+ date: 2021-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: httparty
14
+ name: dalli
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rake
28
+ name: httparty
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: dalli
56
+ name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -80,20 +80,6 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: rspec
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - '='
88
- - !ruby/object:Gem::Version
89
- version: 2.6.0
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - '='
95
- - !ruby/object:Gem::Version
96
- version: 2.6.0
97
83
  description: Dashboard framework
98
84
  email:
99
85
  - joakim.kolsjo@gmail.com
@@ -101,8 +87,9 @@ executables: []
101
87
  extensions: []
102
88
  extra_rdoc_files: []
103
89
  files:
90
+ - ".github/workflows/ci.yml"
104
91
  - ".gitignore"
105
- - ".rvmrc"
92
+ - ".rubocop.yml"
106
93
  - Gemfile
107
94
  - LICENCE
108
95
  - README.md
@@ -116,13 +103,13 @@ files:
116
103
  - lib/publisher.rb
117
104
  - lib/version.rb
118
105
  - spec/collector_spec.rb
119
- - spec/integration_spec.rb
120
106
  - spec/publisher_spec.rb
121
107
  - spec/spec_helper.rb
122
108
  homepage: ''
123
109
  licenses:
124
110
  - MIT
125
- metadata: {}
111
+ metadata:
112
+ rubygems_mfa_required: 'true'
126
113
  post_install_message:
127
114
  rdoc_options: []
128
115
  require_paths:
@@ -138,8 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
125
  - !ruby/object:Gem::Version
139
126
  version: '0'
140
127
  requirements: []
141
- rubyforge_project:
142
- rubygems_version: 2.2.1
128
+ rubygems_version: 3.2.32
143
129
  signing_key:
144
130
  specification_version: 4
145
131
  summary: Dashboard framework
data/.rvmrc DELETED
@@ -1 +0,0 @@
1
- rvm ruby-2.1.0@dboard --create
@@ -1,54 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
2
-
3
- # Test app
4
- require 'sinatra'
5
-
6
- get "/sources" do
7
- Dboard::Api.get(params)
8
- end
9
-
10
- post "/sources/:type" do
11
- Dboard::Api.update(params)
12
- end
13
-
14
- describe "Dashboard" do
15
-
16
- def app
17
- Sinatra::Application
18
- end
19
-
20
- def start_app
21
- app.port = 20843
22
- app.environment = 'test'
23
- @app_thread = Thread.new { app.run! }
24
- sleep 1
25
- end
26
-
27
- def stop_app
28
- @app_thread && @app_thread.kill
29
- end
30
-
31
- before do
32
- ENV['API_URL'] = "http://localhost:20843"
33
- ENV['API_USER'] = 'test'
34
- ENV['API_PASSWORD'] = 'test'
35
- @new_relic = mock
36
- @new_relic.stub!(:fetch).and_return({ :db => "33.3%", :memory => "33333 MB" })
37
- Dboard::CACHE.delete "dashboard::source::new_relic"
38
- end
39
-
40
- it "should collect stats and post them to the server" do
41
- start_app
42
- body = Dboard::Api::Client.get("/sources?types=new_relic")
43
- JSON.parse(body)["sources"]["new_relic"]["data"].should == {}
44
- Dboard::Collector.instance.update_source(:new_relic, @new_relic)
45
- body = Dboard::Api::Client.get("/sources?types=new_relic")
46
- JSON.parse(body)["sources"]["new_relic"]["data"].should == { "db" => "33.3%", "memory" => "33333 MB" }
47
- end
48
-
49
- after do
50
- stop_app
51
- end
52
-
53
- end
54
-