jobtick 0.1.2 → 0.1.4

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
2
  SHA256:
3
- metadata.gz: dff65ac46c3a2ce39e670a9b940726f11e7f72ab5695812031417097fbf6e2d6
4
- data.tar.gz: bb6044dd0c4c43c9e956de1eec4a5f2032ba6787aebfb9f1ddae8267ff79c35a
3
+ metadata.gz: 7db7f99928881432d43b819fb1ffa2772e6a690f659b0a54ac543bbbbed8e9db
4
+ data.tar.gz: 8c67224a9ae754c411914958e3e9f8a3255bf9af5b09f2d2d2bd11ddffbf72f6
5
5
  SHA512:
6
- metadata.gz: 80ac95fe4fa508de3656ed0cc187657e9e3c2be0f68a76451dc4e1b0633a51b81e4f86ec0665163278daeb12d64e374f0496f6a1c1c7596aac673519cdc6fc87
7
- data.tar.gz: c0ce19f6cc1b5cf3f7bad98a38448bf012dafb09dd74fade6934b797ab94f05f82971d5e49cad5a3067f862b29d3cb65f373d8b139468d5912cbc7f7cdb15271
6
+ metadata.gz: 316eab73c6b4ef66752f86515113cf1f881e059c678045b47b63fc04a0dd67265753cd4c9a607db64e47b7629f71b2e107e7b0d62d959f3a1905326af42e5969
7
+ data.tar.gz: 65725c57b8f2b1f6f97f3b93b799d3a2e57f5f4fae15cb8f0232f7259d3ee9285a37f60fa0b3bf2212b6f5faef77c565863e4158fe9109c49d712e7ecaa29115
data/CHANGELOG.md CHANGED
@@ -1,4 +1,20 @@
1
- ## [Unreleased]
1
+ ## [0.1.4] - 2026-05-05
2
+
3
+ - Add `prune` configuration option — when enabled, monitors absent from the latest sync payload are permanently deleted, keeping the dashboard in sync with your schedule config
4
+ - Remove stale `.gem` build artifact from repository
5
+
6
+ ## [0.1.3] - 2026-05-05
7
+
8
+ - Add `JobTick::WheneverSetup.install!(self)` — one-line setup that overrides Whenever's built-in `runner`, `rake`, and `command` job types to send heartbeat pings automatically, with no per-job changes required
9
+ - Extract shared `Parsers.slugify` helper, remove redundant `.to_s` calls, unify guard style
10
+
11
+ ## [0.1.2] - 2026-04-30
12
+
13
+ - Add automatic ping instrumentation for Solid Queue (via `ActiveJob::Base` `around_perform` hook) and Sidekiq (via server middleware)
14
+
15
+ ## [0.1.1] - 2026-04-28
16
+
17
+ - Send `app_name` on monitor sync
2
18
 
3
19
  ## [0.1.0] - 2026-04-27
4
20
 
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # JobTick
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/jobtick.svg)](https://rubygems.org/gems/jobtick)
3
4
  [![Ruby](https://github.com/clearstack-labs/jobtick/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/clearstack-labs/jobtick/actions/workflows/main.yml)
4
5
 
5
6
  **Rails job monitoring for Whenever, Solid Queue, and Sidekiq.**
@@ -57,6 +58,19 @@ That's it. On next deploy, JobTick reads your schedule config, registers a monit
57
58
 
58
59
  No changes to individual job files. No manual monitor creation. No names to keep in sync.
59
60
 
61
+ ### Removing stale monitors automatically
62
+
63
+ By default, monitors are only added — nothing is removed when you delete a job from your schedule. To have each deploy also clean up monitors that are no longer in your config, enable pruning:
64
+
65
+ ```ruby
66
+ JobTick.configure do |config|
67
+ config.api_key = ENV['JOBTICK_API_KEY']
68
+ config.prune = true # remove monitors absent from the latest sync
69
+ end
70
+ ```
71
+
72
+ With `prune` enabled, a deploy acts as the single source of truth: any monitor whose key is not present in the current payload is permanently deleted. You can also remove individual monitors manually from the JobTick dashboard at any time.
73
+
60
74
  ---
61
75
 
62
76
  ## What gets monitored
@@ -97,25 +111,34 @@ JobTick installs a server middleware that wraps every job execution. For native
97
111
 
98
112
  ### Whenever (`config/schedule.rb`)
99
113
 
100
- Whenever schedules jobs as cron shell commands, so there is no Ruby hook point to instrument automatically. One setup step is required: run the generator and update your `config/schedule.rb` to use the `jobtick_runner` and `jobtick_rake` job types instead of the built-in `runner` and `rake`:
114
+ Whenever schedules jobs as cron shell commands, so there is no Ruby hook point to instrument automatically. Add one line to `config/schedule.rb`:
101
115
 
102
- ```
103
- bundle exec rake jobtick:whenever:setup
116
+ ```ruby
117
+ JobTick::WheneverSetup.install!(self)
104
118
  ```
105
119
 
106
- This prints the two `job_type` definitions to add to the top of your schedule file, then use them in place of the standard types:
120
+ This overrides the built-in `runner`, `rake`, and `command` job types to wrap every execution with `curl` pings. Your existing schedule entries need no changes:
107
121
 
108
122
  ```ruby
123
+ JobTick::WheneverSetup.install!(self)
124
+
109
125
  every 1.day, at: '2:00 am' do
110
- jobtick_runner 'InvoiceJob.perform_later', monitor_key: 'whenever.invoice_job'
126
+ runner 'InvoiceJob.perform_later'
111
127
  end
112
128
 
113
129
  every :hour do
114
- jobtick_rake 'reports:sync', monitor_key: 'whenever.reports_sync'
130
+ rake 'reports:sync'
115
131
  end
116
132
  ```
117
133
 
118
- The job types wrap execution with `curl` pings to the JobTick API, so no changes to individual job classes are needed.
134
+ After adding the line, run `whenever --update-crontab` as normal and all jobs will start sending heartbeats.
135
+
136
+ If jobtick is not already loaded via your Rails environment, require it first:
137
+
138
+ ```ruby
139
+ require 'jobtick/whenever_setup'
140
+ JobTick::WheneverSetup.install!(self)
141
+ ```
119
142
 
120
143
  ---
121
144
 
@@ -123,7 +146,7 @@ The job types wrap execution with `curl` pings to the JobTick API, so no changes
123
146
 
124
147
  **Silent failure detection** — alerts when a job stops running entirely, not just when it raises an exception. The failure mode your error monitor misses.
125
148
 
126
- **Auto-sync on deploy** — add a job to your schedule, it appears in your dashboard at next deploy. Remove a job, its monitor is automatically retired.
149
+ **Auto-sync on deploy** — add a job to your schedule, it appears in your dashboard at next deploy. Enable `config.prune = true` to automatically retire monitors when jobs are removed from your schedule.
127
150
 
128
151
  **Run history** — see every execution: start time, duration, exit status. Spot when a job starts getting slower before it becomes a problem.
129
152
 
@@ -137,17 +160,16 @@ The job types wrap execution with `curl` pings to the JobTick API, so no changes
137
160
 
138
161
  - Ruby >= 3.3
139
162
  - Rails >= 7.0
140
- - One or more of: Whenever, Solid Queue, Sidekiq
141
-
142
- ---
163
+ - One or more of:
143
164
 
144
- ## Status
165
+ | Adapter | Supported versions |
166
+ |---|---|
167
+ | Solid Queue | >= 0.1 |
168
+ | Sidekiq | >= 6 |
169
+ | sidekiq-cron | >= 1.0 |
170
+ | Whenever | >= 0.10 |
145
171
 
146
- > **JobTick is currently in development.**
147
- > Sign up for early access at [jobtick.app](https://jobtick.app).
148
- > Launching June 2026.
149
-
150
- If you want to follow along or give early feedback, open an issue or watch the repo.
172
+ ---
151
173
 
152
174
  ---
153
175
 
@@ -19,12 +19,13 @@ module JobTick
19
19
  post("/ping/#{monitor_key}", payload)
20
20
  end
21
21
 
22
- def register(monitors, app_name: nil)
22
+ def register(monitors, app_name: nil, prune: false)
23
23
  return unless JobTick.config.enabled
24
24
  return if JobTick.config.api_key.nil?
25
25
 
26
26
  payload = { monitors: monitors }
27
27
  payload[:app_name] = app_name if app_name && !app_name.empty?
28
+ payload[:prune] = true if prune
28
29
  post("/monitors/sync", payload)
29
30
  end
30
31
 
@@ -2,12 +2,13 @@
2
2
 
3
3
  module JobTick
4
4
  class Configuration
5
- attr_accessor :api_key, :endpoint, :environment, :enabled
5
+ attr_accessor :api_key, :endpoint, :environment, :enabled, :prune
6
6
 
7
7
  def initialize
8
8
  @endpoint = "https://api.jobtick.app/v1"
9
9
  @environment = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "production"
10
10
  @enabled = @environment == "production"
11
+ @prune = false
11
12
  end
12
13
  end
13
14
  end
@@ -6,11 +6,9 @@ module JobTick
6
6
  def self.included(base)
7
7
  base.around_perform do |job, block|
8
8
  key = JobTick.monitor_key_for(job.class.name)
9
- if key
10
- JobTick::Monitor.run(key) { block.call }
11
- else
12
- block.call
13
- end
9
+ next block.call unless key
10
+
11
+ JobTick::Monitor.run(key) { block.call }
14
12
  end
15
13
  end
16
14
  end
@@ -3,14 +3,14 @@
3
3
  module JobTick
4
4
  module Middleware
5
5
  class Sidekiq
6
- def call(_worker, job, _queue)
6
+ def call(_worker, job, _queue, &)
7
7
  # Active Job wrappers are handled by the around_perform hook
8
8
  return yield if job["wrapped"]
9
9
 
10
10
  key = JobTick.monitor_key_for(job["class"])
11
11
  return yield unless key
12
12
 
13
- JobTick::Monitor.run(key) { yield }
13
+ JobTick::Monitor.run(key, &)
14
14
  end
15
15
 
16
16
  def self.install
@@ -2,6 +2,10 @@
2
2
 
3
3
  module JobTick
4
4
  module Parsers
5
+ def self.slugify(str)
6
+ str.downcase.gsub(/[^a-z0-9]+/, "_").gsub(/\A_+|_+\z/, "")
7
+ end
8
+
5
9
  class Sidekiq
6
10
  def self.parse
7
11
  return [] unless defined?(::Sidekiq)
@@ -16,7 +20,7 @@ module JobTick
16
20
 
17
21
  def self.parse_cron_jobs
18
22
  ::Sidekiq::Cron::Job.all.map do |job|
19
- { key: "sidekiq.#{slugify(job.name)}", schedule: job.cron, source: "sidekiq", task: job.klass }
23
+ { key: "sidekiq.#{Parsers.slugify(job.name)}", schedule: job.cron, source: "sidekiq", task: job.klass }
20
24
  end
21
25
  end
22
26
  private_class_method :parse_cron_jobs
@@ -24,17 +28,12 @@ module JobTick
24
28
  def self.parse_periodic_jobs
25
29
  periodic = sidekiq_periodic_config
26
30
  (periodic || []).map do |klass, opts|
27
- { key: "sidekiq.#{slugify(klass.to_s)}", schedule: opts[:cron] || opts[:every].to_s,
31
+ { key: "sidekiq.#{Parsers.slugify(klass.to_s)}", schedule: opts[:cron] || opts[:every].to_s,
28
32
  source: "sidekiq", task: klass.to_s }
29
33
  end
30
34
  end
31
35
  private_class_method :parse_periodic_jobs
32
36
 
33
- def self.slugify(str)
34
- str.downcase.gsub(/[^a-z0-9]+/, "_").gsub(/\A_+|_+\z/, "")
35
- end
36
- private_class_method :slugify
37
-
38
37
  def self.sidekiq_periodic_config
39
38
  if ::Sidekiq.respond_to?(:default_configuration)
40
39
  ::Sidekiq.default_configuration[:periodic]
@@ -26,9 +26,7 @@ module JobTick
26
26
  end
27
27
 
28
28
  def self.job_key(job)
29
- task = job[:task].to_s.strip
30
- slug = task.downcase.gsub(/[^a-z0-9]+/, "_").gsub(/\A_+|_+\z/, "")
31
- "whenever.#{slug}"
29
+ "whenever.#{Parsers.slugify(job[:task].to_s.strip)}"
32
30
  end
33
31
  private_class_method :job_key
34
32
  end
@@ -16,7 +16,9 @@ module JobTick
16
16
  return [] if monitors.empty?
17
17
 
18
18
  app_name = Rails.application.class.module_parent_name if defined?(Rails)
19
- JobTick.client.register(monitors, app_name: app_name)
19
+ options = { app_name: app_name }
20
+ options[:prune] = true if JobTick.config.prune
21
+ JobTick.client.register(monitors, **options)
20
22
  monitors
21
23
  end
22
24
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JobTick
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.4"
5
5
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jobtick"
4
+
5
+ module JobTick
6
+ # Overrides Whenever's built-in job types to wrap execution with jobtick pings.
7
+ #
8
+ # Usage — add one line to config/schedule.rb:
9
+ #
10
+ # JobTick::WheneverSetup.install!(self)
11
+ #
12
+ # This replaces the :runner, :rake, and :command job types so that every
13
+ # scheduled job automatically sends started/completed/failed heartbeats without
14
+ # any per-job configuration.
15
+ module WheneverSetup
16
+ def self.install!(schedule)
17
+ endpoint = JobTick.config.endpoint
18
+
19
+ schedule.job_type :runner, wrap(endpoint, "cd :path && bundle exec rails runner ':task' :output")
20
+ schedule.job_type :rake, wrap(endpoint, "cd :path && bundle exec rake :task :output")
21
+ schedule.job_type :command, wrap(endpoint, "cd :path && :task :output")
22
+ end
23
+
24
+ def self.wrap(endpoint, inner_cmd)
25
+ # Shell equivalent of Parsers.slugify: downcase, collapse non-alnum runs to _, strip leading/trailing _.
26
+ sed = "sed 's/[^a-z0-9][^a-z0-9]*/_/g' | sed 's/^_*//;s/_*$//'"
27
+ slug = "$(printf '%s' ':task' | tr '[:upper:]' '[:lower:]' | #{sed})"
28
+ key_assign = %(JOBTICK_KEY="whenever.#{slug}")
29
+
30
+ "#{key_assign} ; " \
31
+ "curl -sf \"#{endpoint}/ping/$JOBTICK_KEY/started\" ; " \
32
+ "#{inner_cmd} && " \
33
+ "curl -sf \"#{endpoint}/ping/$JOBTICK_KEY/completed\" || " \
34
+ "curl -sf \"#{endpoint}/ping/$JOBTICK_KEY/failed\""
35
+ end
36
+ private_class_method :wrap
37
+ end
38
+ end
data/lib/jobtick.rb CHANGED
@@ -37,12 +37,10 @@ module JobTick
37
37
  @monitor_map ||= {}
38
38
  end
39
39
 
40
- def monitor_map=(map)
41
- @monitor_map = map
42
- end
40
+ attr_writer :monitor_map
43
41
 
44
42
  def monitor_key_for(class_name)
45
- monitor_map[class_name.to_s]
43
+ monitor_map[class_name]
46
44
  end
47
45
 
48
46
  def reset!
@@ -9,26 +9,20 @@ namespace :jobtick do
9
9
  end
10
10
 
11
11
  namespace :whenever do
12
- desc "Print Whenever job_type wrappers to add to config/schedule.rb for heartbeat injection"
12
+ desc "Print the line to add to config/schedule.rb to enable JobTick heartbeat injection"
13
13
  task :setup do
14
- endpoint = JobTick.config.endpoint
15
14
  puts <<~RUBY
16
- # Add to config/schedule.rb to enable JobTick heartbeat injection for Whenever jobs:
15
+ # Add to config/schedule.rb to enable JobTick heartbeat injection for all Whenever jobs:
17
16
 
18
- job_type :jobtick_runner, %(curl -sf "#{endpoint}/ping/:monitor_key/started" ; ) \\
19
- %(bundle exec rails runner ':task' :output && ) \\
20
- %(curl -sf "#{endpoint}/ping/:monitor_key/completed" || ) \\
21
- %(curl -sf "#{endpoint}/ping/:monitor_key/failed")
17
+ JobTick::WheneverSetup.install!(self)
22
18
 
23
- job_type :jobtick_rake, %(curl -sf "#{endpoint}/ping/:monitor_key/started" ; ) \\
24
- %(bundle exec rake :task :output && ) \\
25
- %(curl -sf "#{endpoint}/ping/:monitor_key/completed" || ) \\
26
- %(curl -sf "#{endpoint}/ping/:monitor_key/failed")
27
-
28
- # Then use jobtick_runner / jobtick_rake instead of runner / rake, e.g.:
29
- # every 1.hour do
30
- # jobtick_runner "InvoiceJob.perform_later", monitor_key: "invoice_job"
31
- # end
19
+ # This overrides the built-in runner, rake, and command job types so that
20
+ # every scheduled job automatically sends started/completed/failed heartbeats.
21
+ # No per-job changes are required.
22
+ #
23
+ # If jobtick is not already loaded via your Rails environment, add:
24
+ # require "jobtick/whenever_setup"
25
+ # before the install! call.
32
26
  RUBY
33
27
  end
34
28
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jobtick
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clearstack Labs
@@ -35,7 +35,6 @@ files:
35
35
  - LICENSE.txt
36
36
  - README.md
37
37
  - Rakefile
38
- - jobtick-0.0.1.gem
39
38
  - lib/jobtick.rb
40
39
  - lib/jobtick/client.rb
41
40
  - lib/jobtick/configuration.rb
@@ -48,6 +47,7 @@ files:
48
47
  - lib/jobtick/railtie.rb
49
48
  - lib/jobtick/registry.rb
50
49
  - lib/jobtick/version.rb
50
+ - lib/jobtick/whenever_setup.rb
51
51
  - lib/tasks/jobtick.rake
52
52
  - sig/jobtick.rbs
53
53
  homepage: https://jobtick.app
data/jobtick-0.0.1.gem DELETED
Binary file