diff --git a/.gitignore b/.gitignore index 652dfc513a..5f06417990 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ /cache/ /config/*.deployed /config/aliases +/config/cable.yml /config/config.tmp /config/crontab /config/database.yml diff --git a/Gemfile b/Gemfile index 80779b2ae2..b0de8d94d3 100644 --- a/Gemfile +++ b/Gemfile @@ -117,7 +117,6 @@ gem 'matrix', '~> 0.4.2' gem 'mini_magick', '~> 4.13.1' gem 'net-protocol', '~> 0.1.3' gem 'redcarpet', '~> 3.6.0' -gem 'redis', '~> 4.8.1' gem 'rolify', '~> 6.0.1' gem 'ruby-msg', '~> 1.5.0', git: 'https://github.com/mysociety/ruby-msg.git', branch: 'ascii-encoding' gem 'rubyzip', '~> 2.3.2' diff --git a/Gemfile.lock b/Gemfile.lock index 164980537f..9ef78f5a0f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -649,7 +649,6 @@ DEPENDENCIES rails-i18n (~> 7.0.5) recaptcha (~> 5.17.0) redcarpet (~> 3.6.0) - redis (~> 4.8.1) rolify (~> 6.0.1) rspec-activemodel-mocks (~> 1.2.1) rspec-rails (~> 7.0.1) diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb new file mode 100644 index 0000000000..d672697283 --- /dev/null +++ b/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb new file mode 100644 index 0000000000..0ff5442f47 --- /dev/null +++ b/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/app/channels/job_status_channel.rb b/app/channels/job_status_channel.rb new file mode 100644 index 0000000000..305d421c67 --- /dev/null +++ b/app/channels/job_status_channel.rb @@ -0,0 +1,5 @@ +class JobStatusChannel < ApplicationCable::Channel + def subscribed + stream_from 'job_status_channel' + end +end diff --git a/app/controllers/jobs_controller.rb b/app/controllers/jobs_controller.rb new file mode 100644 index 0000000000..4828f9cb24 --- /dev/null +++ b/app/controllers/jobs_controller.rb @@ -0,0 +1,9 @@ +class JobsController < ApplicationController + def index + end + + def start + LongRunningJob.perform_later(current_user&.id) + redirect_to jobs_path + end +end diff --git a/app/javascript/channels/consumer.js b/app/javascript/channels/consumer.js new file mode 100644 index 0000000000..940c8adb1a --- /dev/null +++ b/app/javascript/channels/consumer.js @@ -0,0 +1,2 @@ +import { createConsumer } from "@rails/actioncable" +export default createConsumer() diff --git a/app/javascript/channels/index.js b/app/javascript/channels/index.js new file mode 100644 index 0000000000..280d96a668 --- /dev/null +++ b/app/javascript/channels/index.js @@ -0,0 +1,2 @@ +// Import all the channels to be used by Action Cable +import "./job_status_channel" diff --git a/app/javascript/channels/job_status_channel.js b/app/javascript/channels/job_status_channel.js new file mode 100644 index 0000000000..939897a87f --- /dev/null +++ b/app/javascript/channels/job_status_channel.js @@ -0,0 +1,13 @@ +import consumer from "./consumer" + +consumer.subscriptions.create("JobStatusChannel", { + received(data) { + const statusElement = document.getElementById("job-status") + const progressElement = document.getElementById("job-progress") + const resultElement = document.getElementById("job-result") + + if (statusElement) statusElement.textContent = data.status + if (progressElement) progressElement.value = data.progress + if (data.result && resultElement) resultElement.textContent = data.result + } +}) diff --git a/app/javascript/modern_application.js b/app/javascript/modern_application.js index 57c70c4406..d8dd2e61f9 100644 --- a/app/javascript/modern_application.js +++ b/app/javascript/modern_application.js @@ -1,5 +1,6 @@ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails import "@hotwired/turbo-rails"; +import "channels"; import "controllers"; // Disable old jQuery UJS event handling, allowing Turbo to handle instead diff --git a/app/jobs/long_running_job.rb b/app/jobs/long_running_job.rb new file mode 100644 index 0000000000..05d47204ae --- /dev/null +++ b/app/jobs/long_running_job.rb @@ -0,0 +1,30 @@ +class LongRunningJob < ApplicationJob + queue_as :default + + def perform(_user_id) + progress = 0 + + while progress < 99 + sleep(rand(0.5..2.0)) + progress += rand(5..15) + progress = [progress, 99].min + + ActionCable.server.broadcast( + 'job_status_channel', + { + status: 'Processing...', + progress: progress + } + ) + end + + ActionCable.server.broadcast( + 'job_status_channel', + { + status: 'Completed', + progress: 100, + result: "Task completed at #{Time.current}" + } + ) + end +end diff --git a/app/views/jobs/index.html.erb b/app/views/jobs/index.html.erb new file mode 100644 index 0000000000..b370c333d4 --- /dev/null +++ b/app/views/jobs/index.html.erb @@ -0,0 +1,15 @@ +<% content_for :javascript_head do %> + <%= javascript_importmap_tags('modern_application') %> +<% end %> + +
+

Background Job Status

+ +
+

Status: Not started

+ +

Result:

+
+ + <%= button_to "Start Job", start_jobs_path, method: :post, class: 'start-button', data: { turbo: true } %> +
diff --git a/config/application.rb b/config/application.rb index b1dee98822..1f2d05fa12 100644 --- a/config/application.rb +++ b/config/application.rb @@ -11,7 +11,7 @@ # require "action_mailbox/engine" require "action_text/engine" require "action_view/railtie" -# require "action_cable/engine" +require "action_cable/engine" require "sprockets/railtie" # require "rails/test_unit/railtie" diff --git a/config/cable.yml-example b/config/cable.yml-example new file mode 100644 index 0000000000..c1b13820b4 --- /dev/null +++ b/config/cable.yml-example @@ -0,0 +1,12 @@ +development: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: alaveteli_development + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: alaveteli_production diff --git a/config/general.yml-example b/config/general.yml-example index 630a11fbe2..12c48a299f 100644 --- a/config/general.yml-example +++ b/config/general.yml-example @@ -806,6 +806,7 @@ SHARED_FILES_PATH: '' # # --- SHARED_FILES: + - config/cable.yml - config/database.yml - config/general.yml - config/sidekiq.yml diff --git a/config/importmap.rb b/config/importmap.rb index 18071f7542..c87122fd8d 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -3,8 +3,10 @@ pin "@hotwired/stimulus", to: "stimulus.min.js" pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" pin "@hotwired/turbo-rails", to: "turbo.min.js" +pin "@rails/actioncable", to: "actioncable.esm.js" pin "sortablejs" # @1.15.2 pin "modern_application" +pin_all_from "app/javascript/channels", under: "channels" pin_all_from "app/javascript/controllers", under: "controllers" pin_all_from "app/javascript/helpers", under: "helpers" diff --git a/config/routes.rb b/config/routes.rb index 14a77c88c6..4de74a0345 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -937,4 +937,10 @@ def matches?(request) end end #### + + resources :jobs, only: [:index] do + collection do + post :start + end + end end diff --git a/docker/bootstrap b/docker/bootstrap index 637b5012ae..e26312c5b5 100755 --- a/docker/bootstrap +++ b/docker/bootstrap @@ -29,6 +29,7 @@ fi success_msg 'done' notice_msg "Copying example files..." +[ ! -f config/cable.yml ] && cp config/cable.yml-example config/cable.yml [ ! -f config/database.yml ] && cp config/database.yml-example config/database.yml [ ! -f config/sidekiq.yml ] && cp config/sidekiq.yml-example config/sidekiq.yml [ ! -f config/storage.yml ] && cp config/storage.yml-example config/storage.yml diff --git a/script/install-as-user b/script/install-as-user index 5760f98f73..9f9f4e3447 100755 --- a/script/install-as-user +++ b/script/install-as-user @@ -184,6 +184,9 @@ do fi done +# add cable.yml +cp config/cable.yml-example config/cable.yml + # add sidekiq.yml cp config/sidekiq.yml-example config/sidekiq.yml