fulltext search

This commit is contained in:
Pete Matsyburka
2025-06-05 13:16:55 +03:00
parent 9d1860f038
commit 33811945be
36 changed files with 682 additions and 25 deletions
@@ -10,7 +10,7 @@ module Api
end
def index
submissions = Submissions.search(@submissions, params[:q])
submissions = Submissions.search(current_user, @submissions, params[:q])
submissions = filter_submissions(submissions, params)
submissions = paginate(submissions.preload(:created_by_user, :submitters,
@@ -80,6 +80,8 @@ module Api
end
end
SearchEntries.enqueue_reindex(submissions)
render json: build_create_json(submissions)
rescue Submitters::NormalizeValues::BaseError, Submissions::CreateFromSubmitters::BaseError,
DownloadUtils::UnableToDownload => e
+3 -1
View File
@@ -5,7 +5,7 @@ module Api
load_and_authorize_resource :submitter
def index
submitters = Submitters.search(@submitters, params[:q])
submitters = Submitters.search(current_user, @submitters, params[:q])
submitters = filter_submitters(submitters, params)
@@ -65,6 +65,8 @@ module Api
Submitters.send_signature_requests([@submitter])
end
SearchEntries.enqueue_reindex(@submitter)
render json: Submitters::SerializeForApi.call(@submitter, with_template: false,
with_urls: true,
with_events: false,
@@ -33,6 +33,8 @@ module Api
'webhook_url_id' => webhook_url.id)
end
SearchEntries.enqueue_reindex(cloned_template)
render json: Templates::SerializeForApi.call(cloned_template, schema_documents)
end
end
+3 -1
View File
@@ -65,6 +65,8 @@ module Api
@template.update!(template_params)
SearchEntries.enqueue_reindex(@template)
WebhookUrls.for_account_id(@template.account_id, 'template.updated').each do |webhook_url|
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id,
'webhook_url_id' => webhook_url.id)
@@ -86,7 +88,7 @@ module Api
private
def filter_templates(templates, params)
templates = Templates.search(templates, params[:q])
templates = Templates.search(current_user, templates, params[:q])
templates = params[:archived].in?(['true', true]) ? templates.archived : templates.active
templates = templates.where(external_id: params[:application_key]) if params[:application_key].present?
templates = templates.where(external_id: params[:external_id]) if params[:external_id].present?
+2
View File
@@ -53,6 +53,8 @@ class StartFormController < ApplicationController
if is_new_record
enqueue_submission_create_webhooks(@submitter)
SearchEntries.enqueue_reindex(@submitter)
if @submitter.submission.expire_at?
ProcessSubmissionExpiredJob.perform_at(@submitter.submission.expire_at,
'submission_id' => @submitter.submission_id)
@@ -9,7 +9,7 @@ class SubmissionsArchivedController < ApplicationController
.or(@submissions.where.not(templates: { archived_at: nil }))
.preload(:template_accesses, :created_by_user, template: :author)
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
@submissions = Submissions.search(current_user, @submissions, params[:q], search_template: true)
@submissions = Submissions::Filter.call(@submissions, current_user, params)
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
+6 -2
View File
@@ -8,6 +8,10 @@ class SubmissionsController < ApplicationController
prepend_before_action :maybe_redirect_com, only: %i[show]
before_action only: :create do
authorize!(:create, Submission)
end
def show
@submission = Submissions.preload_with_pages(@submission)
@@ -26,8 +30,6 @@ class SubmissionsController < ApplicationController
end
def create
authorize!(:create, Submission)
save_template_message(@template, params) if params[:save_message] == '1'
if params[:is_custom_message] != '1'
@@ -56,6 +58,8 @@ class SubmissionsController < ApplicationController
Submissions.send_signature_requests(submissions)
SearchEntries.enqueue_reindex(submissions)
redirect_to template_path(@template), notice: I18n.t('new_recipients_have_been_added')
rescue Submissions::CreateFromSubmitters::BaseError => e
render turbo_stream: turbo_stream.replace(:submitters_error,
@@ -10,7 +10,7 @@ class SubmissionsDashboardController < ApplicationController
.where(templates: { archived_at: nil })
.preload(:template_accesses, :created_by_user, template: :author)
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
@submissions = Submissions.search(current_user, @submissions, params[:q], search_template: true)
@submissions = Submissions::Filter.call(@submissions, current_user, params)
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
@@ -22,13 +22,17 @@ class SubmittersAutocompleteController < ApplicationController
def search_submitters(submitters)
if SELECT_COLUMNS.include?(params[:field])
column = Submitter.arel_table[params[:field].to_sym]
if Docuseal.fulltext_search?(current_user)
Submitters.fulltext_search_field(current_user, submitters, params[:q], params[:field])
else
column = Submitter.arel_table[params[:field].to_sym]
term = "#{params[:q].downcase}%"
term = "#{params[:q].downcase}%"
submitters.where(column.matches(term))
submitters.where(column.matches(term))
end
else
Submitters.search(submitters, params[:q])
Submitters.search(current_user, submitters, params[:q])
end
end
end
+2
View File
@@ -38,6 +38,8 @@ class SubmittersController < ApplicationController
if @submitter.save
maybe_resend_email_sms(@submitter, params)
SearchEntries.enqueue_reindex(@submitter)
redirect_back fallback_location: submission_path(submission), notice: I18n.t('changes_have_been_saved')
else
redirect_back fallback_location: submission_path(submission), alert: I18n.t('unable_to_save')
@@ -6,7 +6,7 @@ class TemplateFoldersController < ApplicationController
def show
@templates = @template_folder.templates.active.accessible_by(current_ability)
.preload(:author, :template_accesses)
@templates = Templates.search(@templates, params[:q])
@templates = Templates.search(current_user, @templates, params[:q])
@templates = Templates::Order.call(@templates, current_user, cookies.permanent[:dashboard_templates_order])
@pagy, @templates = pagy_auto(@templates, limit: 12)
@@ -5,7 +5,7 @@ class TemplatesArchivedController < ApplicationController
def index
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder, :template_accesses).order(id: :desc)
@templates = Templates.search(@templates, params[:q])
@templates = Templates.search(current_user, @templates, params[:q])
@pagy, @templates = pagy_auto(@templates, limit: 12)
end
@@ -6,7 +6,7 @@ class TemplatesArchivedSubmissionsController < ApplicationController
def index
@submissions = @submissions.where.not(archived_at: nil)
@submissions = Submissions.search(@submissions, params[:q], search_values: true)
@submissions = Submissions.search(current_user, @submissions, params[:q], search_values: true)
@submissions = Submissions::Filter.call(@submissions, current_user, params)
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
@@ -22,6 +22,8 @@ class TemplatesCloneAndReplaceController < ApplicationController
Templates::CloneAttachments.call(template: cloned_template, original_template: @template,
excluded_attachment_uuids: documents.map(&:uuid))
SearchEntries.enqueue_reindex(cloned_template)
respond_to do |f|
f.html { redirect_to edit_template_path(cloned_template) }
f.json { render json: { id: cloned_template.id } }
+10 -2
View File
@@ -8,7 +8,7 @@ class TemplatesController < ApplicationController
def show
submissions = @template.submissions.accessible_by(current_ability)
submissions = submissions.active if @template.archived_at.blank?
submissions = Submissions.search(submissions, params[:q], search_values: true)
submissions = Submissions.search(current_user, submissions, params[:q], search_values: true)
submissions = Submissions::Filter.call(submissions, current_user, params.except(:status))
@base_submissions = submissions
@@ -72,6 +72,8 @@ class TemplatesController < ApplicationController
if @template.save
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
SearchEntries.enqueue_reindex(@template)
enqueue_template_created_webhooks(@template)
maybe_redirect_to_template(@template)
@@ -81,7 +83,13 @@ class TemplatesController < ApplicationController
end
def update
@template.update!(template_params)
@template.assign_attributes(template_params)
is_name_changed = @template.name_changed?
@template.save!
SearchEntries.enqueue_reindex(@template) if is_name_changed
enqueue_template_updated_webhooks(@template)
@@ -57,7 +57,7 @@ class TemplatesDashboardController < ApplicationController
end
end
Templates.search(rel, params[:q])
Templates.search(current_user, rel, params[:q])
end
def sort_template_folders(template_folders, current_user, order)
@@ -25,6 +25,8 @@ class TemplatesUploadsController < ApplicationController
enqueue_template_created_webhooks(@template)
SearchEntries.enqueue_reindex(@template)
redirect_to edit_template_path(@template)
rescue Templates::CreateAttachments::PdfEncrypted
render turbo_stream: turbo_stream.append(params[:form_id], html: helpers.tag.prompt_password)
@@ -36,7 +36,9 @@ export default class extends HTMLElement {
}
fetch = (text, resolve) => {
const q = text.split(/[;,\s]+/).pop().trim()
const q = this.dataset.field === 'email'
? text.split(/[;,\s]+/).pop().trim()
: text
if (q) {
const queryParams = new URLSearchParams({ q, field: this.dataset.field })
+13
View File
@@ -0,0 +1,13 @@
# frozen_string_literal: true
class ReindexSearchEntryJob
include Sidekiq::Job
InvalidFormat = Class.new(StandardError)
def perform(params = {})
entry = SearchEntry.find_or_initialize_by(params.slice('record_type', 'record_id'))
SearchEntries.reindex_record(entry.record)
end
end
+25
View File
@@ -0,0 +1,25 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: search_entries
#
# id :bigint not null, primary key
# record_type :string not null
# tsvector :tsvector not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# record_id :bigint not null
#
# Indexes
#
# index_search_entries_on_account_id_tsvector_submission (account_id,tsvector) WHERE ((record_type)::text = 'Submission'::text) USING gin
# index_search_entries_on_account_id_tsvector_submitter (account_id,tsvector) WHERE ((record_type)::text = 'Submitter'::text) USING gin
# index_search_entries_on_account_id_tsvector_template (account_id,tsvector) WHERE ((record_type)::text = 'Template'::text) USING gin
# index_search_entries_on_record_id_and_record_type (record_id,record_type) UNIQUE
#
class SearchEntry < ApplicationRecord
belongs_to :record, polymorphic: true
belongs_to :account
end
+2
View File
@@ -38,6 +38,8 @@ class Submission < ApplicationRecord
belongs_to :account
belongs_to :created_by_user, class_name: 'User', optional: true
has_one :search_entry, as: :record, inverse_of: :record, dependent: :destroy
has_many :submitters, dependent: :destroy
has_many :submission_events, dependent: :destroy
+1
View File
@@ -41,6 +41,7 @@ class Submitter < ApplicationRecord
belongs_to :submission
belongs_to :account
has_one :template, through: :submission
has_one :search_entry, as: :record, inverse_of: :record, dependent: :destroy
attribute :values, :string, default: -> { {} }
attribute :preferences, :string, default: -> { {} }
+2
View File
@@ -42,6 +42,8 @@ class Template < ApplicationRecord
belongs_to :account
belongs_to :folder, class_name: 'TemplateFolder'
has_one :search_entry, as: :record, inverse_of: :record, dependent: :destroy
before_validation :maybe_set_default_folder, on: :create
attribute :preferences, :string, default: -> { {} }
@@ -26,7 +26,7 @@
</div>
</div>
<% view_archived_html = capture do %>
<% if current_account.submissions.where.not(archived_at: nil).exists? %>
<% if can?(:manage, :countless) || current_account.submissions.where.not(archived_at: nil).exists? %>
<div>
<a href="<%= submissions_archived_index_path %>" class="link text-sm"><%= t('view_archived') %></a>
</div>
+1 -1
View File
@@ -113,7 +113,7 @@
</div>
<% end %>
<% view_archived_html = capture do %>
<% if @template.submissions.where.not(archived_at: nil).exists? && !@template.archived_at? %>
<% if (can?(:manage, :countless) || @template.submissions.where.not(archived_at: nil).exists?) && !@template.archived_at? %>
<div>
<a href="<%= template_archived_index_path(@template) %>" class="link text-sm"><%= t('view_archived') %></a>
</div>
+1 -1
View File
@@ -1,4 +1,4 @@
<% has_archived = current_account.templates.where.not(archived_at: nil).exists? %>
<% has_archived = can?(:manage, :countless) || current_account.templates.where.not(archived_at: nil).exists? %>
<% show_dropzone = params[:q].blank? && @pagy.pages == 1 && ((@template_folders.size < 10 && @templates.size.zero?) || (@template_folders.size < 7 && @templates.size < 4) || (@template_folders.size < 4 && @templates.size < 7)) %>
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
<dashboard-dropzone>