From bed98f4344f4cc7a20eba5db2c82575002bd095c Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 16 Jun 2025 16:48:20 +0300 Subject: [PATCH 01/24] optimize query --- app/controllers/api/submissions_controller.rb | 4 ++- .../send_submission_email_controller.rb | 25 +++++++++++++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/app/controllers/api/submissions_controller.rb b/app/controllers/api/submissions_controller.rb index 11e9b3e7..7c00f0f3 100644 --- a/app/controllers/api/submissions_controller.rb +++ b/app/controllers/api/submissions_controller.rb @@ -112,7 +112,9 @@ module Api submissions = submissions.where(slug: params[:slug]) if params[:slug].present? if params[:template_folder].present? - submissions = submissions.joins(template: :folder).where(folder: { name: params[:template_folder] }) + folder = TemplateFolder.find_by(name: params[:template_folder], account_id: current_user.account_id) + + submissions = folder ? submissions.joins(:template).where(template: { folder_id: folder.id }) : submissions.none end if params.key?(:archived) diff --git a/app/controllers/send_submission_email_controller.rb b/app/controllers/send_submission_email_controller.rb index ef6ff62b..45852360 100644 --- a/app/controllers/send_submission_email_controller.rb +++ b/app/controllers/send_submission_email_controller.rb @@ -11,11 +11,16 @@ class SendSubmissionEmailController < ApplicationController def create if params[:template_slug] - @submitter = Submitter.completed.joins(submission: :template).find_by!(email: params[:email].to_s.downcase, - template: { slug: params[:template_slug] }) + template = Template.find_by!(slug: params[:template_slug]) + + @submitter = + Submitter.completed.where(submission: template.submissions).find_by!(email: params[:email].to_s.downcase) elsif params[:submission_slug] - @submitter = Submitter.completed.joins(:submission).find_by(email: params[:email].to_s.downcase, - submission: { slug: params[:submission_slug] }) + submission = Submission.find_by(slug: params[:submission_slug]) + + if submission + @submitter = Submitter.completed.find_by(submission: submission, email: params[:email].to_s.downcase) + end return redirect_to submissions_preview_completed_path(params[:submission_slug], status: :error) unless @submitter else @@ -24,14 +29,18 @@ class SendSubmissionEmailController < ApplicationController RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes) - unless EmailEvent.exists?(tag: :submitter_documents_copy, email: @submitter.email, emailable: @submitter, - event_type: :send, created_at: SEND_DURATION.ago..Time.current) - SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later! - end + SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later! unless already_sent?(@submitter) respond_to do |f| f.html { render :success } f.json { head :ok } end end + + private + + def already_sent?(submitter) + EmailEvent.exists?(tag: :submitter_documents_copy, email: submitter.email, emailable: submitter, + event_type: :send, created_at: SEND_DURATION.ago..Time.current) + end end From 11db68cdc7634a0e5b35a2e94d002e1ecd54b7c8 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 16 Jun 2025 18:55:23 +0300 Subject: [PATCH 02/24] fix merge --- lib/submissions/create_from_submitters.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/submissions/create_from_submitters.rb b/lib/submissions/create_from_submitters.rb index 91997560..4ad407df 100644 --- a/lib/submissions/create_from_submitters.rb +++ b/lib/submissions/create_from_submitters.rb @@ -195,6 +195,8 @@ module Submissions merged_submitter['uuid'] = new_uuid merged_submitter['name'] = name merged_submitter.delete('linked_to_uuid') + + next merged_submitter end submitter From f60249725599ebbaeafc47beb9bc1ee33f2e8902 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 16 Jun 2025 22:17:14 +0300 Subject: [PATCH 03/24] require all submitters --- app/controllers/templates_preferences_controller.rb | 2 +- app/views/submissions/_detailed_form.html.erb | 2 +- app/views/submissions/_email_form.html.erb | 2 +- app/views/submissions/_phone_form.html.erb | 2 +- app/views/templates_preferences/show.html.erb | 10 ++++++++++ config/locales/i18n.yml | 6 ++++++ 6 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/controllers/templates_preferences_controller.rb b/app/controllers/templates_preferences_controller.rb index 8f820e3f..e2ec9ee3 100644 --- a/app/controllers/templates_preferences_controller.rb +++ b/app/controllers/templates_preferences_controller.rb @@ -25,7 +25,7 @@ class TemplatesPreferencesController < ApplicationController documents_copy_email_attach_documents documents_copy_email_reply_to completed_notification_email_attach_documents completed_redirect_url validate_unique_submitters - submitters_order require_phone_2fa + require_all_submitters submitters_order require_phone_2fa default_expire_at_duration default_expire_at completed_notification_email_subject completed_notification_email_body diff --git a/app/views/submissions/_detailed_form.html.erb b/app/views/submissions/_detailed_form.html.erb index 847ac5a8..e5c48f28 100644 --- a/app/views/submissions/_detailed_form.html.erb +++ b/app/views/submissions/_detailed_form.html.erb @@ -20,7 +20,7 @@ "> - <%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('name'), required: index.zero?, value: item['email'].present? ? current_account.submitters.accessible_by(current_ability).where.not(name: nil).order(id: :desc).find_by(email: item['email'])&.name : ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : ''), dir: 'auto', id: "detailed_name_#{item['uuid']}" %> + <%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('name'), required: index.zero? || template.preferences['require_all_submitters'], value: item['email'].present? ? current_account.submitters.accessible_by(current_ability).where.not(name: nil).order(id: :desc).find_by(email: item['email'])&.name : ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : ''), dir: 'auto', id: "detailed_name_#{item['uuid']}" %>
diff --git a/app/views/submissions/_email_form.html.erb b/app/views/submissions/_email_form.html.erb index e2e90aa6..b50595d2 100644 --- a/app/views/submissions/_email_form.html.erb +++ b/app/views/submissions/_email_form.html.erb @@ -29,7 +29,7 @@ "> - <%= tag.input type: 'email', multiple: true, name: 'submission[1][submitters][][email]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('email'), required: index.zero?, value: item['email'].presence || ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.email : ''), id: "email_#{item['uuid']}" %> + <%= tag.input type: 'email', multiple: true, name: 'submission[1][submitters][][email]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('email'), required: index.zero? || template.preferences['require_all_submitters'], value: item['email'].presence || ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.email : ''), id: "email_#{item['uuid']}" %> diff --git a/app/views/submissions/_phone_form.html.erb b/app/views/submissions/_phone_form.html.erb index 1034eb27..649739fc 100644 --- a/app/views/submissions/_phone_form.html.erb +++ b/app/views/submissions/_phone_form.html.erb @@ -21,7 +21,7 @@ "> - <%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('#{t('use_international_format_1xxx_')}') : ''", oninput: "this.setCustomValidity('')", name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('phone'), required: index.zero?, id: "phone_phone_#{item['uuid']}" %> + <%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('#{t('use_international_format_1xxx_')}') : ''", oninput: "this.setCustomValidity('')", name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('phone'), required: index.zero? || template.preferences['require_all_submitters'], id: "phone_phone_#{item['uuid']}" %> <% if submitters.size > 1 %> diff --git a/app/views/templates_preferences/show.html.erb b/app/views/templates_preferences/show.html.erb index 47b342c7..165def27 100644 --- a/app/views/templates_preferences/show.html.erb +++ b/app/views/templates_preferences/show.html.erb @@ -358,6 +358,16 @@ <% end %>
<% end %> + <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %> +
+ + <%= t('require_all_recipients') %> + + <%= f.fields_for :preferences, Struct.new(:require_all_submitters).new(@template.preferences['require_all_submitters']) do |ff| %> + <%= ff.check_box :require_all_submitters, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', '' %> + <% end %> +
+ <% end %> <% end %>
<%= button_tag button_title(title: t('save'), disabled_with: t('updating')), class: 'base-button', form: :submitters_form %> diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 378a6f5e..179dff66 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -23,6 +23,7 @@ en: &en thanks: Thanks private: Private stripe_integration: Stripe Integration + require_all_recipients: Require all recipients stripe_account_has_been_connected: Stripe account has been connected. re_connect_stripe: Re-connect Stripe bcc_recipients: BCC recipients @@ -851,6 +852,7 @@ en: &en range_without_total: "%{from}-%{to} items" es: &es + require_all_recipients: Requerir a todos los destinatarios stripe_integration: Integración con Stripe stripe_account_has_been_connected: La cuenta de Stripe ha sido conectada. re_connect_stripe: Volver a conectar Stripe @@ -1683,6 +1685,7 @@ es: &es range_without_total: "%{from}-%{to} elementos" it: &it + require_all_recipients: Richiedi tutti i destinatari stripe_integration: Integrazione Stripe stripe_account_has_been_connected: L'account Stripe è stato collegato. re_connect_stripe: Ricollega Stripe @@ -2513,6 +2516,7 @@ it: &it range_without_total: "%{from}-%{to} elementi" fr: &fr + require_all_recipients: Exiger tous les destinataires stripe_integration: Intégration Stripe stripe_account_has_been_connected: Le compte Stripe a été connecté. re_connect_stripe: Reconnecter Stripe @@ -3346,6 +3350,7 @@ fr: &fr range_without_total: "%{from} à %{to} éléments" pt: &pt + require_all_recipients: Exigir todos os destinatários stripe_integration: Integração com Stripe stripe_account_has_been_connected: Conta Stripe foi conectada. re_connect_stripe: Reconectar Stripe @@ -4179,6 +4184,7 @@ pt: &pt range_without_total: "%{from}-%{to} itens" de: &de + require_all_recipients: Alle Empfänger erforderlich stripe_integration: Stripe-Integration stripe_account_has_been_connected: Stripe-Konto wurde verbunden. re_connect_stripe: Stripe erneut verbinden From 2aece98d6732e1ad589ca102fa6deb2acde7d288 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 16 Jun 2025 22:37:50 +0300 Subject: [PATCH 04/24] add pro routes --- app/controllers/errors_controller.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb index 16b5fc2d..0c9e3632 100644 --- a/app/controllers/errors_controller.rb +++ b/app/controllers/errors_controller.rb @@ -5,8 +5,12 @@ class ErrorsController < ActionController::Base 'This feature is available in Pro Edition: https://www.docuseal.com/pricing' ENTERPRISE_PATHS = [ + '/submissions/html', + '/api/submissions/html', '/templates/html', '/api/templates/html', + '/submissions/pdf', + '/api/submissions/pdf', '/templates/pdf', '/api/templates/pdf', '/templates/doc', From d8bf2cc101db38ef14d1b947f08335d0239b1606 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 17 Jun 2025 10:37:25 +0300 Subject: [PATCH 05/24] add index --- app/models/email_event.rb | 1 + app/models/submission.rb | 9 +++++---- app/models/template.rb | 11 ++++++----- ...0250617072547_add_email_events_event_type_index.rb | 8 ++++++++ .../20250617074820_add_templates_folder_index.rb | 7 +++++++ .../20250617075609_add_submissions_template_index.rb | 7 +++++++ db/schema.rb | 5 ++++- 7 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 db/migrate/20250617072547_add_email_events_event_type_index.rb create mode 100644 db/migrate/20250617074820_add_templates_folder_index.rb create mode 100644 db/migrate/20250617075609_add_submissions_template_index.rb diff --git a/app/models/email_event.rb b/app/models/email_event.rb index bc853a3d..d4eae8e0 100644 --- a/app/models/email_event.rb +++ b/app/models/email_event.rb @@ -21,6 +21,7 @@ # index_email_events_on_account_id_and_event_datetime (account_id,event_datetime) # index_email_events_on_email (email) # index_email_events_on_emailable (emailable_type,emailable_id) +# index_email_events_on_event_type_and_email (event_type,email) WHERE ((event_type)::text = ANY ((ARRAY['bounce'::character varying, 'soft_bounce'::character varying, 'complaint'::character varying, 'soft_complaint'::character varying])::text[])) # index_email_events_on_message_id (message_id) # # Foreign Keys diff --git a/app/models/submission.rb b/app/models/submission.rb index 31547d87..b5b59479 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -23,10 +23,11 @@ # # Indexes # -# index_submissions_on_account_id_and_id (account_id,id) -# index_submissions_on_created_by_user_id (created_by_user_id) -# index_submissions_on_slug (slug) UNIQUE -# index_submissions_on_template_id (template_id) +# index_submissions_on_account_id_and_id (account_id,id) +# index_submissions_on_account_id_and_template_id_and_id (account_id,template_id,id) WHERE (archived_at IS NULL) +# index_submissions_on_created_by_user_id (created_by_user_id) +# index_submissions_on_slug (slug) UNIQUE +# index_submissions_on_template_id (template_id) # # Foreign Keys # diff --git a/app/models/template.rb b/app/models/template.rb index dbfadb79..f37e83bc 100644 --- a/app/models/template.rb +++ b/app/models/template.rb @@ -23,11 +23,12 @@ # # Indexes # -# index_templates_on_account_id (account_id) -# index_templates_on_author_id (author_id) -# index_templates_on_external_id (external_id) -# index_templates_on_folder_id (folder_id) -# index_templates_on_slug (slug) UNIQUE +# index_templates_on_account_id (account_id) +# index_templates_on_account_id_and_folder_id_and_id (account_id,folder_id,id) WHERE (archived_at IS NULL) +# index_templates_on_author_id (author_id) +# index_templates_on_external_id (external_id) +# index_templates_on_folder_id (folder_id) +# index_templates_on_slug (slug) UNIQUE # # Foreign Keys # diff --git a/db/migrate/20250617072547_add_email_events_event_type_index.rb b/db/migrate/20250617072547_add_email_events_event_type_index.rb new file mode 100644 index 00000000..935d377b --- /dev/null +++ b/db/migrate/20250617072547_add_email_events_event_type_index.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class AddEmailEventsEventTypeIndex < ActiveRecord::Migration[8.0] + def change + add_index :email_events, %i[event_type email], + where: "event_type IN ('bounce', 'soft_bounce', 'complaint', 'soft_complaint')" + end +end diff --git a/db/migrate/20250617074820_add_templates_folder_index.rb b/db/migrate/20250617074820_add_templates_folder_index.rb new file mode 100644 index 00000000..dd8e2daa --- /dev/null +++ b/db/migrate/20250617074820_add_templates_folder_index.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddTemplatesFolderIndex < ActiveRecord::Migration[8.0] + def change + add_index :templates, %i[account_id folder_id id], where: 'archived_at IS NULL' + end +end diff --git a/db/migrate/20250617075609_add_submissions_template_index.rb b/db/migrate/20250617075609_add_submissions_template_index.rb new file mode 100644 index 00000000..9004818c --- /dev/null +++ b/db/migrate/20250617075609_add_submissions_template_index.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddSubmissionsTemplateIndex < ActiveRecord::Migration[8.0] + def change + add_index :submissions, %i[account_id template_id id], where: 'archived_at IS NULL' + end +end diff --git a/db/schema.rb b/db/schema.rb index c9e47280..7d2eae1e 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[8.0].define(version: 2025_06_15_091654) do +ActiveRecord::Schema[8.0].define(version: 2025_06_17_075609) do # These are extensions that must be enabled in order to support this database enable_extension "btree_gin" enable_extension "plpgsql" @@ -178,6 +178,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_15_091654) do t.index ["account_id", "event_datetime"], name: "index_email_events_on_account_id_and_event_datetime" t.index ["email"], name: "index_email_events_on_email" t.index ["emailable_type", "emailable_id"], name: "index_email_events_on_emailable" + t.index ["event_type", "email"], name: "index_email_events_on_event_type_and_email", where: "((event_type)::text = ANY ((ARRAY['bounce'::character varying, 'soft_bounce'::character varying, 'complaint'::character varying, 'soft_complaint'::character varying])::text[]))" t.index ["message_id"], name: "index_email_events_on_message_id" end @@ -304,6 +305,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_15_091654) do t.datetime "expire_at" t.text "name" t.index ["account_id", "id"], name: "index_submissions_on_account_id_and_id" + t.index ["account_id", "template_id", "id"], name: "index_submissions_on_account_id_and_template_id_and_id", where: "(archived_at IS NULL)" t.index ["created_by_user_id"], name: "index_submissions_on_created_by_user_id" t.index ["slug"], name: "index_submissions_on_slug", unique: true t.index ["template_id"], name: "index_submissions_on_template_id" @@ -383,6 +385,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_15_091654) do t.string "external_id" t.text "preferences", null: false t.boolean "shared_link", default: false, null: false + t.index ["account_id", "folder_id", "id"], name: "index_templates_on_account_id_and_folder_id_and_id", where: "(archived_at IS NULL)" t.index ["account_id"], name: "index_templates_on_account_id" t.index ["author_id"], name: "index_templates_on_author_id" t.index ["external_id"], name: "index_templates_on_external_id" From 5519459519f527745a2ce8ab480da9d2925dbfef Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 17 Jun 2025 12:42:57 +0300 Subject: [PATCH 06/24] refactor --- app/controllers/api/submissions_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/submissions_controller.rb b/app/controllers/api/submissions_controller.rb index 7c00f0f3..af784ba2 100644 --- a/app/controllers/api/submissions_controller.rb +++ b/app/controllers/api/submissions_controller.rb @@ -112,7 +112,7 @@ module Api submissions = submissions.where(slug: params[:slug]) if params[:slug].present? if params[:template_folder].present? - folder = TemplateFolder.find_by(name: params[:template_folder], account_id: current_user.account_id) + folder = TemplateFolder.accessible_by(current_ability).find_by(name: params[:template_folder]) submissions = folder ? submissions.joins(:template).where(template: { folder_id: folder.id }) : submissions.none end From 7965c17f46b18e41ab91036f9ded296cde5f6537 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 17 Jun 2025 17:39:31 +0300 Subject: [PATCH 07/24] fix serializer --- lib/submissions/serialize_for_api.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/submissions/serialize_for_api.rb b/lib/submissions/serialize_for_api.rb index e7271699..3fcd5056 100644 --- a/lib/submissions/serialize_for_api.rb +++ b/lib/submissions/serialize_for_api.rb @@ -36,18 +36,18 @@ module Submissions last_submitter = submitters.max_by(&:completed_at) if with_documents - json[:documents] = serialized_submitters.find { |e| e['id'] == last_submitter.id }['documents'] + json['documents'] = serialized_submitters.find { |e| e['id'] == last_submitter.id }['documents'] end - json[:status] = 'completed' - json[:completed_at] = last_submitter.completed_at + json['status'] = 'completed' + json['completed_at'] = last_submitter.completed_at else - json[:documents] = [] if with_documents - json[:status] = build_status(submission, submitters) - json[:completed_at] = nil + json['documents'] = [] if with_documents + json['status'] = build_status(submission, submitters) + json['completed_at'] = nil end - json[:submitters] = serialized_submitters + json['submitters'] = serialized_submitters json end From 2491b7b7e3d8a3c8200bfb9aa7389e00a11d307e Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 18 Jun 2025 11:42:23 +0300 Subject: [PATCH 08/24] adjust indexes --- app/models/email_event.rb | 2 +- app/models/submission.rb | 11 ++++++----- app/models/template.rb | 1 + ...0250617072547_add_email_events_event_type_index.rb | 4 ++-- .../20250617075609_add_submissions_template_index.rb | 3 +++ .../20250618085322_add_archived_templates_index.rb | 8 ++++++++ db/schema.rb | 6 ++++-- 7 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 db/migrate/20250618085322_add_archived_templates_index.rb diff --git a/app/models/email_event.rb b/app/models/email_event.rb index d4eae8e0..ed796226 100644 --- a/app/models/email_event.rb +++ b/app/models/email_event.rb @@ -20,8 +20,8 @@ # # index_email_events_on_account_id_and_event_datetime (account_id,event_datetime) # index_email_events_on_email (email) +# index_email_events_on_email_event_types (email) WHERE ((event_type)::text = ANY ((ARRAY['bounce'::character varying, 'soft_bounce'::character varying, 'complaint'::character varying, 'soft_complaint'::character varying])::text[])) # index_email_events_on_emailable (emailable_type,emailable_id) -# index_email_events_on_event_type_and_email (event_type,email) WHERE ((event_type)::text = ANY ((ARRAY['bounce'::character varying, 'soft_bounce'::character varying, 'complaint'::character varying, 'soft_complaint'::character varying])::text[])) # index_email_events_on_message_id (message_id) # # Foreign Keys diff --git a/app/models/submission.rb b/app/models/submission.rb index b5b59479..002933e5 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -23,11 +23,12 @@ # # Indexes # -# index_submissions_on_account_id_and_id (account_id,id) -# index_submissions_on_account_id_and_template_id_and_id (account_id,template_id,id) WHERE (archived_at IS NULL) -# index_submissions_on_created_by_user_id (created_by_user_id) -# index_submissions_on_slug (slug) UNIQUE -# index_submissions_on_template_id (template_id) +# index_submissions_on_account_id_and_id (account_id,id) +# index_submissions_on_account_id_and_template_id_and_id (account_id,template_id,id) WHERE (archived_at IS NULL) +# index_submissions_on_account_id_and_template_id_and_id_archived (account_id,template_id,id) WHERE (archived_at IS NOT NULL) +# index_submissions_on_created_by_user_id (created_by_user_id) +# index_submissions_on_slug (slug) UNIQUE +# index_submissions_on_template_id (template_id) # # Foreign Keys # diff --git a/app/models/template.rb b/app/models/template.rb index f37e83bc..41b635a0 100644 --- a/app/models/template.rb +++ b/app/models/template.rb @@ -25,6 +25,7 @@ # # index_templates_on_account_id (account_id) # index_templates_on_account_id_and_folder_id_and_id (account_id,folder_id,id) WHERE (archived_at IS NULL) +# index_templates_on_account_id_and_id_archived (account_id,id) WHERE (archived_at IS NOT NULL) # index_templates_on_author_id (author_id) # index_templates_on_external_id (external_id) # index_templates_on_folder_id (folder_id) diff --git a/db/migrate/20250617072547_add_email_events_event_type_index.rb b/db/migrate/20250617072547_add_email_events_event_type_index.rb index 935d377b..6a543efb 100644 --- a/db/migrate/20250617072547_add_email_events_event_type_index.rb +++ b/db/migrate/20250617072547_add_email_events_event_type_index.rb @@ -2,7 +2,7 @@ class AddEmailEventsEventTypeIndex < ActiveRecord::Migration[8.0] def change - add_index :email_events, %i[event_type email], - where: "event_type IN ('bounce', 'soft_bounce', 'complaint', 'soft_complaint')" + add_index :email_events, :email, where: "event_type IN ('bounce', 'soft_bounce', 'complaint', 'soft_complaint')", + name: 'index_email_events_on_email_event_types' end end diff --git a/db/migrate/20250617075609_add_submissions_template_index.rb b/db/migrate/20250617075609_add_submissions_template_index.rb index 9004818c..9bfdd5dc 100644 --- a/db/migrate/20250617075609_add_submissions_template_index.rb +++ b/db/migrate/20250617075609_add_submissions_template_index.rb @@ -3,5 +3,8 @@ class AddSubmissionsTemplateIndex < ActiveRecord::Migration[8.0] def change add_index :submissions, %i[account_id template_id id], where: 'archived_at IS NULL' + add_index :submissions, %i[account_id template_id id], + where: 'archived_at IS NOT NULL', + name: 'index_submissions_on_account_id_and_template_id_and_id_archived' end end diff --git a/db/migrate/20250618085322_add_archived_templates_index.rb b/db/migrate/20250618085322_add_archived_templates_index.rb new file mode 100644 index 00000000..b55f52a5 --- /dev/null +++ b/db/migrate/20250618085322_add_archived_templates_index.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class AddArchivedTemplatesIndex < ActiveRecord::Migration[8.0] + def change + add_index :templates, %i[account_id id], where: 'archived_at IS NOT NULL', + name: 'index_templates_on_account_id_and_id_archived' + end +end diff --git a/db/schema.rb b/db/schema.rb index 7d2eae1e..703b5df7 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[8.0].define(version: 2025_06_17_075609) do +ActiveRecord::Schema[8.0].define(version: 2025_06_18_085322) do # These are extensions that must be enabled in order to support this database enable_extension "btree_gin" enable_extension "plpgsql" @@ -177,8 +177,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_17_075609) do t.datetime "created_at", null: false t.index ["account_id", "event_datetime"], name: "index_email_events_on_account_id_and_event_datetime" t.index ["email"], name: "index_email_events_on_email" + t.index ["email"], name: "index_email_events_on_email_event_types", where: "((event_type)::text = ANY ((ARRAY['bounce'::character varying, 'soft_bounce'::character varying, 'complaint'::character varying, 'soft_complaint'::character varying])::text[]))" t.index ["emailable_type", "emailable_id"], name: "index_email_events_on_emailable" - t.index ["event_type", "email"], name: "index_email_events_on_event_type_and_email", where: "((event_type)::text = ANY ((ARRAY['bounce'::character varying, 'soft_bounce'::character varying, 'complaint'::character varying, 'soft_complaint'::character varying])::text[]))" t.index ["message_id"], name: "index_email_events_on_message_id" end @@ -306,6 +306,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_17_075609) do t.text "name" t.index ["account_id", "id"], name: "index_submissions_on_account_id_and_id" t.index ["account_id", "template_id", "id"], name: "index_submissions_on_account_id_and_template_id_and_id", where: "(archived_at IS NULL)" + t.index ["account_id", "template_id", "id"], name: "index_submissions_on_account_id_and_template_id_and_id_archived", where: "(archived_at IS NOT NULL)" t.index ["created_by_user_id"], name: "index_submissions_on_created_by_user_id" t.index ["slug"], name: "index_submissions_on_slug", unique: true t.index ["template_id"], name: "index_submissions_on_template_id" @@ -386,6 +387,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_17_075609) do t.text "preferences", null: false t.boolean "shared_link", default: false, null: false t.index ["account_id", "folder_id", "id"], name: "index_templates_on_account_id_and_folder_id_and_id", where: "(archived_at IS NULL)" + t.index ["account_id", "id"], name: "index_templates_on_account_id_and_id_archived", where: "(archived_at IS NOT NULL)" t.index ["account_id"], name: "index_templates_on_account_id" t.index ["author_id"], name: "index_templates_on_author_id" t.index ["external_id"], name: "index_templates_on_external_id" From 6f0e7c0ca82468163a91f66f505ea3db0fa16534 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 18 Jun 2025 23:52:46 +0300 Subject: [PATCH 09/24] skip autocomplete --- lib/submitters.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/submitters.rb b/lib/submitters.rb index 6e7d856d..6a10fce3 100644 --- a/lib/submitters.rb +++ b/lib/submitters.rb @@ -35,11 +35,11 @@ module Submitters def fulltext_search_field(current_user, submitters, keyword, field_name) keyword = keyword.delete("\0") - return submitters if keyword.blank? + return submitters.none if keyword.blank? weight = FIELD_NAME_WEIGHTS[field_name] - return submitters if weight.blank? + return submitters.none if weight.blank? query = if keyword.match?(/\d/) && !keyword.match?(/\p{L}/) From 36331bb993ae1121a52f27cc5034422cc024d589 Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Sat, 14 Jun 2025 00:05:36 +0300 Subject: [PATCH 10/24] show found submissions when searching on templates --- .../templates_dashboard_controller.rb | 17 ++++++++++++++ app/views/shared/_pagination.html.erb | 22 ++++++++++--------- app/views/templates_dashboard/index.html.erb | 9 ++++++++ spec/system/dashboard_spec.rb | 13 +++++++++++ 4 files changed, 51 insertions(+), 10 deletions(-) diff --git a/app/controllers/templates_dashboard_controller.rb b/app/controllers/templates_dashboard_controller.rb index af47d179..51922c48 100644 --- a/app/controllers/templates_dashboard_controller.rb +++ b/app/controllers/templates_dashboard_controller.rb @@ -36,6 +36,8 @@ class TemplatesDashboardController < ApplicationController end @pagy, @templates = pagy_auto(@templates, limit:) + + load_related_submissions if params[:q].present? && @templates.blank? end end @@ -100,4 +102,19 @@ class TemplatesDashboardController < ApplicationController template_folders.order(id: :desc) end end + + def load_related_submissions + @related_submissions = Submission.accessible_by(current_ability) + .left_joins(:template) + .where(archived_at: nil) + .where(templates: { archived_at: nil }) + .preload(:template_accesses, :created_by_user, + template: :author, + submitters: :start_form_submission_events) + + @related_submissions = Submissions.search(current_user, @related_submissions, params[:q]) + .order(id: :desc) + + @related_submissions_pagy, @related_submissions = pagy_auto(@related_submissions, limit: 5) + end end diff --git a/app/views/shared/_pagination.html.erb b/app/views/shared/_pagination.html.erb index 7676eb5f..b50c7ac1 100644 --- a/app/views/shared/_pagination.html.erb +++ b/app/views/shared/_pagination.html.erb @@ -1,27 +1,29 @@ -<% link = pagy_anchor(@pagy) %> -<% if @pagy.pages > 1 %> +<% link = pagy_anchor(pagy) %> +<% if pagy.pages > 1 %>
<%= local_assigns[:right_additional_html] %>
- <% if @pagy.prev %> - <%== link.call(@pagy.prev, '«', classes: 'join-item btn min-h-full h-10') %> + <% if pagy.prev %> + <%== link.call(pagy.prev, '«', classes: 'join-item btn min-h-full h-10') %> <% else %> « <% end %> - <%= t('page_number', number: @pagy.page) %> + <%= t('page_number', number: pagy.page) %> - <% if @pagy.next %> - <%== link.call(@pagy.next, '»', classes: 'join-item btn min-h-full h-10') %> + <% if local_assigns[:next_page_path].present? %> + <%= link_to '»', local_assigns[:next_page_path], class: 'join-item btn min-h-full h-10' %> + <% elsif pagy.next %> + <%== link.call(pagy.next, '»', classes: 'join-item btn min-h-full h-10') %> <% else %> » <% end %> diff --git a/app/views/templates_dashboard/index.html.erb b/app/views/templates_dashboard/index.html.erb index f1a4c3de..d4bbd8b0 100644 --- a/app/views/templates_dashboard/index.html.erb +++ b/app/views/templates_dashboard/index.html.erb @@ -98,5 +98,14 @@ <%= t('templates_not_found') %>
+ <% if @related_submissions.present? %> +

+ <%= t('submissions') %> +

+
+ <%= render partial: 'templates/submission', collection: @related_submissions, locals: { with_template: true } %> +
+ <%= render 'shared/pagination', pagy: @related_submissions_pagy, items_name: 'submissions', next_page_path: submissions_path(q: params[:q]) %> + <% end %> <% end %> <%= render 'shared/review_form' %> diff --git a/spec/system/dashboard_spec.rb b/spec/system/dashboard_spec.rb index 81d16458..beca9606 100644 --- a/spec/system/dashboard_spec.rb +++ b/spec/system/dashboard_spec.rb @@ -49,5 +49,18 @@ RSpec.describe 'Dashboard Page' do expect(page).to have_current_path(edit_template_path(Template.last), ignore_query: true) end end + + it 'searches be submitter email' do + submission = create(:submission, :with_submitters, template: templates[0]) + submitter = submission.submitters.first + + SearchEntries.reindex_all + + visit root_path(q: submitter.email) + + expect(page).to have_content('Templates not Found') + expect(page).to have_content('Submissions') + expect(page).to have_content(submitter.name) + end end end From 4a888d048e7d94185b3ccc25ca428d58fe501d18 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 19 Jun 2025 11:57:48 +0300 Subject: [PATCH 11/24] fix empty file generate --- lib/submissions/generate_result_attachments.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index 2751afde..bdfff77b 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -256,7 +256,15 @@ module Submissions when ->(type) { type == 'signature' && (with_signature_id || field.dig('preferences', 'reason_field_uuid')) } attachment = submitter.attachments.find { |a| a.uuid == value } - image = load_vips_image(attachment, attachments_data_cache).autorot + image = + begin + load_vips_image(attachment, attachments_data_cache).autorot + rescue Vips::Error + next unless attachment.content_type.starts_with?('image/') + next if attachment.byte_size.zero? + + raise + end reason_value = submitter.values[field.dig('preferences', 'reason_field_uuid')].presence From ea245628610bf885c7db3a52cec30f80b44774be Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 19 Jun 2025 12:25:35 +0300 Subject: [PATCH 12/24] fix time format --- lib/time_utils.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/time_utils.rb b/lib/time_utils.rb index 9150b1b9..292b67bb 100644 --- a/lib/time_utils.rb +++ b/lib/time_utils.rb @@ -53,6 +53,8 @@ module TimeUtils def format_date_string(string, format, locale) date = Date.parse(string.to_s) + format = format.upcase if format + format ||= locale.to_s.ends_with?('US') ? DEFAULT_DATE_FORMAT_US : DEFAULT_DATE_FORMAT i18n_format = format.sub(/D+/, DAY_FORMATS[format[/D+/]]) From 8f3a203994362a32eff68b04f44ba49826160652 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 19 Jun 2025 12:42:44 +0300 Subject: [PATCH 13/24] markdown link --- lib/markdown_to_html.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/markdown_to_html.rb b/lib/markdown_to_html.rb index c66b88b1..e9d04f75 100644 --- a/lib/markdown_to_html.rb +++ b/lib/markdown_to_html.rb @@ -2,11 +2,12 @@ module MarkdownToHtml LINK_REGEXP = %r{\[([^\]]+)\]\((https?://[^)]+)\)} - LINK_REPLACE = '\1' module_function def call(text) - text.gsub(LINK_REGEXP, LINK_REPLACE) + text.gsub(LINK_REGEXP) do + ApplicationController.helpers.link_to(Regexp.last_match(1), Regexp.last_match(2)) + end end end From 76321cef3cff6a97b8b2bd3c0655cdb3551a15cb Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 19 Jun 2025 15:43:13 +0300 Subject: [PATCH 14/24] optimize autocomplete --- lib/submitters.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/submitters.rb b/lib/submitters.rb index 6a10fce3..b6f356e0 100644 --- a/lib/submitters.rb +++ b/lib/submitters.rb @@ -65,12 +65,13 @@ module Submitters SearchEntries.build_weights_wildcard_tsquery(keyword, weight) end - submitters.where( - id: SearchEntry.where(record_type: 'Submitter') - .where(account_id: current_user.account_id) - .where(*query) - .select(:record_id) - ) + submitter_ids = SearchEntry.where(record_type: 'Submitter') + .where(account_id: current_user.account_id) + .where(*query) + .limit(500) + .pluck(:record_id) + + submitters.where(id: submitter_ids.first(100)) end def plain_search(submitters, keyword) From 5050a67d08749a1649d68a3db17dd6a793b1e0a3 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 19 Jun 2025 19:35:04 +0300 Subject: [PATCH 15/24] add datenow type --- app/javascript/template_builder/builder.vue | 12 ++++++++++++ app/javascript/template_builder/field_type.vue | 6 ++++-- app/javascript/template_builder/fields.vue | 2 +- app/javascript/template_builder/i18n.js | 6 ++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index e36a3c61..5df396d8 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -1052,6 +1052,12 @@ export default { field.readonly = true } + if (type === 'datenow') { + field.type = 'date' + field.readonly = true + field.default_value = '{{date}}' + } + if (type === 'date') { field.preferences = { format: this.defaultDateFormat @@ -1450,6 +1456,12 @@ export default { } } + if (field.type === 'datenow') { + field.type = 'date' + field.readonly = true + field.default_value = '{{date}}' + } + if (['stamp', 'heading'].includes(field.type)) { field.readonly = true } diff --git a/app/javascript/template_builder/field_type.vue b/app/javascript/template_builder/field_type.vue index df0bd488..1ccaf5cb 100644 --- a/app/javascript/template_builder/field_type.vue +++ b/app/javascript/template_builder/field_type.vue @@ -51,7 +51,7 @@