diff --git a/.rubocop.yml b/.rubocop.yml index 4406e2f2..3926846b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -31,10 +31,13 @@ Metrics/MethodLength: - 'db/migrate/**' Metrics/CyclomaticComplexity: - Max: 10 + Max: 15 Metrics/PerceivedComplexity: - Max: 10 + Max: 15 + +Layout/LineLength: + AllowedPatterns: ['\A\s*#'] Metrics/AbcSize: Max: 35 diff --git a/app/controllers/send_submission_email_controller.rb b/app/controllers/send_submission_email_controller.rb index e4d1d1c8..cd61045a 100644 --- a/app/controllers/send_submission_email_controller.rb +++ b/app/controllers/send_submission_email_controller.rb @@ -17,8 +17,6 @@ class SendSubmissionEmailController < ApplicationController Submitter.find_by!(slug: params[:submitter_slug]) end - Submissions::GenerateResultAttachments.call(@submitter) if @submitter.documents.blank? - SubmitterMailer.documents_copy_email(@submitter).deliver_later! respond_to do |f| diff --git a/app/controllers/start_form_controller.rb b/app/controllers/start_form_controller.rb index 93ca4fbf..a6e0ccea 100644 --- a/app/controllers/start_form_controller.rb +++ b/app/controllers/start_form_controller.rb @@ -13,7 +13,7 @@ class StartFormController < ApplicationController def update @submitter = Submitter.where(submission: @template.submissions.where(deleted_at: nil)) - .find_or_initialize_by(**submitter_params) + .find_or_initialize_by(email: submitter_params[:email]) if @submitter.completed_at? redirect_to start_form_completed_path(@template.slug, email: submitter_params[:email]) @@ -25,7 +25,7 @@ class StartFormController < ApplicationController ua: request.user_agent ) - @submitter.build_submission(template: @template) + @submitter.submission ||= Submission.new(template: @template) if @submitter.save redirect_to submit_form_path(@submitter.slug) diff --git a/app/controllers/submissions_download_controller.rb b/app/controllers/submissions_download_controller.rb index 45b688d8..21a6076c 100644 --- a/app/controllers/submissions_download_controller.rb +++ b/app/controllers/submissions_download_controller.rb @@ -6,7 +6,7 @@ class SubmissionsDownloadController < ApplicationController def index submitter = Submitter.find_by(slug: params[:submitter_slug]) - Submissions::GenerateResultAttachments.call(submitter) if submitter.documents.blank? + Submissions::EnsureResultGenerated.call(submitter) urls = Submitters.select_attachments_for_download(submitter).map do |attachment| diff --git a/app/controllers/submit_form_controller.rb b/app/controllers/submit_form_controller.rb index c014b6d7..e1257d5b 100644 --- a/app/controllers/submit_form_controller.rb +++ b/app/controllers/submit_form_controller.rb @@ -24,6 +24,8 @@ class SubmitFormController < ApplicationController submitter.save! if submitter.completed_at? + GenerateSubmitterResultAttachmentsJob.perform_later(submitter) + submitter.submission.template.account.users.active.each do |user| SubmitterMailer.completed_email(submitter, user).deliver_later! end diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 00000000..d92ffddc --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class ApplicationJob < ActiveJob::Base +end diff --git a/app/jobs/generate_submitter_result_attachments_job.rb b/app/jobs/generate_submitter_result_attachments_job.rb new file mode 100644 index 00000000..b250b5d9 --- /dev/null +++ b/app/jobs/generate_submitter_result_attachments_job.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class GenerateSubmitterResultAttachmentsJob < ApplicationJob + def perform(submitter) + Submissions::EnsureResultGenerated.call(submitter) + end +end diff --git a/app/mailers/submitter_mailer.rb b/app/mailers/submitter_mailer.rb index d66ef55c..edf8935d 100644 --- a/app/mailers/submitter_mailer.rb +++ b/app/mailers/submitter_mailer.rb @@ -21,6 +21,9 @@ class SubmitterMailer < ApplicationMailer def documents_copy_email(submitter) @submitter = submitter + + Submissions::EnsureResultGenerated.call(@submitter) + @documents = Submitters.select_attachments_for_download(submitter) @documents.each do |attachment| diff --git a/app/models/document_generation_event.rb b/app/models/document_generation_event.rb new file mode 100644 index 00000000..95daafa6 --- /dev/null +++ b/app/models/document_generation_event.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: document_generation_events +# +# id :bigint not null, primary key +# event_name :string not null +# created_at :datetime not null +# updated_at :datetime not null +# submitter_id :bigint not null +# +# Indexes +# +# index_document_generation_events_on_submitter_id (submitter_id) +# index_document_generation_events_on_submitter_id_and_event_name (submitter_id,event_name) UNIQUE WHERE ((event_name)::text = ANY ((ARRAY['start'::character varying, 'complete'::character varying])::text[])) +# +# Foreign Keys +# +# fk_rails_... (submitter_id => submitters.id) +# +class DocumentGenerationEvent < ApplicationRecord + belongs_to :submitter + + enum :event_name, { + complete: 'complete', + fail: 'fail', + start: 'start', + retry: 'retry' + }, scope: false +end diff --git a/app/models/submitter.rb b/app/models/submitter.rb index 9ea4f8d8..cedca938 100644 --- a/app/models/submitter.rb +++ b/app/models/submitter.rb @@ -39,6 +39,8 @@ class Submitter < ApplicationRecord has_many_attached :documents has_many_attached :attachments + has_many :document_generation_events, dependent: :destroy + def status if completed_at? 'completed' diff --git a/app/views/start_form/show.html.erb b/app/views/start_form/show.html.erb index bfa58bf6..135a2f7f 100644 --- a/app/views/start_form/show.html.erb +++ b/app/views/start_form/show.html.erb @@ -33,7 +33,7 @@ <%= f.email_field :email, required: true, class: 'base-input', placeholder: 'Provide your email to start' %>
- <%= f.button button_title(title: 'Start'), class: 'base-button' %> + <%= f.button button_title(title: 'Start', disabled_with: 'Starting'), class: 'base-button' %>
<% end %> diff --git a/db/migrate/20230701075115_create_document_generation_events.rb b/db/migrate/20230701075115_create_document_generation_events.rb new file mode 100644 index 00000000..9a90140c --- /dev/null +++ b/db/migrate/20230701075115_create_document_generation_events.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class CreateDocumentGenerationEvents < ActiveRecord::Migration[7.0] + def change + create_table :document_generation_events do |t| + t.references :submitter, null: false, foreign_key: true, index: true + t.string :event_name, null: false + + t.index %i[submitter_id event_name], unique: true, where: "event_name IN ('start', 'complete')" + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index e12d44eb..2d877c24 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_06_12_182744) do +ActiveRecord::Schema[7.0].define(version: 2023_07_01_075115) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -52,6 +52,15 @@ ActiveRecord::Schema[7.0].define(version: 2023_06_12_182744) do t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true end + create_table "document_generation_events", force: :cascade do |t| + t.bigint "submitter_id", null: false + t.string "event_name", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["submitter_id", "event_name"], name: "index_document_generation_events_on_submitter_id_and_event_name", unique: true, where: "((event_name)::text = ANY ((ARRAY['start'::character varying, 'complete'::character varying])::text[]))" + t.index ["submitter_id"], name: "index_document_generation_events_on_submitter_id" + end + create_table "encrypted_configs", force: :cascade do |t| t.bigint "account_id", null: false t.string "key", null: false @@ -135,6 +144,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_06_12_182744) do add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" + add_foreign_key "document_generation_events", "submitters" add_foreign_key "encrypted_configs", "accounts" add_foreign_key "submissions", "templates" add_foreign_key "submissions", "users", column: "created_by_user_id" diff --git a/lib/submissions/ensure_result_generated.rb b/lib/submissions/ensure_result_generated.rb new file mode 100644 index 00000000..05b5a437 --- /dev/null +++ b/lib/submissions/ensure_result_generated.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Submissions + module EnsureResultGenerated + WAIT_FOR_RETRY = 2.seconds + CHECK_EVENT_INTERVAL = 1.second + CHECK_COMPLETE_TIMEOUT = 20.seconds + + WaitForCompleteTimeout = Class.new(StandardError) + + module_function + + def call(submitter) + return submitter.documents if submitter.document_generation_events.complete.exists? + + events = + DocumentGenerationEvent.uncached do + DocumentGenerationEvent.where(submitter:).order(:created_at).to_a + end + + if events.present? && events.last.event_name.in?(%w[start retry]) + wait_for_complete_or_fail(submitter) + else + submitter.document_generation_events.create!(event_name: events.present? ? :retry : :start) + + GenerateResultAttachments.call(submitter) + + submitter.document_generation_events.create!(event_name: :complete) + end + rescue ActiveRecord::RecordNotUnique + sleep WAIT_FOR_RETRY + + retry + rescue StandardError + submitter.document_generation_events.create!(event_name: :fail) + + raise + end + + def wait_for_complete_or_fail(submitter) + total_wait_time = 0 + + loop do + sleep CHECK_EVENT_INTERVAL + total_wait_time += CHECK_EVENT_INTERVAL + + last_event = + DocumentGenerationEvent.uncached do + DocumentGenerationEvent.where(submitter:).order(:created_at).last + end + + break last_event if last_event.event_name.in?(%w[complete fail]) + + raise WaitForCompleteTimeout if total_wait_time > CHECK_COMPLETE_TIMEOUT + end + end + end +end diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index 391d725d..2e551a10 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -209,9 +209,13 @@ module Submissions end def build_pdfs_index(submitter) - latest_submitter = submitter.submission.submitters - .select { |e| e.id != submitter.id && e.completed_at? } - .max_by(&:completed_at) + latest_submitter = + submitter.submission.submitters + .select(&:completed_at?) + .select { |e| e.id != submitter.id && e.completed_at <= submitter.completed_at } + .max_by(&:completed_at) + + Submissions::EnsureResultGenerated.call(latest_submitter) if latest_submitter documents = latest_submitter&.documents&.preload(:blob).to_a.presence documents ||= submitter.submission.template.documents.preload(:blob)