From 534c3a2e101bcab9041820e0465c3c42544332d7 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 13 Aug 2024 00:27:40 +0300 Subject: [PATCH] add submission.completed --- .../api/submission_events_controller.rb | 52 +++++++++++++++++++ app/jobs/process_submitter_completion_job.rb | 27 +++++++--- ...ubmission_completed_webhook_request_job.rb | 47 +++++++++++++++++ config/routes.rb | 1 + 4 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 app/controllers/api/submission_events_controller.rb create mode 100644 app/jobs/send_submission_completed_webhook_request_job.rb diff --git a/app/controllers/api/submission_events_controller.rb b/app/controllers/api/submission_events_controller.rb new file mode 100644 index 00000000..1a32023b --- /dev/null +++ b/app/controllers/api/submission_events_controller.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Api + class SubmissionEventsController < ApiBaseController + load_and_authorize_resource :submission, parent: false + + def index + submissions = build_completed_query(@submissions) + + params[:after] = Time.zone.at(params[:after].to_i) if params[:after].present? + params[:before] = Time.zone.at(params[:before].to_i) if params[:before].present? + + submissions = paginate(submissions.preload( + :created_by_user, :submission_events, + template: :folder, + submitters: { documents_attachments: :blob, attachments_attachments: :blob }, + audit_trail_attachment: :blob + ), + field: :completed_at) + + render json: { + data: submissions.map do |s| + { + event_type: 'submission.completed', + timestamp: s.completed_at, + data: Submissions::SerializeForApi.call(s, s.submitters) + } + end, + pagination: { + count: submissions.size, + next: submissions.last&.completed_at&.to_i, + prev: submissions.first&.completed_at&.to_i + } + } + end + + private + + def build_completed_query(submissions) + submissions = submissions.where( + Submitter.where(completed_at: nil).where( + Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id]) + ).select(1).arel.exists.not + ) + + submissions.joins(:submitters) + .group(:id) + .select(Submission.arel_table[Arel.star], + Submitter.arel_table[:completed_at].maximum.as('completed_at')) + end + end +end diff --git a/app/jobs/process_submitter_completion_job.rb b/app/jobs/process_submitter_completion_job.rb index ae23a20a..20ac4050 100644 --- a/app/jobs/process_submitter_completion_job.rb +++ b/app/jobs/process_submitter_completion_job.rb @@ -20,10 +20,10 @@ class ProcessSubmitterCompletionJob enqueue_completed_emails(submitter) end - enqueue_completed_webhooks(submitter) + enqueue_completed_webhooks(submitter, is_all_completed:) end - def enqueue_completed_webhooks(submitter) + def enqueue_completed_webhooks(submitter, is_all_completed: false) webhook_config = Accounts.load_webhook_config(submitter.account) if webhook_config @@ -31,13 +31,26 @@ class ProcessSubmitterCompletionJob 'encrypted_config_id' => webhook_config.id }) end - webhook_ids = submitter.account.webhook_urls.where( + webhook_urls = submitter.account.webhook_urls + + webhook_urls = webhook_urls.where( Arel::Table.new(:webhook_urls)[:events].matches('%"form.completed"%') - ).pluck(:id) + ).or( + webhook_urls.where( + Arel::Table.new(:webhook_urls)[:events].matches('%"submission.completed"%') + ) + ) + + webhook_urls.each do |webhook| + if webhook.events.include?('form.completed') + SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id, + 'webhook_url_id' => webhook.id }) + end - webhook_ids.each do |webhook_id| - SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id, - 'webhook_url_id' => webhook_id }) + if webhook.events.include?('submission.completed') && is_all_completed + SendSubmissionCompletedWebhookRequestJob.perform_async({ 'submission_id' => submitter.submission_id, + 'webhook_url_id' => webhook.id }) + end end end diff --git a/app/jobs/send_submission_completed_webhook_request_job.rb b/app/jobs/send_submission_completed_webhook_request_job.rb new file mode 100644 index 00000000..5a58c514 --- /dev/null +++ b/app/jobs/send_submission_completed_webhook_request_job.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class SendSubmissionCompletedWebhookRequestJob + include Sidekiq::Job + + sidekiq_options queue: :webhooks + + USER_AGENT = 'DocuSeal.co Webhook' + + MAX_ATTEMPTS = 10 + + def perform(params = {}) + submission = Submission.find(params['submission_id']) + + attempt = params['attempt'].to_i + + webhook_url = submission.account.webhook_urls.find(params['webhook_url_id']) + + url = webhook_url.url if webhook_url.events.include?('submission.completed') + + return if url.blank? + + resp = begin + Faraday.post(url, + { + event_type: 'submission.completed', + timestamp: Time.current, + data: Submissions::SerializeForApi.call(submission) + }.to_json, + **EncryptedConfig.find_or_initialize_by(account_id: submission.account_id, + key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h, + 'Content-Type' => 'application/json', + 'User-Agent' => USER_AGENT) + rescue Faraday::Error + nil + end + + if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS && + (!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan)) + SendSubmissionCompletedWebhookRequestJob.perform_in((2**attempt).minutes, { + **params, + 'attempt' => attempt + 1, + 'last_status' => resp&.status.to_i + }) + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 6cdc09a3..0bea5270 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -49,6 +49,7 @@ Rails.application.routes.draw do end scope 'events' do resources :form_events, only: %i[index], path: 'form/:type' + resources :submission_events, only: %i[index], path: 'submission/:type' end end