diff --git a/.rubocop.yml b/.rubocop.yml
index 68fd33e..3ab92c1 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,5 +1,3 @@
-inherit_from: .rubocop_todo.yml
-
require:
- rubocop-rake
- rubocop-rspec
@@ -225,3 +223,19 @@ RSpec/ExampleLength:
RSpec/NestedGroups:
Enabled: false
+
+Metrics/AbcSize:
+ Enabled: false
+
+Naming/MethodParameterName:
+ Enabled: false
+
+Naming/FileName:
+ Exclude:
+ - 'lib/sidekiq-belt.rb'
+
+RSpec/VerifiedDoubles:
+ Enabled: false
+
+RSpec/MultipleMemoizedHelpers:
+ Enabled: false
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
deleted file mode 100644
index b84e85c..0000000
--- a/.rubocop_todo.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-# This configuration was generated by
-# `rubocop --auto-gen-config`
-# on 2023-10-11 15:02:29 UTC using RuboCop version 1.56.4.
-# The point is for the user to remove these configuration records
-# one by one as the offenses are removed from the code base.
-# Note that changes in the inspected code, or installation of new
-# versions of RuboCop, may require this file to be generated again.
-
-# Offense count: 1
-# Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, CheckDefinitionPathHierarchyRoots, Regex, IgnoreExecutableScripts, AllowedAcronyms.
-# CheckDefinitionPathHierarchyRoots: lib, spec, test, src
-# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
-Naming/FileName:
- Exclude:
- - 'lib/sidekiq-belt.rb'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8f26699..c3cfa69 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
## [Unreleased]
+## [0.2.0] - 2023-10-07
+
+- Feature to Pause/Unpause Periodic Jobs
+
## [0.1.0] - 2023-10-07
-- Run manualy Periodic Jobs
+- Feature to run manualy Periodic Jobs
diff --git a/Gemfile.lock b/Gemfile.lock
index 8381258..aa6aeaa 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
- sidekiq-belt (0.1.0)
+ sidekiq-belt (0.2.0)
sidekiq (> 7.0)
GEM
diff --git a/README.md b/README.md
index bd32b14..011795e 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,18 @@ Sidekiq::Belt.use!([:periodic_run])
### Pause Periodic Jobs (sidekiq-enterprise)
-This feature is not yet implemented.
+This option adds a button to pause and unpause the cron of a periodic job.
+When a periodic job is paused, the perform is skiped and on server this content is logged.
+
+```
+2023-10-12T19:24:00.001Z pid=127183 tid=2ian INFO: Job SomeHourlyWorkerClass is paused by Periodic Pause
+```
+
+To enable this feature, pass the `periodic_pause` option:
+
+```ruby
+Sidekiq::Belt.use!([:periodic_pause])
+```
### Delete an Unfinished Batch (sidekiq-pro)
diff --git a/lib/sidekiq/belt/ent/periodic_pause.rb b/lib/sidekiq/belt/ent/periodic_pause.rb
index 941fee0..454302c 100644
--- a/lib/sidekiq/belt/ent/periodic_pause.rb
+++ b/lib/sidekiq/belt/ent/periodic_pause.rb
@@ -1,15 +1,84 @@
# frozen_string_literal: true
+require "sidekiq/web/helpers"
+
module Sidekiq
module Belt
module Ent
module PeriodicPause
+ def paused?
+ Sidekiq.redis { |r| r.hget("PeriodicPaused", @lid.to_s) }.to_s == "p"
+ end
+
+ def pause!
+ Sidekiq.redis { |r| r.hset("PeriodicPaused", @lid.to_s, "p") }
+ end
+
+ def unpause!
+ Sidekiq.redis { |r| r.hdel("PeriodicPaused", @lid.to_s) }
+ end
+
+ module SidekiqLoopsPeriodicPause
+ PAUSE_BUTTON = <<~ERB
+
+ ERB
+
+ UNPAUSE_BUTTON = <<~ERB
+
+ ERB
+
+ def self.registered(app)
+ app.replace_content("/loops") do |content|
+ # Add the top of the table
+ content.gsub!("\n ", "<%= t('Pause/Unpause') %> | \n ")
+
+ # Add the run button
+ content.gsub!(
+ "\n \n <% end %>",
+ "\n" \
+ "<% if (loup.paused?) %>#{UNPAUSE_BUTTON}<% else %>#{PAUSE_BUTTON}<% end %>" \
+ " | \n \n <% end %>"
+ )
+ end
+
+ app.post("/loops/:lid/pause") do
+ Sidekiq::Periodic::Loop.new(params[:lid]).pause!
+
+ return redirect "#{root_path}loops"
+ end
+
+ app.post("/loops/:lid/unpause") do
+ Sidekiq::Periodic::Loop.new(params[:lid]).unpause!
+
+ return redirect "#{root_path}loops"
+ end
+ end
+ end
+
+ module PauseServer
+ def enqueue_job(cycle, ts)
+ cycle.paused? ? logger.info("Job #{cycle.klass} is paused by Periodic Pause") : super
+ end
+ end
+
def self.use!
- # require("sidekiq-ent/periodic")
- # require("sidekiq-ent/periodic/static_loop")
+ require("sidekiq-ent/web")
+ require("sidekiq-ent/periodic")
+ require("sidekiq-ent/periodic/manager")
+ require("sidekiq-ent/periodic/static_loop")
- # Sidekiq::Periodic::Loop.prepend(Sidekiq::Belt::Ent::PeriodicPause)
- # Sidekiq::Periodic::StaticLoop.prepend(Sidekiq::Belt::Ent::PeriodicPause)
+ Sidekiq::Web.register(Sidekiq::Belt::Ent::PeriodicPause::SidekiqLoopsPeriodicPause)
+ Sidekiq::Periodic::Loop.prepend(Sidekiq::Belt::Ent::PeriodicPause)
+ Sidekiq::Periodic::StaticLoop.prepend(Sidekiq::Belt::Ent::PeriodicPause)
+ Sidekiq::Periodic::Manager.prepend(Sidekiq::Belt::Ent::PeriodicPause::PauseServer)
end
end
end
diff --git a/lib/sidekiq/belt/version.rb b/lib/sidekiq/belt/version.rb
index be7a88f..ede9c71 100644
--- a/lib/sidekiq/belt/version.rb
+++ b/lib/sidekiq/belt/version.rb
@@ -2,6 +2,6 @@
module Sidekiq
module Belt
- VERSION = "0.1.0"
+ VERSION = "0.2.0"
end
end
diff --git a/spec/sidekiq/belt/ent/periodic_pause_spec.rb b/spec/sidekiq/belt/ent/periodic_pause_spec.rb
new file mode 100644
index 0000000..466ce7b
--- /dev/null
+++ b/spec/sidekiq/belt/ent/periodic_pause_spec.rb
@@ -0,0 +1,147 @@
+# frozen_string_literal: true
+
+require "sidekiq"
+require "sidekiq/web"
+require "byebug"
+
+RSpec.describe(Sidekiq::Belt::Ent::PeriodicPause) do
+ let(:redis_mock) { double("redis") }
+ let(:dummy_job_class) do
+ Class.new do
+ include Sidekiq::Worker
+ end
+ end
+ let(:loop_class) do
+ Class.new do
+ attr_accessor :lid, :klass, :options
+
+ def initialize(lid, klass, options)
+ @lid = lid
+ @klass = klass
+ @options = options
+ end
+ end
+ end
+
+ before do
+ loop_class.prepend(described_class)
+ allow(Sidekiq).to receive(:redis).and_yield(redis_mock)
+ allow(dummy_job_class).to receive(:perform_async).and_return(true)
+ stub_const("DummyJob", dummy_job_class)
+ end
+
+ describe ".paused?" do
+ context "when job is paused" do
+ before do
+ allow(redis_mock).to receive(:hget).and_return("p")
+ end
+
+ it "return true to the job" do
+ expect(loop_class.new("abc", "DummyJob", {}).paused?).to be(true)
+ end
+ end
+
+ context "when job is not paused" do
+ before do
+ allow(redis_mock).to receive(:hget).and_return(nil)
+ end
+
+ it "return true to the job" do
+ expect(loop_class.new("abc", "DummyJob", {}).paused?).to be(false)
+ end
+ end
+ end
+
+ describe ".pause!" do
+ before do
+ allow(redis_mock).to receive(:hset).and_return(true)
+ end
+
+ it "saves the job as paused" do
+ loop_class.new("abc", "DummyJob", {}).pause!
+
+ expect(redis_mock).to have_received(:hset).with("PeriodicPaused", "abc", "p")
+ end
+ end
+
+ describe ".unpause!" do
+ before do
+ allow(redis_mock).to receive(:hdel).and_return(true)
+ end
+
+ it "removes the job as unpaused" do
+ loop_class.new("abc", "DummyJob", {}).unpause!
+
+ expect(redis_mock).to have_received(:hdel).with("PeriodicPaused", "abc")
+ end
+ end
+
+ describe ".use!" do
+ before do
+ stub_const("Sidekiq::Periodic", Module.new)
+ stub_const("Sidekiq::Periodic::StaticLoop", Class.new)
+ stub_const("Sidekiq::Periodic::Loop", Class.new)
+ stub_const("Sidekiq::Periodic::Manager", Class.new)
+
+ allow(described_class).to receive(:require).and_return(true)
+ allow(Sidekiq::Web).to receive(:register)
+ allow(Sidekiq::Periodic::Loop).to receive(:prepend)
+ allow(Sidekiq::Periodic::StaticLoop).to receive(:prepend)
+ allow(Sidekiq::Periodic::Manager).to receive(:prepend)
+ end
+
+ it "injects the code" do
+ described_class.use!
+
+ expect(described_class).to have_received(:require).with("sidekiq-ent/web").once
+ expect(described_class).to have_received(:require).with("sidekiq-ent/periodic").once
+ expect(described_class).to have_received(:require).with("sidekiq-ent/periodic/manager").once
+ expect(described_class).to have_received(:require).with("sidekiq-ent/periodic/static_loop").once
+
+ expect(Sidekiq::Web).to have_received(:register).with(described_class::SidekiqLoopsPeriodicPause)
+ expect(Sidekiq::Periodic::Loop).to have_received(:prepend).with(described_class)
+ expect(Sidekiq::Periodic::StaticLoop).to have_received(:prepend).with(described_class)
+ expect(Sidekiq::Periodic::Manager).to have_received(:prepend).with(described_class::PauseServer)
+ end
+ end
+
+ describe "PauseServer.enqueue_job" do
+ let(:logger) { Logger.new($stdout) }
+ let(:instance) { dummy_pause_server.new }
+ let(:cycle) { loop_class.new("abc", "DummyJob", {}) }
+ let(:dummy_pause_server) do
+ Class.new do
+ def enqueue_job(cycle, ts)
+ [cycle, ts]
+ end
+ end
+ end
+
+ before do
+ allow(logger).to receive(:info).and_return(true)
+ allow(instance).to receive(:logger).and_return(logger)
+ dummy_pause_server.prepend(described_class::PauseServer)
+ end
+
+ context "when job is paused" do
+ before do
+ allow(redis_mock).to receive(:hget).and_return("p")
+ end
+
+ it "does not run the job" do
+ expect(cycle.paused?).to be(true)
+ expect(instance.enqueue_job(cycle, 2)).to be(true)
+
+ expect(logger).to have_received(:info).with("Job DummyJob is paused by Periodic Pause")
+ end
+ end
+
+ context "when job is not paused" do
+ it "runs the job" do
+ allow(redis_mock).to receive(:hget).and_return(nil)
+
+ expect(instance.enqueue_job(cycle, 2)).to eq([cycle, 2])
+ end
+ end
+ end
+end