Skip to content

Commit

Permalink
Adding NBP push connector
Browse files Browse the repository at this point in the history
  • Loading branch information
Mathis-Z committed Jun 25, 2024
1 parent 2bb08c1 commit 065f0a4
Show file tree
Hide file tree
Showing 25 changed files with 641 additions and 5 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ gem 'sassc-rails'
gem 'shakapacker', '8.0.0'
gem 'simple_form'
gem 'slim-rails'
gem 'solid_queue'
gem 'sprockets-rails'
gem 'terser'
gem 'turbolinks'
Expand Down
13 changes: 13 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ GEM
docile (1.4.0)
drb (2.2.1)
erubi (1.13.0)
et-orbi (1.2.11)
tzinfo
execjs (2.9.1)
factory_bot (6.4.6)
activesupport (>= 5.0.0)
Expand All @@ -164,6 +166,9 @@ GEM
faraday-net_http (3.1.0)
net-http
ffi (1.17.0)
fugit (1.11.0)
et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4)
glob (0.4.1)
globalid (1.2.1)
activesupport (>= 6.1)
Expand Down Expand Up @@ -314,6 +319,7 @@ GEM
rspec-expectations (~> 3.12)
rspec-mocks (~> 3.12)
rspec-support (~> 3.12)
raabro (1.4.0)
racc (1.8.0)
rack (3.1.3)
rack-mini-profiler (3.3.1)
Expand Down Expand Up @@ -500,6 +506,12 @@ GEM
slim_lint (0.27.0)
rubocop (>= 1.0, < 2.0)
slim (>= 3.0, < 6.0)
solid_queue (0.3.3)
activejob (>= 7.1)
activerecord (>= 7.1)
concurrent-ruby (>= 1.3.1)
fugit (~> 1.11.0)
railties (>= 7.1)
sorted_set (1.0.3)
rbtree
set (~> 1.0)
Expand Down Expand Up @@ -629,6 +641,7 @@ DEPENDENCIES
simplecov
slim-rails
slim_lint
solid_queue
sprockets-rails
stackprof
terser
Expand Down
5 changes: 5 additions & 0 deletions app/jobs/application_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class ApplicationJob < ActiveJob::Base
include ActiveRecordLogging
end
20 changes: 20 additions & 0 deletions app/jobs/concerns/active_record_logging.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

# This module is used to log ActiveRecord queries performed in jobs.
module ActiveRecordLogging
extend ActiveSupport::Concern

included do
around_perform do |_job, block|
# With our current Solid Queue setup, there is a difference between both logger:
# - *ActiveRecord::Base.logger*: This logger is used for SQL queries and, normally, writes to the log file only.
# - *Rails.logger*: The regular logger, which writes to the log file and the console.
# For the duration of the job, we want to write the SQL queries to the Rails logger, so they show up in the console.
# See config/solid_queue_logging.rb for more information.
previous_logger = ActiveRecord::Base.logger
ActiveRecord::Base.logger = Rails.logger
block.call
ActiveRecord::Base.logger = previous_logger

Check warning on line 17 in app/jobs/concerns/active_record_logging.rb

View check run for this annotation

Codecov / codecov/patch

app/jobs/concerns/active_record_logging.rb#L14-L17

Added lines #L14 - L17 were not covered by tests
end
end
end
11 changes: 11 additions & 0 deletions app/jobs/nbp_delete_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

class NbpDeleteJob < ApplicationJob
retry_on Faraday::Error, wait: :polynomially_longer

def perform(task_uuid)
Nbp::PushConnector.instance.delete_task!(task_uuid)

Check warning on line 7 in app/jobs/nbp_delete_job.rb

View check run for this annotation

Codecov / codecov/patch

app/jobs/nbp_delete_job.rb#L7

Added line #L7 was not covered by tests

Rails.logger.debug { "Task with UUID #{task_uuid} deleted from NBP" }

Check warning on line 9 in app/jobs/nbp_delete_job.rb

View check run for this annotation

Codecov / codecov/patch

app/jobs/nbp_delete_job.rb#L9

Added line #L9 was not covered by tests
end
end
9 changes: 9 additions & 0 deletions app/jobs/nbp_push_all_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

class NbpPushAllJob < ApplicationJob
def perform
Task.find_each(batch_size: 50) do |task|
NbpPushJob.perform_later task
end

Check warning on line 7 in app/jobs/nbp_push_all_job.rb

View check run for this annotation

Codecov / codecov/patch

app/jobs/nbp_push_all_job.rb#L5-L7

Added lines #L5 - L7 were not covered by tests
end
end
13 changes: 13 additions & 0 deletions app/jobs/nbp_push_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

class NbpPushJob < ApplicationJob
retry_on Faraday::Error, wait: :polynomially_longer, attempts: 5

def perform(task)
builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') {|xml| LomService::ExportLom.call(task:, xml:) }

Nbp::PushConnector.instance.push_lom!(builder.to_xml)

Check warning on line 9 in app/jobs/nbp_push_job.rb

View check run for this annotation

Codecov / codecov/patch

app/jobs/nbp_push_job.rb#L9

Added line #L9 was not covered by tests

Rails.logger.debug { "Task ##{task.id} \"#{task}\" pushed to NBP" }

Check warning on line 11 in app/jobs/nbp_push_job.rb

View check run for this annotation

Codecov / codecov/patch

app/jobs/nbp_push_job.rb#L11

Added line #L11 was not covered by tests
end
end
21 changes: 20 additions & 1 deletion app/models/task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
class Task < ApplicationRecord
acts_as_taggable_on :state

before_validation :lowercase_language
after_destroy_commit :remove_metadata_from_nbp, if: -> { Nbp::PushConnector.enabled? }
after_save_commit :sync_metadata_with_nbp, if: -> { Nbp::PushConnector.enabled? }

validates :title, presence: true

validates :uuid, uniqueness: true

before_validation :lowercase_language
validates :language, format: {with: /\A[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*\z/, message: :not_de_or_us}
validate :primary_language_tag_in_iso639?

Expand Down Expand Up @@ -102,6 +105,22 @@ def self.ransackable_associations(_auth_object = nil)
%w[labels]
end

def sync_metadata_with_nbp
if access_level_public?
publish_metadata_to_nbp
elsif saved_change_to_access_level?
remove_metadata_from_nbp
end
end

def publish_metadata_to_nbp
NbpPushJob.perform_later self
end

def remove_metadata_from_nbp
NbpDeleteJob.perform_later uuid
end

# This method creates a duplicate while leaving permissions and ownership unchanged
def duplicate
dup.tap do |task|
Expand Down
3 changes: 3 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,8 @@ class Application < Rails::Application

# Fix invalid Content-Type header for incoming requests made by edu-sharing.
config.middleware.insert_before 0, Middleware::EduSharingContentType

# Configure some defaults for the Solid Queue Supervisor
require_relative 'solid_queue_defaults'
end
end
4 changes: 4 additions & 0 deletions config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false

# Use a real queuing backend for Active Job (and separate queues per environment).
config.active_job.queue_adapter = :solid_queue
config.active_job.queue_name_prefix = 'codeharbor_development'

config.action_mailer.perform_caching = false

# Print deprecation notices to the Rails logger.
Expand Down
4 changes: 2 additions & 2 deletions config/environments/production.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@
# config.cache_store = :mem_cache_store

# Use a real queuing backend for Active Job (and separate queues per environment).
# config.active_job.queue_adapter = :resque
# config.active_job.queue_name_prefix = "codeharbor_production"
config.active_job.queue_adapter = :solid_queue
config.active_job.queue_name_prefix = 'codeharbor_production'

config.action_mailer.perform_caching = false

Expand Down
4 changes: 4 additions & 0 deletions config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
# Store uploaded files on the local file system in a temporary directory.
config.active_storage.service = :test

# Use a real queuing backend for Active Job (and separate queues per environment).
config.active_job.queue_adapter = :solid_queue
config.active_job.queue_name_prefix = 'codeharbor_test'

config.action_mailer.perform_caching = false

# Tell Action Mailer not to deliver emails to the real world.
Expand Down
1 change: 1 addition & 0 deletions config/schedule.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@

every 1.day, at: '3:00 am' do
rake 'import_cache_files:cleanup'
SolidQueue::Job.clear_finished_in_batches
end
11 changes: 11 additions & 0 deletions config/settings/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,14 @@ omniauth:
private_key: ~
oai_pmh:
admin_mail: admin@example.org
nbp:
push_connector:
enable: true
client_id: testing_client_id
client_secret: testing_client_secret
token_path: 'https://test.provider/token'
api_host: 'https://test.api.host'
source:
organization: test_organization
name: CodeHarbor
slug: CoHaP2
18 changes: 18 additions & 0 deletions config/solid_queue.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# default: &default
# dispatchers:
# - polling_interval: 1
# batch_size: 500
# workers:
# - queues: "*"
# threads: 3
# processes: 1
# polling_interval: 0.1
#
# development:
# <<: *default
#
# test:
# <<: *default
#
# production:
# <<: *default
39 changes: 39 additions & 0 deletions config/solid_queue_defaults.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

# This file must be loaded before other initializers due to the logging configuration.
Rails.application.configure do
# On shutdown, jobs Solid Queue will wait the specified timeout before forcefully shutting down.
# Any job not finished by then will be picked up again after a restart.
config.solid_queue.shutdown_timeout = 10.seconds
# Remove *successful* jobs from the database after 30 days
config.solid_queue.clear_finished_jobs_after = 30.days
config.solid_queue.supervisor_pidfile = Rails.root.join('tmp/pids/solid_queue_supervisor.pid')

# For Solid Queue, we want to hide regular SQL queries from the console, but still log them to a separate file.
# For the normal webserver, this dedicated setup is neither needed nor desired.
next unless Rake.application.top_level_tasks.to_s.include?('solid_queue:')

# Specify that all logs should be written to the specified log file
file_name = "#{Rails.env}.solid_queue.log"
config.paths.add 'log', with: "log/#{file_name}"

# Send all logs regarding SQL queries to the log file.
# This will include all queries performed by Solid Queue including periodic job checks.
log_file = ActiveSupport::Logger.new(Rails.root.join('log', file_name))
config.active_record.logger = ActiveSupport::BroadcastLogger.new(log_file)

config.after_initialize do
# Create a new logger that will write to the console
console = ActiveSupport::Logger.new($stdout)
console.level = Rails.logger.level
# Enable this line to have the same log format as Rails.logger
# It will include the job name, the job ID for each line
# console.formatter = Rails.logger.formatter

ActiveSupport.on_load :solid_queue_record do
# Once SolidQueue is loaded, we can broadcast its logs to the console, too.
# Due to the initialization order, this will effectively start logging once SolidQueue is about to start.
Rails.logger.broadcast_to console
end
end
end
103 changes: 103 additions & 0 deletions db/migrate/20240609104039_create_solid_queue_tables.solid_queue.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# frozen_string_literal: true

# This migration comes from solid_queue (originally 20231211200639)
class CreateSolidQueueTables < ActiveRecord::Migration[7.0]
def change
create_table :solid_queue_jobs do |t|
t.string :queue_name, null: false
t.string :class_name, null: false, index: true
t.text :arguments
t.integer :priority, default: 0, null: false
t.string :active_job_id, index: true
t.datetime :scheduled_at
t.datetime :finished_at, index: true
t.string :concurrency_key

t.timestamps

t.index %i[queue_name finished_at], name: 'index_solid_queue_jobs_for_filtering'
t.index %i[scheduled_at finished_at], name: 'index_solid_queue_jobs_for_alerting'
end

create_table :solid_queue_scheduled_executions do |t|
t.references :job, index: {unique: true}, null: false
t.string :queue_name, null: false
t.integer :priority, default: 0, null: false
t.datetime :scheduled_at, null: false

t.datetime :created_at, null: false

t.index %i[scheduled_at priority job_id], name: 'index_solid_queue_dispatch_all'
end

create_table :solid_queue_ready_executions do |t|
t.references :job, index: {unique: true}, null: false
t.string :queue_name, null: false
t.integer :priority, default: 0, null: false

t.datetime :created_at, null: false

t.index %i[priority job_id], name: 'index_solid_queue_poll_all'
t.index %i[queue_name priority job_id], name: 'index_solid_queue_poll_by_queue'
end

create_table :solid_queue_claimed_executions do |t|
t.references :job, index: {unique: true}, null: false
t.bigint :process_id
t.datetime :created_at, null: false

t.index %i[process_id job_id]
end

create_table :solid_queue_blocked_executions do |t|
t.references :job, index: {unique: true}, null: false
t.string :queue_name, null: false
t.integer :priority, default: 0, null: false
t.string :concurrency_key, null: false
t.datetime :expires_at, null: false

t.datetime :created_at, null: false

t.index %i[expires_at concurrency_key], name: 'index_solid_queue_blocked_executions_for_maintenance'
end

create_table :solid_queue_failed_executions do |t|
t.references :job, index: {unique: true}, null: false
t.text :error
t.datetime :created_at, null: false
end

create_table :solid_queue_pauses do |t|
t.string :queue_name, null: false, index: {unique: true}
t.datetime :created_at, null: false
end

create_table :solid_queue_processes do |t|
t.string :kind, null: false
t.datetime :last_heartbeat_at, null: false, index: true
t.bigint :supervisor_id, index: true

t.integer :pid, null: false
t.string :hostname
t.text :metadata

t.datetime :created_at, null: false
end

create_table :solid_queue_semaphores do |t|
t.string :key, null: false, index: {unique: true}
t.integer :value, default: 1, null: false
t.datetime :expires_at, null: false, index: true

t.timestamps

t.index %i[key value], name: 'index_solid_queue_semaphores_on_key_and_value'
end

add_foreign_key :solid_queue_blocked_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
add_foreign_key :solid_queue_claimed_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
add_foreign_key :solid_queue_failed_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
add_foreign_key :solid_queue_ready_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
add_foreign_key :solid_queue_scheduled_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

# This migration comes from solid_queue (originally 20240110143450)
class AddMissingIndexToBlockedExecutions < ActiveRecord::Migration[7.1]
def change
add_index :solid_queue_blocked_executions, %i[concurrency_key priority job_id], name: 'index_solid_queue_blocked_executions_for_release'
end
end
Loading

0 comments on commit 065f0a4

Please sign in to comment.