rush_job_mongoid 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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 %>