diff --git a/Gemfile.lock b/Gemfile.lock index 5685c2c3..8f63e42e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -144,7 +144,7 @@ GEM cldr-plurals-runtime-rb (1.1.0) cmdparse (3.0.7) coderay (1.1.3) - concurrent-ruby (1.3.6) + concurrent-ruby (1.3.7) connection_pool (3.0.2) crack (1.0.1) bigdecimal @@ -195,13 +195,13 @@ GEM railties (>= 6.1.0) faker (3.6.1) i18n (>= 1.8.11, < 2) - faraday (2.14.2) + faraday (2.14.3) faraday-net_http (>= 2.0, < 3.5) json logger faraday-follow_redirects (0.5.0) faraday (>= 1, < 3) - faraday-net_http (3.4.2) + faraday-net_http (3.4.4) net-http (~> 0.5) ferrum (0.17.2) addressable (~> 2.5) @@ -323,15 +323,15 @@ GEM net-smtp (0.5.1) net-protocol nio4r (2.7.5) - nokogiri (1.19.3-aarch64-linux-gnu) + nokogiri (1.19.4-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.19.3-aarch64-linux-musl) + nokogiri (1.19.4-aarch64-linux-musl) racc (~> 1.4) - nokogiri (1.19.3-arm64-darwin) + nokogiri (1.19.4-arm64-darwin) racc (~> 1.4) - nokogiri (1.19.3-x86_64-linux-gnu) + nokogiri (1.19.4-x86_64-linux-gnu) racc (~> 1.4) - nokogiri (1.19.3-x86_64-linux-musl) + nokogiri (1.19.4-x86_64-linux-musl) racc (~> 1.4) numo-narray-alt (0.10.3) onnxruntime (0.10.1-aarch64-linux) diff --git a/app/controllers/api/submissions_controller.rb b/app/controllers/api/submissions_controller.rb index edd769df..ba64cbc6 100644 --- a/app/controllers/api/submissions_controller.rb +++ b/app/controllers/api/submissions_controller.rb @@ -2,6 +2,10 @@ module Api class SubmissionsController < ApiBaseController + SUBMISSION_COLUMNS = %i[id name slug source submitters_order expire_at created_at updated_at + archived_at variables template_id template_submitters created_by_user_id].freeze + TEMPLATE_COLUMNS = %i[id name external_id created_at updated_at folder_id submitters].freeze + load_and_authorize_resource :template, only: :create load_and_authorize_resource :submission, only: %i[show index destroy] @@ -13,10 +17,22 @@ module Api submissions = Submissions.search(current_user, @submissions, params[:q]) submissions = filter_submissions(submissions, params) - submissions = paginate(submissions.preload(:created_by_user, :submitters, - template: { folder: :parent_folder }, - combined_document_attachment: :blob, - audit_trail_attachment: :blob)) + with_fields = params[:include].to_s.include?('fields') || params[:include].to_s.include?('combined_document_url') + + submissions = paginate( + submissions.select(with_fields ? nil : SUBMISSION_COLUMNS) + .preload(:created_by_user, :submitters, combined_document_attachment: :blob, + audit_trail_attachment: :blob) + ) + + ActiveRecord::Associations::Preloader.new( + records: submissions, + associations: :template, + scope: with_fields ? nil : Template.select(TEMPLATE_COLUMNS) + ).call + + ActiveRecord::Associations::Preloader.new(records: submissions.filter_map(&:template), + associations: { folder: :parent_folder }).call expires_at = Accounts.link_expires_at(current_account) diff --git a/app/controllers/api/submitters_controller.rb b/app/controllers/api/submitters_controller.rb index fbaf3477..15b3e119 100644 --- a/app/controllers/api/submitters_controller.rb +++ b/app/controllers/api/submitters_controller.rb @@ -104,7 +104,7 @@ module Api private - def maybe_filder_by_completed_at(submitters, params) + def maybe_filter_by_completed_at(submitters, params) if params[:completed_after].present? submitters = submitters.where(completed_at: Time.zone.parse(params[:completed_after])..) end @@ -177,7 +177,7 @@ module Api submitters = submitters.joins(:submission).where(submissions: { template_id: params[:template_id] }) end - maybe_filder_by_completed_at(submitters, params) + maybe_filter_by_completed_at(submitters, params) end def assign_external_id(submitter, attrs) diff --git a/app/controllers/api/templates_controller.rb b/app/controllers/api/templates_controller.rb index b3d05cec..2de64605 100644 --- a/app/controllers/api/templates_controller.rb +++ b/app/controllers/api/templates_controller.rb @@ -48,7 +48,7 @@ module Api archived = params.key?(:archived) ? params[:archived] : params.dig(:template, :archived) - if archived.in?([true, false]) + if archived.in?([true, false]) && current_ability.can?(:destroy, @template) @template.archived_at = archived == true ? Time.current : nil end @@ -57,7 +57,10 @@ module Api SearchEntries.enqueue_reindex(@template) WebhookUrls.enqueue_events(@template, 'template.updated') - WebhookUrls.enqueue_events(@template, 'template.archived') if archived == true + + if @template.saved_change_to_archived_at? && @template.archived_at? + WebhookUrls.enqueue_events(@template, 'template.archived') + end render json: @template.as_json(only: %i[id updated_at]) end diff --git a/app/controllers/submissions_archived_controller.rb b/app/controllers/submissions_archived_controller.rb index 793da755..662e0950 100644 --- a/app/controllers/submissions_archived_controller.rb +++ b/app/controllers/submissions_archived_controller.rb @@ -7,7 +7,7 @@ class SubmissionsArchivedController < ApplicationController @submissions = @submissions.left_joins(:template) @submissions = @submissions.where.not(archived_at: nil) .or(@submissions.where.not(templates: { archived_at: nil })) - .preload(:template_accesses, :created_by_user, template: :author) + .preload(:template_accesses, :created_by_user) @submissions = Submissions.search(current_user, @submissions, params[:q], search_template: true) @submissions = Submissions::Filter.call(@submissions, current_user, params) @@ -18,6 +18,15 @@ class SubmissionsArchivedController < ApplicationController @submissions.order(id: :desc) end - @pagy, @submissions = pagy_auto(@submissions.preload(submitters: :start_form_submission_events)) + @pagy, @submissions = pagy_auto(@submissions.select_for_list.preload(submitters: :start_form_submission_events)) + + template_scope = @submissions.all?(&:template_submitters) ? Template.select_for_list : nil + + ActiveRecord::Associations::Preloader.new(records: @submissions, + associations: :template, + scope: template_scope).call + + ActiveRecord::Associations::Preloader.new(records: @submissions.filter_map(&:template), + associations: :author).call end end diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index fd598309..04bc5e94 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true class SubmissionsController < ApplicationController - before_action :load_template, only: %i[new create] - authorize_resource :template, only: %i[new create] - + load_and_authorize_resource :template, only: %i[new create] load_and_authorize_resource :submission, only: %i[show destroy] prepend_before_action :maybe_redirect_com, only: %i[show] @@ -113,8 +111,4 @@ class SubmissionsController < ApplicationController def submissions_params params.permit(submission: { submitters: [:uuid, :email, :phone, :name, { values: {} }] }) end - - def load_template - @template = Template.accessible_by(current_ability).find(params[:template_id]) - end end diff --git a/app/controllers/submissions_dashboard_controller.rb b/app/controllers/submissions_dashboard_controller.rb index f0851741..f04e9bea 100644 --- a/app/controllers/submissions_dashboard_controller.rb +++ b/app/controllers/submissions_dashboard_controller.rb @@ -8,7 +8,7 @@ class SubmissionsDashboardController < ApplicationController @submissions = @submissions.where(archived_at: nil) .where(templates: { archived_at: nil }) - .preload(:template_accesses, :created_by_user, template: :author) + .preload(:template_accesses, :created_by_user) @submissions = Submissions.search(current_user, @submissions, params[:q], search_template: true) @submissions = Submissions::Filter.call(@submissions, current_user, params) @@ -19,6 +19,15 @@ class SubmissionsDashboardController < ApplicationController @submissions.order(id: :desc) end - @pagy, @submissions = pagy_auto(@submissions.preload(submitters: :start_form_submission_events)) + @pagy, @submissions = pagy_auto(@submissions.select_for_list.preload(submitters: :start_form_submission_events)) + + template_scope = @submissions.all?(&:template_submitters) ? Template.select_for_list : nil + + ActiveRecord::Associations::Preloader.new(records: @submissions, + associations: :template, + scope: template_scope).call + + ActiveRecord::Associations::Preloader.new(records: @submissions.filter_map(&:template), + associations: :author).call end end diff --git a/app/controllers/template_folders_controller.rb b/app/controllers/template_folders_controller.rb index 25e489ce..a315b167 100644 --- a/app/controllers/template_folders_controller.rb +++ b/app/controllers/template_folders_controller.rb @@ -30,9 +30,11 @@ class TemplateFoldersController < ApplicationController (@template_folders.size < 7 ? 9 : 6) end - @pagy, @templates = pagy_auto(@templates, limit:) + @pagy, @templates = pagy_auto(@templates.select_for_list, limit:) - load_related_submissions if params[:q].present? && @templates.blank? + if params[:q].present? && @templates.blank? + @related_submissions_pagy, @related_submissions = load_related_submissions(@template_folder) + end else @pagy, @template_folders = pagy(@template_folders, limit: FOLDERS_PER_PAGE) @@ -55,11 +57,10 @@ class TemplateFoldersController < ApplicationController def selected_order @selected_order ||= - if cookies.permanent[:dashboard_templates_order].blank? || - (cookies.permanent[:dashboard_templates_order] == 'used_at' && can?(:manage, :countless)) + if can?(:manage, :countless) 'created_at' else - cookies.permanent[:dashboard_templates_order] + cookies.permanent[:dashboard_templates_order].presence || 'created_at' end end @@ -67,20 +68,20 @@ class TemplateFoldersController < ApplicationController params.require(:template_folder).permit(:name) end - def load_related_submissions - @related_submissions = + def load_related_submissions(template_folder) + related_submissions = Submission.accessible_by(current_ability) .where(archived_at: nil) .where(template_id: current_account.templates.active - .where(folder: [@template_folder, *@template_folder.subfolders]) + .where(folder: [template_folder, *template_folder.subfolders]) .select(:id)) .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 = Submissions.search(current_user, related_submissions, params[:q]) + .order(id: :desc) - @related_submissions_pagy, @related_submissions = pagy_auto(@related_submissions, limit: 5) + pagy_auto(related_submissions.select_for_list, limit: 5) end end diff --git a/app/controllers/templates_archived_controller.rb b/app/controllers/templates_archived_controller.rb index 69b16936..84d40074 100644 --- a/app/controllers/templates_archived_controller.rb +++ b/app/controllers/templates_archived_controller.rb @@ -10,11 +10,17 @@ class TemplatesArchivedController < ApplicationController @templates = Templates.search(current_user, @templates, params[:q]) - @pagy, @templates = pagy_auto(@templates, limit: 12) + @pagy, @templates = pagy_auto(@templates.select_for_list, limit: 12) return unless params[:q].present? && @templates.blank? - @related_submissions = + @related_submissions_pagy, @related_submissions = load_related_submissions + end + + private + + def load_related_submissions + related_submissions = Submission.accessible_by(current_ability) .joins(:template) .where.not(templates: { archived_at: nil }) @@ -22,9 +28,9 @@ class TemplatesArchivedController < ApplicationController template: :author, submitters: :start_form_submission_events) - @related_submissions = Submissions.search(current_user, @related_submissions, params[:q]) - .order(id: :desc) + related_submissions = Submissions.search(current_user, related_submissions, params[:q]) + .order(id: :desc) - @related_submissions_pagy, @related_submissions = pagy_auto(@related_submissions, limit: 5) + pagy_auto(related_submissions.select_for_list, limit: 5) end end diff --git a/app/controllers/templates_archived_submissions_controller.rb b/app/controllers/templates_archived_submissions_controller.rb index 39aa0877..cbe7e45a 100644 --- a/app/controllers/templates_archived_submissions_controller.rb +++ b/app/controllers/templates_archived_submissions_controller.rb @@ -15,7 +15,7 @@ class TemplatesArchivedSubmissionsController < ApplicationController @submissions.order(id: :desc) end - @pagy, @submissions = pagy_auto(@submissions.preload(submitters: :start_form_submission_events)) + @pagy, @submissions = pagy_auto(@submissions.select_for_list.preload(submitters: :start_form_submission_events)) rescue ActiveRecord::RecordNotFound redirect_to root_path end diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb index ae54665a..edf73faa 100644 --- a/app/controllers/templates_controller.rb +++ b/app/controllers/templates_controller.rb @@ -19,7 +19,8 @@ class TemplatesController < ApplicationController submissions.order(id: :desc) end - @pagy, @submissions = pagy_auto(submissions.preload(:template_accesses, submitters: :start_form_submission_events)) + @pagy, @submissions = + pagy_auto(submissions.select_for_list.preload(:template_accesses, submitters: :start_form_submission_events)) rescue ActiveRecord::RecordNotFound redirect_to root_path end diff --git a/app/controllers/templates_dashboard_controller.rb b/app/controllers/templates_dashboard_controller.rb index 4497038e..d9a683c9 100644 --- a/app/controllers/templates_dashboard_controller.rb +++ b/app/controllers/templates_dashboard_controller.rb @@ -11,86 +11,126 @@ class TemplatesDashboardController < ApplicationController helper_method :selected_order def index + @default_folder = current_account.default_template_folder + @template_folders = TemplateFolders.filter_active_folders(@template_folders.where(parent_folder_id: nil), @templates) + @template_folders = @template_folders.where.not(id: @default_folder.id) if params[:q].blank? + @template_folders = TemplateFolders.search(@template_folders, params[:q]) @template_folders = TemplateFolders.sort(@template_folders, current_user, selected_order) - @pagy, @template_folders = pagy( - @template_folders, - limit: FOLDERS_PER_PAGE, - page: @template_folders.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD ? params[:page] : 1 - ) + @shared_templates = Templates.shared(current_user).active + + @pagy, @template_folders, @show_default_folder, @show_shared_folder, @show_shared_inline = + load_folders(@template_folders, @templates, @shared_templates) if @pagy.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD @templates = @templates.none else - @template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME } - @templates = filter_templates(@templates).preload(:author, :template_accesses) - @templates = Templates::Order.call(@templates, current_user, selected_order) - - limit = - if @template_folders.size < 4 - TEMPLATES_PER_PAGE - else - (@template_folders.size < 7 ? 9 : 6) - end + if @show_shared_inline + @templates = @shared_templates.preload(:template_sharings) + else + @templates = @templates.active + @templates = @templates.where(folder_id: @default_folder.id) if params[:q].blank? + end - @pagy, @templates = pagy_auto(@templates, limit:) + @pagy, @templates = load_templates(@templates.select_for_list, @pagy.count, + show_shared_inline: @show_shared_inline) - load_related_submissions if params[:q].present? && @templates.blank? + if params[:q].present? && @templates.blank? + @related_submissions_pagy, @related_submissions = load_related_submissions + end end end private - def filter_templates(templates) - rel = templates.active + def load_templates(templates, folders_count, show_shared_inline: false) + templates = templates.preload(:author, :template_accesses) - if params[:q].blank? - if Docuseal.multitenant? ? current_account.testing? : current_account.linked_account_account - shared_account_ids = [current_user.account_id] - shared_account_ids << TemplateSharing::ALL_ID if !Docuseal.multitenant? && !current_account.testing? + templates = + if show_shared_inline + Templates.search_shared(current_user, templates, params[:q]) + else + Templates.search(current_user, templates, params[:q]) + end - shared_template_ids = TemplateSharing.where(account_id: shared_account_ids).select(:template_id) + templates = Templates::Order.call(templates, current_user, selected_order) - rel = Template.where( - Template.arel_table[:id].in( - rel.where(folder_id: current_account.default_template_folder.id).select(:id).arel - .union(:all, shared_template_ids.arel) - ) - ) + limit = + if folders_count < 4 + TEMPLATES_PER_PAGE else - rel = rel.where(folder_id: current_account.default_template_folder.id) + (folders_count < 7 ? 9 : 6) end + + pagy_auto(templates, limit:) + end + + def load_folders(template_folders, templates, shared_templates) + if params[:q].present? + pagy(template_folders, limit: FOLDERS_PER_PAGE, + page: template_folders.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD ? params[:page] : 1) + else + load_folders_with_pinned(template_folders, templates, shared_templates) end + end + + def load_folders_with_pinned(template_folders, templates, shared_templates) + folders_count = template_folders.count + + shared_exists = shared_templates.exists? + default_has_templates = templates.active.exists?(folder_id: current_account.default_template_folder.id) + + show_inline_folders = + folders_count + (shared_exists && default_has_templates ? 1 : 0) <= SHOW_TEMPLATES_FOLDERS_THRESHOLD + + show_shared_inline = shared_exists && !default_has_templates && show_inline_folders + + show_shared_in_grid = shared_exists && !show_shared_inline + show_default_in_grid = !show_inline_folders && default_has_templates + + pinned_count = (show_default_in_grid ? 1 : 0) + (show_shared_in_grid ? 1 : 0) + + pagy = Pagy::Offset.new(count: folders_count + pinned_count, + page: show_inline_folders ? 1 : [params[:page].to_s.to_i, 1].max, + limit: FOLDERS_PER_PAGE, + raise_range_error: true) + + show_default_folder = show_default_in_grid && pagy.page == 1 + show_shared_folder = show_shared_in_grid && pagy.page == 1 + + folder_offset = pagy.page == 1 ? 0 : pagy.offset - pinned_count + folder_limit = pagy.page == 1 ? FOLDERS_PER_PAGE - pinned_count : FOLDERS_PER_PAGE + + template_folders = template_folders.offset(folder_offset).limit(folder_limit) - Templates.search(current_user, rel, params[:q]) + [pagy, template_folders, show_default_folder, show_shared_folder, show_shared_inline] end def selected_order @selected_order ||= - if cookies.permanent[:dashboard_templates_order].blank? || - (cookies.permanent[:dashboard_templates_order] == 'used_at' && can?(:manage, :countless)) + if can?(:manage, :countless) 'created_at' else - cookies.permanent[:dashboard_templates_order] + cookies.permanent[:dashboard_templates_order].presence || 'created_at' 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) + 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) + + pagy_auto(related_submissions.select_for_list, limit: 5) end end diff --git a/app/controllers/templates_restore_controller.rb b/app/controllers/templates_restore_controller.rb index 422b69cf..64afea11 100644 --- a/app/controllers/templates_restore_controller.rb +++ b/app/controllers/templates_restore_controller.rb @@ -4,7 +4,7 @@ class TemplatesRestoreController < ApplicationController load_and_authorize_resource :template def create - authorize!(:update, @template) + authorize!(:destroy, @template) @template.update!(archived_at: nil) diff --git a/app/controllers/templates_shared_controller.rb b/app/controllers/templates_shared_controller.rb new file mode 100644 index 00000000..8855d94d --- /dev/null +++ b/app/controllers/templates_shared_controller.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class TemplatesSharedController < ApplicationController + def index + authorize!(:read, Template) + + @is_archived = params[:archived] == 'true' + + @templates = Templates.shared(current_user) + + @has_archived = !@is_archived && @templates.archived.exists? + + @templates = @is_archived ? @templates.archived : @templates.active + + @templates = @templates.preload(:author, :template_accesses, :template_sharings) + .order(id: :desc) + + @templates = Templates.search_shared(current_user, @templates, params[:q]) + + @pagy, @templates = pagy_auto(@templates.select_for_list, limit: 12) + + return unless params[:q].present? && @templates.blank? + + @related_submissions_pagy, @related_submissions = load_related_submissions(is_archived: @is_archived) + end + + private + + def load_related_submissions(is_archived:) + shared_templates = Templates.shared(current_user) + shared_templates = is_archived ? shared_templates.archived : shared_templates.active + + related_submissions = + Submission.accessible_by(current_ability) + .where(template_id: shared_templates.select(:id)) + .preload(:template_accesses, :created_by_user, + template: :author, + submitters: :start_form_submission_events) + + related_submissions = related_submissions.where(archived_at: nil) unless is_archived + + related_submissions = Submissions.search(current_user, related_submissions, params[:q]) + .order(id: :desc) + + pagy_auto(related_submissions.select_for_list, limit: 5) + end +end diff --git a/app/javascript/submission_form/form.vue b/app/javascript/submission_form/form.vue index 648354f2..95726537 100644 --- a/app/javascript/submission_form/form.vue +++ b/app/javascript/submission_form/form.vue @@ -289,6 +289,7 @@ :id="currentField.uuid" dir="auto" :required="currentField.required" + :aria-label="showFieldNames && (currentField.name || currentField.title) ? undefined : (currentField.name || currentField.title || t('select_your_option'))" :aria-describedby="currentField.description ? currentField.uuid + '-desc' : undefined" class="select base-input !text-2xl w-full text-center font-normal" :class="{ 'text-gray-300': !values[currentField.uuid] }" @@ -317,7 +318,7 @@