rush_job_mongoid 1.0.0

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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +60 -0
  4. data/Rakefile +5 -0
  5. data/app/assets/config/rush_job_mongoid_manifest.js +4 -0
  6. data/app/assets/images/rush_job_mongoid/funnel-fill.svg +3 -0
  7. data/app/assets/images/rush_job_mongoid/funnel.svg +3 -0
  8. data/app/assets/images/rush_job_mongoid/pencil-square-dark.svg +4 -0
  9. data/app/assets/images/rush_job_mongoid/pencil-square-light.svg +4 -0
  10. data/app/assets/javascript/rush_job_mongoid/application.js +13 -0
  11. data/app/assets/javascript/rush_job_mongoid/controllers/rush_job_mongoid_filters_controller.js +9 -0
  12. data/app/assets/javascript/rush_job_mongoid/controllers/rush_job_mongoid_polling_controller.js +50 -0
  13. data/app/assets/javascript/rush_job_mongoid/controllers/rush_job_mongoid_reload_jobs_table_controller.js +7 -0
  14. data/app/assets/javascript/rush_job_mongoid/controllers/rush_job_mongoid_table_update_controller.js +19 -0
  15. data/app/assets/stylesheets/rush_job_mongoid/application.scss +16 -0
  16. data/app/assets/stylesheets/rush_job_mongoid/components/_polling.scss +3 -0
  17. data/app/assets/stylesheets/rush_job_mongoid/layout/_container.scss +4 -0
  18. data/app/controllers/rush_job_mongoid/application_controller.rb +5 -0
  19. data/app/controllers/rush_job_mongoid/dashboard_controller.rb +28 -0
  20. data/app/controllers/rush_job_mongoid/rush_jobs_controller.rb +57 -0
  21. data/app/controllers/rush_job_mongoid/settings_controller.rb +18 -0
  22. data/app/helpers/rush_job_mongoid/application_helper.rb +6 -0
  23. data/app/helpers/rush_job_mongoid/filter_helper.rb +20 -0
  24. data/app/helpers/rush_job_mongoid/settings_helper.rb +19 -0
  25. data/app/models/rush_job_mongoid/rush_job.rb +83 -0
  26. data/app/presenters/rush_job_mongoid/pagination_presenter.rb +17 -0
  27. data/app/presenters/rush_job_mongoid/queue_groups_presenter.rb +25 -0
  28. data/app/services/rush_job_mongoid/locked_jobs.rb +15 -0
  29. data/app/services/rush_job_mongoid/settings.rb +19 -0
  30. data/app/views/layouts/rush_job_mongoid/_filter_modal.html.erb +58 -0
  31. data/app/views/layouts/rush_job_mongoid/_flash_messages.html.erb +5 -0
  32. data/app/views/layouts/rush_job_mongoid/_navbar.html.erb +72 -0
  33. data/app/views/layouts/rush_job_mongoid/application.html.erb +24 -0
  34. data/app/views/rush_job_mongoid/dashboard/_dashboard_tables.html.erb +34 -0
  35. data/app/views/rush_job_mongoid/dashboard/_queues_table.html.erb +39 -0
  36. data/app/views/rush_job_mongoid/dashboard/index.html.erb +7 -0
  37. data/app/views/rush_job_mongoid/dashboard/index.js.erb +3 -0
  38. data/app/views/rush_job_mongoid/rush_jobs/_jobs_table.html.erb +43 -0
  39. data/app/views/rush_job_mongoid/rush_jobs/edit.html.erb +51 -0
  40. data/app/views/rush_job_mongoid/rush_jobs/index.html.erb +7 -0
  41. data/app/views/rush_job_mongoid/rush_jobs/index.js.erb +3 -0
  42. data/app/views/rush_job_mongoid/shared/_pagination.html.erb +89 -0
  43. data/app/views/rush_job_mongoid/shared/_pagination_link.html.erb +3 -0
  44. data/app/views/rush_job_mongoid/shared/_polling.html.erb +32 -0
  45. data/config/importmap.rb +5 -0
  46. data/config/locales/en.yml +46 -0
  47. data/config/routes.rb +7 -0
  48. data/lib/rush_job_mongoid/engine.rb +23 -0
  49. data/lib/rush_job_mongoid/version.rb +3 -0
  50. data/lib/rush_job_mongoid.rb +6 -0
  51. data/lib/tasks/rush_job_mongoid_tasks.rake +4 -0
  52. data/vendor/stylesheets/rush_job_mongoid/bootstrap.css +12273 -0
  53. metadata +364 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 965c03dc946e6df0a48d7d174e4cc88554c754ab9348f20568177790d866125b
4
+ data.tar.gz: db8783bcb66c206c1ad374799595ab58b6f13e97f0b442f2ac1ed4990efc0e01
5
+ SHA512:
6
+ metadata.gz: 24a160ff6b9a070d8d1267ce6374b5f82ce0114da5fd4f55f59d9ed48889b2f47caf2fe8e38c1383adf86cc90e7a516ee0667dfd18cd917d6cffe9e229a0ef76
7
+ data.tar.gz: 6191e7b39f8077e2ea636fc047cf7c5db2d89cd285b5f9c86e13b4dfdf27d54f26d0c926c11b75c1de66a9685e9542c23598a2cb25f460f7e1a17943deb866bb
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright JavaKoala
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # RushJobMongoid
2
+ User interface for Delayed Job Mongoid (https://github.com/collectiveidea/delayed_job_mongoid) in Ruby on Rails
3
+
4
+ <img width="656" alt="Dashboard" src="docs/assets/dashboard.png">
5
+ <img width="656" alt="Jobs" src="docs/assets/jobs.png">
6
+
7
+ ### Note
8
+ - This app uses cookies to store the selected options.
9
+
10
+ ## Usage
11
+ You will need Delayed Job, `delayed_job`, and Delayed Job Mongoid, `delayed_job_mongoid`, installed in a Ruby on Rails application for this gem to work properly.
12
+
13
+ Navigate to the `/rush_job_mongoid` route in your application to see the Delayed Jobs. Locally this would be `http://localhost:3000/rush_job_mongoid`
14
+
15
+ ### Options
16
+
17
+ #### Dark Mode
18
+
19
+ Enables or disables dark mode
20
+
21
+ #### Editing
22
+
23
+ Make sure workers are stopped before editing
24
+
25
+ #### Queues
26
+
27
+ Queues on the dashboard are eager loaded. If there are lots of queues this can affect performance. Disable queues if this becomes an issue.
28
+
29
+ ## Installation
30
+ Add this line to your Ruby on Rails application's Gemfile:
31
+
32
+ ```ruby
33
+ gem 'rush_job', '~> 1.0.0'
34
+ ```
35
+
36
+ And then execute:
37
+ ```bash
38
+ $ bundle install
39
+ ```
40
+
41
+ Add the following to your `config/routes.rb` file:
42
+ ```ruby
43
+ mount RushJob::Engine => '/rush_job_mongoid'
44
+ ```
45
+
46
+ ## Contributing
47
+ Open an issue or
48
+ 1. Fork
49
+ 2. Update
50
+ 3. Test
51
+ 1. `bundle exec rails app:test:all` and check coverage in `test/coverage/index.html`
52
+ 2. `bundle exec rubocop`
53
+ 3. `bundle exec brakeman`
54
+ 4. Open pull request
55
+
56
+ ## Upcoming
57
+ 1. Remove dependency on Rails UJS and move to Hotwire and Turbo
58
+
59
+ ## License
60
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/setup'
2
+
3
+ load 'rails/tasks/statistics.rake'
4
+
5
+ require 'bundler/gem_tasks'
@@ -0,0 +1,4 @@
1
+ //= link_directory ../stylesheets/rush_job_mongoid .scss
2
+ //= link_directory ../../../vendor/stylesheets/rush_job_mongoid .css
3
+ //= link_tree ../javascript/rush_job_mongoid .js
4
+ //= link_directory ../images/rush_job_mongoid .svg
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="rgb(255, 255, 255)" class="bi bi-funnel-fill" viewBox="0 0 16 16">
2
+ <path d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5z"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="rgb(255, 255, 255)" class="bi bi-funnel" viewBox="0 0 16 16">
2
+ <path d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2z"/>
3
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="rgb(0, 0, 0)" class="bi bi-pencil-square" viewBox="0 0 16 16">
2
+ <path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/>
3
+ <path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5z"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="rgb(255, 255, 255)" class="bi bi-pencil-square" viewBox="0 0 16 16">
2
+ <path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/>
3
+ <path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5z"/>
4
+ </svg>
@@ -0,0 +1,13 @@
1
+ import { Application } from '@hotwired/stimulus';
2
+ import Rails from '@rails/ujs';
3
+ import RushJobMongoidFiltersController from './controllers/rush_job_mongoid_filters_controller';
4
+ import RushJobMongoidPollingController from './controllers/rush_job_mongoid_polling_controller';
5
+ import RushJobMongoidReloadJobsTableController from './controllers/rush_job_mongoid_reload_jobs_table_controller';
6
+ import 'bootstrap';
7
+
8
+ Rails.start();
9
+
10
+ window.Stimulus = Application.start();
11
+ Stimulus.register('rush-job-mongoid-filter', RushJobMongoidFiltersController);
12
+ Stimulus.register('rush-job-mongoid-polling', RushJobMongoidPollingController);
13
+ Stimulus.register('rush-job-mongoid-reload-jobs-table', RushJobMongoidReloadJobsTableController);
@@ -0,0 +1,9 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ export default class RushJobMongoidFiltersController extends Controller {
4
+ static targets = ['input'];
5
+
6
+ clearForm() {
7
+ this.inputTargets.forEach((input) => input.value = '');
8
+ }
9
+ }
@@ -0,0 +1,50 @@
1
+ import { RushJobMongoidTableUpdateController } from './rush_job_mongoid_table_update_controller';
2
+
3
+ let intervalID;
4
+
5
+ export default class RushJobMongoidPollingController extends RushJobMongoidTableUpdateController {
6
+ static targets = ['pollingTime', 'pollingTimeLabel', 'pollingSlide'];
7
+
8
+ connect() {
9
+ this.pollingChange();
10
+ this.stopPolling();
11
+ }
12
+
13
+ pollingChange() {
14
+ const pollingLabelRegex = /\d+/;
15
+ const pollingTimeTargetHtml = this.pollingTimeLabelTarget.innerHTML;
16
+ const pollingLabelUpdate = pollingTimeTargetHtml.replace(pollingLabelRegex, this.pollingTime());
17
+ this.pollingTimeLabelTarget.innerHTML = pollingLabelUpdate;
18
+ }
19
+
20
+ pollingToggle() {
21
+ const pollingSlide = this.pollingSlideTarget;
22
+
23
+ if (pollingSlide.checked === true) {
24
+ this.startPolling();
25
+ } else {
26
+ this.stopPolling();
27
+ }
28
+ }
29
+
30
+ startPolling() {
31
+ this.updateJobs();
32
+
33
+ intervalID = setTimeout(() => {
34
+ this.startPolling();
35
+ }, this.pollingTime() * 1000);
36
+ }
37
+
38
+ stopPolling() {
39
+ if (intervalID) {
40
+ clearInterval(intervalID);
41
+ }
42
+ }
43
+
44
+ pollingTime() {
45
+ const pollingTimes = [3, 5, 8, 13, 21, 34, 55];
46
+ const pollingTime = this.pollingTimeTarget.value || 3;
47
+
48
+ return pollingTimes[pollingTime];
49
+ }
50
+ }
@@ -0,0 +1,7 @@
1
+ import { RushJobMongoidTableUpdateController } from './rush_job_mongoid_table_update_controller';
2
+
3
+ export default class extends RushJobMongoidTableUpdateController {
4
+ reloadJobs() {
5
+ this.updateJobs();
6
+ }
7
+ }
@@ -0,0 +1,19 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+ import Rails from '@rails/ujs';
3
+
4
+ export class RushJobMongoidTableUpdateController extends Controller {
5
+ updateJobs() {
6
+ this.blurTable();
7
+
8
+ Rails.ajax({
9
+ url: document.location.href,
10
+ type: 'GET',
11
+ dataType: 'script',
12
+ });
13
+ }
14
+
15
+ blurTable() {
16
+ const jobsContainer = document.getElementById('rush-job-mongoid-jobs');
17
+ jobsContainer.classList.add('table-refresh');
18
+ }
19
+ }
@@ -0,0 +1,16 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ */
14
+ @import "../../../vendor/stylesheets/rush_job_mongoid/bootstrap";
15
+ @import "layout/container";
16
+ @import "components/polling";
@@ -0,0 +1,3 @@
1
+ .table-refresh {
2
+ opacity: 0.3;
3
+ }
@@ -0,0 +1,4 @@
1
+ .jobs-container {
2
+ margin-left: 5%;
3
+ margin-right: 5%;
4
+ }
@@ -0,0 +1,5 @@
1
+ module RushJobMongoid
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+ end
5
+ end
@@ -0,0 +1,28 @@
1
+ module RushJobMongoid
2
+ class DashboardController < ApplicationController
3
+ include RushJobMongoid::SettingsHelper
4
+
5
+ def index
6
+ @locked_jobs_presenter = PaginationPresenter.new(params[:locked_jobs_page])
7
+ @locked_jobs = LockedJobs.new(filter_params)
8
+ @queues_presenter = QueueGroupsPresenter.new(params[:queue_groups_page]) if queue_groups_enabled?
9
+ end
10
+
11
+ def destroy
12
+ RushJob.clear_queue(queue_params[:queue], queue_params[:priority])
13
+
14
+ flash[:success] = t(:cleared_queue, queue: params[:queue])
15
+ redirect_to root_path, status: :see_other
16
+ end
17
+
18
+ private
19
+
20
+ def queue_params
21
+ params.permit(:queue, :priority)
22
+ end
23
+
24
+ def filter_params
25
+ params.permit(:doc_id, :priority, :attempts, :job_class, :arguments, :locked_by, :last_error, :queue)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,57 @@
1
+ module RushJobMongoid
2
+ class RushJobsController < ApplicationController
3
+ before_action :find_job, only: %i[edit update destroy]
4
+
5
+ def index
6
+ @pagination_presenter = PaginationPresenter.new(params[:page])
7
+ @rush_jobs = RushJob.filter(filter_params).locked_by_desc.paginate(@pagination_presenter.page, 20)
8
+ end
9
+
10
+ def edit
11
+ flash.now[:warning] = t(:edit_warning)
12
+ end
13
+
14
+ def update
15
+ if @job.update(update_params)
16
+ flash[:success] = t(:updated_job, job_id: @job.id)
17
+ redirect_to rush_jobs_path(doc_id: @job.id)
18
+ else
19
+ flash[:danger] = t(:unable_to_update, errors: @job.errors.full_messages.to_sentence)
20
+ render :edit, status: :unprocessable_entity
21
+ end
22
+ end
23
+
24
+ def destroy
25
+ @job.destroy
26
+
27
+ flash[:success] = t(:deleted_job, job_id: @job.id)
28
+ redirect_to rush_jobs_path
29
+ end
30
+
31
+ private
32
+
33
+ def find_job
34
+ @job = RushJob.find(params[:id])
35
+ rescue Mongoid::Errors::DocumentNotFound => e
36
+ Rails.logger.info e.message
37
+ flash[:danger] = t(:missing_document)
38
+ redirect_to rush_jobs_path
39
+ end
40
+
41
+ def filter_params
42
+ params.permit(:doc_id, :priority, :attempts, :job_class, :arguments, :locked_by, :last_error, :queue)
43
+ end
44
+
45
+ def update_params
46
+ params.require(:rush_job).permit(:priority,
47
+ :attempts,
48
+ :handler,
49
+ :run_at,
50
+ :locked_at,
51
+ :locked_by,
52
+ :failed_at,
53
+ :last_error,
54
+ :queue)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,18 @@
1
+ module RushJobMongoid
2
+ class SettingsController < ApplicationController
3
+ def update
4
+ change_setting if Settings::RUSH_JOB_SETTINGS[params[:setting]&.to_sym]
5
+
6
+ redirect_to root_path
7
+ end
8
+
9
+ private
10
+
11
+ def change_setting
12
+ setting_param = params[:setting]
13
+ setting_cookie = "rush_job_#{setting_param}"
14
+
15
+ cookies.permanent[setting_cookie.to_sym] = Settings.change_setting(setting_param, cookies[setting_cookie.to_sym])
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ module RushJobMongoid
2
+ module ApplicationHelper
3
+ include SettingsHelper
4
+ include FilterHelper
5
+ end
6
+ end
@@ -0,0 +1,20 @@
1
+ module RushJobMongoid
2
+ module FilterHelper
3
+ def filter_param_query
4
+ {
5
+ doc_id: params[:doc_id],
6
+ priority: params[:priority],
7
+ attempts: params[:attempts],
8
+ job_class: params[:job_class],
9
+ arguments: params[:arguments],
10
+ locked_by: params[:locked_by],
11
+ last_error: params[:last_error],
12
+ queue: params[:queue]
13
+ }
14
+ end
15
+
16
+ def filter_param_query_empty?
17
+ filter_param_query.values.join.empty?
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ module RushJobMongoid
2
+ module SettingsHelper
3
+ def current_theme
4
+ cookies[:rush_job_theme] == 'dark' ? 'dark' : 'light'
5
+ end
6
+
7
+ def invert_theme
8
+ cookies[:rush_job_theme] == 'dark' ? 'light' : 'dark'
9
+ end
10
+
11
+ def editing_enabled?
12
+ cookies[:rush_job_editing] == 'enabled'
13
+ end
14
+
15
+ def queue_groups_enabled?
16
+ cookies[:rush_job_queue_groups_presenter] != 'disabled'
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,83 @@
1
+ module RushJobMongoid
2
+ class RushJob
3
+ include Mongoid::Document
4
+
5
+ store_in collection: 'delayed_backend_mongoid_jobs'
6
+
7
+ field :priority, type: Integer, default: 0
8
+ field :attempts, type: Integer, default: 0
9
+ field :handler, type: String
10
+ field :run_at, type: Time
11
+ field :locked_at, type: Time
12
+ field :locked_by, type: String
13
+ field :failed_at, type: Time
14
+ field :last_error, type: String
15
+ field :queue, type: String
16
+
17
+ validates :priority, presence: true
18
+
19
+ scope :locked_jobs, -> { where(:locked_at.exists => true) }
20
+ scope :locked_by_desc, -> { order_by(locked_by: -1, priority: 1, run_at: 1) }
21
+ scope :paginate, ->(page, jobs_per_page) { limit(jobs_per_page).skip(jobs_per_page * (page - 1)) }
22
+ scope :by_job_class, ->(job_class) { where(handler: /#{job_class}/i) if job_class.present? }
23
+ scope :by_arguments, ->(job_arguments) { where(handler: /#{job_arguments}/i) if job_arguments.present? }
24
+ scope :by_last_error, ->(last_error) { where(last_error: /#{last_error}/i) if last_error.present? }
25
+
26
+ %i[by_doc_id by_priority by_attempts by_locked_by by_queue].each do |where_query|
27
+ define_singleton_method where_query do |arg|
28
+ param = where_query.to_s.sub('by_', '').sub('doc', '')
29
+ arg.present? ? where(param => arg) : where({})
30
+ end
31
+ end
32
+
33
+ def job_class
34
+ job_data&.fetch(:job_class)
35
+ end
36
+
37
+ def job_arguments
38
+ job_data&.fetch(:arguments).presence || ''
39
+ end
40
+
41
+ def self.queue_groups
42
+ groups = collection.aggregate([
43
+ { '$group' => {
44
+ _id: { 'queue' => '$queue', 'priority' => '$priority' }, count: { '$sum' => 1 }
45
+ } }
46
+ ]).to_a
47
+
48
+ group_result = []
49
+
50
+ groups.each do |group|
51
+ group_result << { queue: group['_id']['queue'], priority: group['_id']['priority'], count: group['count'] }
52
+ end
53
+
54
+ group_result.sort_by! { |group| [group[:priority], group[:queue]] }
55
+ end
56
+
57
+ def self.clear_queue(queue_name, queue_priority)
58
+ where(queue: queue_name, priority: queue_priority).delete_all
59
+ end
60
+
61
+ def self.filter(filter_params)
62
+ by_doc_id(filter_params[:doc_id])
63
+ .by_priority(filter_params[:priority])
64
+ .by_attempts(filter_params[:attempts])
65
+ .by_job_class(filter_params[:job_class])
66
+ .by_arguments(filter_params[:arguments])
67
+ .by_locked_by(filter_params[:locked_by])
68
+ .by_last_error(filter_params[:last_error])
69
+ .by_queue(filter_params[:queue])
70
+ end
71
+
72
+ private
73
+
74
+ def handler_hash
75
+ safe_yaml = handler.sub('!ruby/object:ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper', '')
76
+ Psych.safe_load(safe_yaml, symbolize_names: true)
77
+ end
78
+
79
+ def job_data
80
+ handler_hash&.fetch(:job_data)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,17 @@
1
+ module RushJobMongoid
2
+ class PaginationPresenter
3
+ def initialize(page_param)
4
+ @page_param = page_param
5
+ end
6
+
7
+ def page
8
+ page = @page_param&.to_i || 1
9
+
10
+ [page, 1].max
11
+ end
12
+
13
+ def pages(item_count, items_per_page)
14
+ (item_count / items_per_page.to_f).ceil
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ module RushJobMongoid
2
+ class QueueGroupsPresenter
3
+ attr_reader :queue_groups_presenter, :rush_job_queue_groups, :queue_groups
4
+
5
+ def initialize(queue_groups_page)
6
+ @queue_groups_presenter = PaginationPresenter.new(queue_groups_page)
7
+ @rush_job_queue_groups = RushJob.queue_groups
8
+ @queue_groups = queue_groups_from_presener
9
+ end
10
+
11
+ def pages_count
12
+ @queue_groups_presenter.pages(@rush_job_queue_groups.count, 10)
13
+ end
14
+
15
+ def page
16
+ @queue_groups_presenter.page
17
+ end
18
+
19
+ private
20
+
21
+ def queue_groups_from_presener
22
+ @rush_job_queue_groups[(@queue_groups_presenter.page - 1) * 10, 10]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ module RushJobMongoid
2
+ class LockedJobs
3
+ def initialize(filters = {})
4
+ @filters = filters
5
+ end
6
+
7
+ def jobs
8
+ RushJob.filter(@filters).locked_jobs
9
+ end
10
+
11
+ def paginate(page, count_per_page = 10)
12
+ jobs.locked_by_desc.paginate(page, count_per_page)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ module RushJobMongoid
2
+ class Settings
3
+ RUSH_JOB_SETTINGS = {
4
+ theme: %w[light dark],
5
+ editing: %w[disabled enabled],
6
+ queue_groups_presenter: %w[enabled disabled]
7
+ }.freeze
8
+
9
+ def self.change_setting(setting, value)
10
+ return unless RUSH_JOB_SETTINGS[setting.to_sym]
11
+
12
+ if RUSH_JOB_SETTINGS[setting.to_sym].include?(value)
13
+ (RUSH_JOB_SETTINGS[setting.to_sym] - [value]).first
14
+ else
15
+ RUSH_JOB_SETTINGS[setting.to_sym].last
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,58 @@
1
+ <div class="modal fade" id="rush-job-mongoid-filter-modal" tabindex="-1" aria-labelledby="rushJobMongoidModalLabel" aria-hidden="true">
2
+ <div class="modal-dialog">
3
+ <div class="modal-content">
4
+ <div class="modal-header">
5
+ <h1 class="modal-title fs-5" id="rushJobMongoidModalLabel"><%= t(:filter_jobs) %></h1>
6
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
7
+ </div>
8
+ <%= form_with url: request.path, method: :get, data: { controller: 'rush-job-mongoid-filter' } do |form| %>
9
+ <div class="modal-body">
10
+ <div class="mb-3">
11
+ <%= form.label :doc_id, t(:id), class: 'form-label' %>
12
+ <%= form.text_field :doc_id, value: params[:doc_id], class: 'form-control', data: { 'rush-job-mongoid-filter-target' => 'input' } %>
13
+ </div>
14
+
15
+ <div class="mb-3">
16
+ <%= form.label :priority, t(:priority), class: 'form-label' %>
17
+ <%= form.number_field :priority, value: params[:priority], class: 'form-control', data: { 'rush-job-mongoid-filter-target' => 'input' } %>
18
+ </div>
19
+
20
+ <div class="mb-3">
21
+ <%= form.label :attempts, t(:attempts), class: 'form-label' %>
22
+ <%= form.number_field :attempts, value: params[:attempts], class: 'form-control', data: { 'rush-job-mongoid-filter-target' => 'input' } %>
23
+ </div>
24
+
25
+ <div class="mb-3">
26
+ <%= form.label :job_class, t(:job_class), class: 'form-label' %>
27
+ <%= form.text_field :job_class, value: params[:job_class], class: 'form-control', data: { 'rush-job-mongoid-filter-target' => 'input' } %>
28
+ </div>
29
+
30
+ <div class="mb-3">
31
+ <%= form.label :arguments, t(:arguments), class: 'form-label' %>
32
+ <%= form.text_field :arguments, value: params[:arguments], class: 'form-control', data: { 'rush-job-mongoid-filter-target' => 'input' } %>
33
+ </div>
34
+
35
+ <div class="mb-3">
36
+ <%= form.label :locked_by, t(:locked_by), class: 'form-label' %>
37
+ <%= form.text_field :locked_by, value: params[:locked_by], class: 'form-control', data: { 'rush-job-mongoid-filter-target' => 'input' } %>
38
+ </div>
39
+
40
+ <div class="mb-3">
41
+ <%= form.label :last_error, t(:last_error), class: 'form-label' %>
42
+ <%= form.text_field :last_error, value: params[:last_error], class: 'form-control', data: { 'rush-job-mongoid-filter-target' => 'input' } %>
43
+ </div>
44
+
45
+ <div class="mb-3">
46
+ <%= form.label :queue, t(:queue), class: 'form-label' %>
47
+ <%= form.text_field :queue, value: params[:queue], class: 'form-control', data: { 'rush-job-mongoid-filter-target' => 'input' } %>
48
+ </div>
49
+ </div>
50
+ <div class="modal-footer">
51
+ <button type="button" class="btn btn-outline-secondary" data-action="click->rush-job-mongoid-filter#clearForm"><%= t(:clear) %></button>
52
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><%= t(:close) %></button>
53
+ <%= form.submit t(:filter), class: 'btn btn-primary' %>
54
+ </div>
55
+ <% end %>
56
+ </div>
57
+ </div>
58
+ </div>
@@ -0,0 +1,5 @@
1
+ <% flash.each do |type, msg| %>
2
+ <div class="alert alert-<%= type %>" role="alert">
3
+ <%= msg %>
4
+ </div>
5
+ <% end %>