dboard 3.2.0 → 3.2.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 +4 -4
- data/.github/workflows/ci.yml +0 -24
- data/README.md +4 -10
- data/lib/collector.rb +13 -20
- data/lib/version.rb +1 -1
- data/spec/collector_spec.rb +0 -47
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d1b44cea534d5b5317f70d02d39b43cd2d072b3eca3446243f0b98bba7569823
|
|
4
|
+
data.tar.gz: d70dee83fae380054f04250252e9956e29630b7f05a9eb49f4f14e536891ee82
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 264dc148bf21c0dbce7173c42f412c5d55d54c27ff26124ac692485c4d7538e66dafd920adc31bb79a595b8069bcd635f07513bc7736ce81205038d5020b0e14
|
|
7
|
+
data.tar.gz: 7640bd50fab1e0a0f43641462657482ba996bff8e0185e6d76f6537a6c2fafdcfe5467201cbd8f45fe5508bc396ddcb5b5bef61ed525c82e80a39236f61237e2
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -27,27 +27,3 @@ jobs:
|
|
|
27
27
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
|
28
28
|
- name: Run tests
|
|
29
29
|
run: bundle exec rake
|
|
30
|
-
|
|
31
|
-
release:
|
|
32
|
-
if: github.ref_name == github.event.repository.default_branch
|
|
33
|
-
needs: test
|
|
34
|
-
environment: Rubygems
|
|
35
|
-
runs-on: ubuntu-latest
|
|
36
|
-
steps:
|
|
37
|
-
- uses: actions/checkout@v7
|
|
38
|
-
- name: Set up Ruby
|
|
39
|
-
uses: ruby/setup-ruby@v1
|
|
40
|
-
with:
|
|
41
|
-
ruby-version: "3.4"
|
|
42
|
-
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
|
43
|
-
- name: Publish gem
|
|
44
|
-
run: |
|
|
45
|
-
mkdir -p $HOME/.gem
|
|
46
|
-
touch $HOME/.gem/credentials
|
|
47
|
-
chmod 0600 $HOME/.gem/credentials
|
|
48
|
-
printf -- '---\n:rubygems_api_key: %s\n' "$RUBYGEMS_API_KEY" > "$HOME/.gem/credentials"
|
|
49
|
-
sed -i -E "s/(VERSION = \"[0-9.]+)\"/\1.${GITHUB_RUN_NUMBER}\"/" lib/version.rb # Add build number to version
|
|
50
|
-
gem build *.gemspec
|
|
51
|
-
gem push *.gem
|
|
52
|
-
env:
|
|
53
|
-
RUBYGEMS_API_KEY: "${{secrets.RUBYGEMS_API_KEY}}"
|
data/README.md
CHANGED
|
@@ -23,19 +23,13 @@ Things dboard do for you:
|
|
|
23
23
|
|
|
24
24
|
Refreshing sources:
|
|
25
25
|
|
|
26
|
-
Each source defines `update_interval` (seconds)
|
|
26
|
+
Each source defines `update_interval` (seconds); the collector polls it on that cadence and pushes the result to the dashboard.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
To refresh a source between scheduled polls (e.g. from an inbound webhook), call `Dboard::Collector.request_update(:key)`, where `:key` is the key the source was registered under. On-demand refreshes are throttled: the first fires immediately and rapid repeats collapse into at most one refresh per floor. The floor defaults to 30 seconds; a source can lower it with `min_update_interval` (seconds), capped at its `update_interval`.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
For a targeted refresh, pass an argument: `Dboard::Collector.request_update(:key, arg)`. A source receives it by defining `fetch(args = nil)`: `args` is `nil` for a full refresh and an array of the requested arguments for a targeted one, so the source can refresh only what changed (e.g. the single project named in a webhook).
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
`request_update` also takes an optional argument for a targeted refresh: `Dboard::Collector.request_update(:key, arg)`. The argument is opaque; the framework forwards it verbatim to the source and never inspects it. Omitting it (or passing `nil`) means a full refresh of the whole source.
|
|
35
|
-
|
|
36
|
-
A source opts in by defining `fetch(args = nil)`. It receives `nil` for a full refresh (a scheduled poll or a no-arg `request_update`) and an array of the accumulated arguments for a targeted refresh. Arguments are batched the same way triggers are coalesced: the leading refresh carries the first argument, and any arguments that arrive during the active window are delivered together in the one trailing refresh. A no-arg (full) request queued during a burst wins over pending arguments, since a full refresh already covers them. The poll shares the same clock and floor as targeted refreshes, so both together still fetch at most once per floor.
|
|
37
|
-
|
|
38
|
-
Publishing stays wholesale: the collector always replaces the stored blob for the key. A source doing a targeted refresh must therefore read its current state (e.g. from the cache) and return the full merged blob, not only the refreshed part.
|
|
32
|
+
Publishing is wholesale: the collector always replaces the stored blob for a key, so a targeted refresh must return the full merged blob (reading current state from the cache if needed), not only the part it refreshed.
|
|
39
33
|
|
|
40
34
|
Data flow:
|
|
41
35
|
|
data/lib/collector.rb
CHANGED
|
@@ -50,7 +50,8 @@ module Dboard
|
|
|
50
50
|
wait_a_little_bit_to_not_start_all_fetches_at_once
|
|
51
51
|
|
|
52
52
|
loop do
|
|
53
|
-
|
|
53
|
+
request_update(source)
|
|
54
|
+
sleep instance.update_interval
|
|
54
55
|
end
|
|
55
56
|
end
|
|
56
57
|
end
|
|
@@ -110,11 +111,15 @@ module Dboard
|
|
|
110
111
|
private
|
|
111
112
|
|
|
112
113
|
def fetch_source(instance, batch)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
full_batch?(batch) ? instance.fetch : instance.fetch(batch)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def full_batch?(batch)
|
|
118
|
+
batch.nil? || batch.any? { |entry| entry.equal?(FULL) }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def describe_batch(batch)
|
|
122
|
+
full_batch?(batch) ? "full" : "targeted: #{batch.join(", ")}"
|
|
118
123
|
end
|
|
119
124
|
|
|
120
125
|
def decide_request(active, last_update_at, floor, now)
|
|
@@ -182,25 +187,13 @@ module Dboard
|
|
|
182
187
|
}
|
|
183
188
|
return false if batch.equal?(SKIP)
|
|
184
189
|
|
|
190
|
+
started = monotonic_now
|
|
185
191
|
update_source(source, instance, batch)
|
|
192
|
+
puts "#{source} refreshed (#{describe_batch(batch)}) in #{(monotonic_now - started).round(1)}s"
|
|
186
193
|
true
|
|
187
194
|
}
|
|
188
195
|
end
|
|
189
196
|
|
|
190
|
-
def update_in_thread(source, instance)
|
|
191
|
-
puts "#{source} polling..."
|
|
192
|
-
fired = perform_refresh(source, instance, instance.update_interval)
|
|
193
|
-
time_until_next_update = @mutex.synchronize {
|
|
194
|
-
remaining = instance.update_interval - (monotonic_now - @last_update_at[source])
|
|
195
|
-
remaining < 0 ? 0 : remaining
|
|
196
|
-
}
|
|
197
|
-
puts "#{source} #{fired ? "updated" : "still fresh"}, will poll again in #{time_until_next_update} seconds (interval: #{instance.update_interval})."
|
|
198
|
-
sleep time_until_next_update
|
|
199
|
-
rescue Exception => ex
|
|
200
|
-
puts "Something failed outside the update_source method. #{ex.message}"
|
|
201
|
-
puts ex.backtrace
|
|
202
|
-
end
|
|
203
|
-
|
|
204
197
|
def min_interval_for(instance)
|
|
205
198
|
override = instance.respond_to?(:min_update_interval) ? instance.min_update_interval : DEFAULT_MIN_INTERVAL
|
|
206
199
|
[ override, instance.update_interval ].min
|
data/lib/version.rb
CHANGED
data/spec/collector_spec.rb
CHANGED
|
@@ -245,19 +245,6 @@ RSpec.describe Dboard::Collector do
|
|
|
245
245
|
end
|
|
246
246
|
|
|
247
247
|
describe "the shared clock" do
|
|
248
|
-
it "lets a manual refresh make the next poll cycle skip its fetch" do
|
|
249
|
-
source = double(:source, update_interval: 100)
|
|
250
|
-
allow(source).to receive(:fetch).and_return({ ok: true })
|
|
251
|
-
collector.register_source(:src, source)
|
|
252
|
-
use_fake_clock
|
|
253
|
-
run_workers_inline
|
|
254
|
-
|
|
255
|
-
collector.request_update(:src)
|
|
256
|
-
collector.send(:update_in_thread, :src, source)
|
|
257
|
-
|
|
258
|
-
expect(source).to have_received(:fetch).once
|
|
259
|
-
end
|
|
260
|
-
|
|
261
248
|
it "re-evaluates a scheduled trailing when a poll refreshes during its sleep" do
|
|
262
249
|
source = double(:source, update_interval: 100, min_update_interval: 10)
|
|
263
250
|
clock = FakeClock.new
|
|
@@ -387,26 +374,6 @@ RSpec.describe Dboard::Collector do
|
|
|
387
374
|
expect(Dboard::Publisher).to have_received(:publish).with(:src, { ok: true })
|
|
388
375
|
end
|
|
389
376
|
|
|
390
|
-
it "leaves pending args untouched when a poll wins and delivers them on the next on-demand refresh" do
|
|
391
|
-
source = double(:source, update_interval: 100)
|
|
392
|
-
fetch_args = []
|
|
393
|
-
allow(source).to receive(:fetch) { |*args| fetch_args << args.first; { ok: true } }
|
|
394
|
-
collector.register_source(:src, source)
|
|
395
|
-
use_fake_clock
|
|
396
|
-
workers = capture_worker_blocks
|
|
397
|
-
|
|
398
|
-
collector.request_update(:src, "a")
|
|
399
|
-
collector.request_update(:src, "p")
|
|
400
|
-
expect(collector.instance_variable_get(:@pending)[:src]).to eq([ "p" ])
|
|
401
|
-
|
|
402
|
-
collector.send(:update_in_thread, :src, source)
|
|
403
|
-
expect(collector.instance_variable_get(:@pending)[:src]).to eq([ "p" ])
|
|
404
|
-
|
|
405
|
-
workers.each(&:call)
|
|
406
|
-
|
|
407
|
-
expect(fetch_args).to eq([ nil, [ "a" ], [ "p" ] ])
|
|
408
|
-
end
|
|
409
|
-
|
|
410
377
|
it "retries the retained leading snapshot when a poll refreshes before it runs" do
|
|
411
378
|
source = double(:source, update_interval: 100)
|
|
412
379
|
fetch_args = []
|
|
@@ -421,20 +388,6 @@ RSpec.describe Dboard::Collector do
|
|
|
421
388
|
|
|
422
389
|
expect(fetch_args).to eq([ [ "p" ] ])
|
|
423
390
|
end
|
|
424
|
-
|
|
425
|
-
it "shares one clock between a partial refresh and the poll" do
|
|
426
|
-
source = double(:source, update_interval: 100)
|
|
427
|
-
allow(source).to receive(:fetch).and_return({ ok: true })
|
|
428
|
-
collector.register_source(:src, source)
|
|
429
|
-
use_fake_clock
|
|
430
|
-
run_workers_inline
|
|
431
|
-
|
|
432
|
-
collector.request_update(:src, "p")
|
|
433
|
-
collector.send(:update_in_thread, :src, source)
|
|
434
|
-
|
|
435
|
-
expect(source).to have_received(:fetch).with([ "p" ]).once
|
|
436
|
-
expect(source).to have_received(:fetch).with(no_args).exactly(0).times
|
|
437
|
-
end
|
|
438
391
|
end
|
|
439
392
|
|
|
440
393
|
describe "under real threads" do
|