mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-03-22 05:18:51 +00:00
Add feature to render reports in the background
This is supposed to lower the memory footprint of all Puma workers. The reports code will occupy needed memory in one Sidekiq worker instead of in several Puma processes. The current code doesn't limit the execution time yet. We either need a way to terminate the report rendering after a while or send an email with a link to access a rendered report.
This commit is contained in:
@@ -692,6 +692,7 @@ Rails/ApplicationJob:
|
||||
- 'app/jobs/heartbeat_job.rb'
|
||||
- 'app/jobs/order_cycle_closing_job.rb'
|
||||
- 'app/jobs/order_cycle_notification_job.rb'
|
||||
- 'app/jobs/report_job.rb'
|
||||
- 'app/jobs/subscription_confirm_job.rb'
|
||||
- 'app/jobs/subscription_placement_job.rb'
|
||||
|
||||
|
||||
@@ -24,17 +24,17 @@ module Admin
|
||||
if report_format.present?
|
||||
export_report
|
||||
else
|
||||
render_report
|
||||
show_report
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def export_report
|
||||
send_data @report.render_as(report_format), filename: report_filename
|
||||
send_data render_report_as(report_format), filename: report_filename
|
||||
end
|
||||
|
||||
def render_report
|
||||
def show_report
|
||||
assign_view_data
|
||||
render "show"
|
||||
end
|
||||
@@ -45,12 +45,26 @@ module Admin
|
||||
@report_subtype = report_subtype
|
||||
@report_title = report_title
|
||||
@rendering_options = rendering_options
|
||||
@table = @report.to_html if render_data?
|
||||
@table = render_report_as(:html) if render_data?
|
||||
@data = Reporting::FrontendData.new(spree_current_user)
|
||||
end
|
||||
|
||||
def render_data?
|
||||
request.post?
|
||||
end
|
||||
|
||||
def render_report_as(format)
|
||||
if OpenFoodNetwork::FeatureToggle.enabled?(:background_reports, spree_current_user)
|
||||
job = ReportJob.perform_later(
|
||||
report_class, spree_current_user, params, format
|
||||
)
|
||||
sleep 1 until job.done?
|
||||
|
||||
# This result has been rendered by Rails in safe mode already.
|
||||
job.result.html_safe # rubocop:disable Rails/OutputSafety
|
||||
else
|
||||
@report.render_as(format)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
34
app/jobs/report_job.rb
Normal file
34
app/jobs/report_job.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Renders a report and saves it to a temporary file.
|
||||
class ReportJob < ActiveJob::Base
|
||||
def perform(report_class, user, params, format)
|
||||
report = report_class.new(user, params, render: true)
|
||||
result = report.render_as(format)
|
||||
write(result)
|
||||
end
|
||||
|
||||
def done?
|
||||
@done ||= File.file?(filename)
|
||||
end
|
||||
|
||||
def result
|
||||
@result ||= read_result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def write(result)
|
||||
File.write(filename, result)
|
||||
end
|
||||
|
||||
def read_result
|
||||
File.read(filename)
|
||||
ensure
|
||||
File.unlink(filename)
|
||||
end
|
||||
|
||||
def filename
|
||||
Rails.root.join("tmp/report-#{job_id}")
|
||||
end
|
||||
end
|
||||
@@ -4,7 +4,7 @@ require 'spreadsheet_architect'
|
||||
|
||||
module Reporting
|
||||
class ReportRenderer
|
||||
REPORT_FORMATS = [:csv, :json, :xlsx, :pdf].freeze
|
||||
REPORT_FORMATS = [:csv, :json, :html, :xlsx, :pdf].freeze
|
||||
|
||||
def initialize(report)
|
||||
@report = report
|
||||
|
||||
37
spec/jobs/report_job_spec.rb
Normal file
37
spec/jobs/report_job_spec.rb
Normal file
@@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe ReportJob do
|
||||
let(:report_args) { [report_class, user, params, format] }
|
||||
let(:report_class) { Reporting::Reports::UsersAndEnterprises::Base }
|
||||
let(:user) { enterprise.owner }
|
||||
let(:enterprise) { create(:enterprise) }
|
||||
let(:params) { {} }
|
||||
let(:format) { :csv }
|
||||
|
||||
it "generates a report" do
|
||||
job = ReportJob.new
|
||||
job.perform(*report_args)
|
||||
expect_csv_report(job)
|
||||
end
|
||||
|
||||
it "enqueues a job for asynch processing" do
|
||||
job = ReportJob.perform_later(*report_args)
|
||||
expect(job.done?).to eq false
|
||||
|
||||
# This performs the job in the same process but that's good enought for
|
||||
# testing the job code. I hope that we can rely on the job worker.
|
||||
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
|
||||
job.retry_job
|
||||
|
||||
expect(job.done?).to eq true
|
||||
expect_csv_report(job)
|
||||
end
|
||||
|
||||
def expect_csv_report(job)
|
||||
table = CSV.parse(job.result)
|
||||
expect(table[0][1]).to eq "Relationship"
|
||||
expect(table[1][1]).to eq "owns"
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user