From d57b85629489a7958a82c2e41ae0e0432578ee36 Mon Sep 17 00:00:00 2001 From: Ryan Wold <64987852+ryanwoldatwork@users.noreply.github.com> Date: Fri, 14 Feb 2025 10:32:32 -0800 Subject: [PATCH] mark a submission as spam Generally, moves the Form Responses inbox more toward an email folder, to encourage responsiveness to feedback, rather than merely counting feedback for reporting purposes. * ensure actions are top aligned in a row * make `archived` a flag, rather than a Submission state * default scope excludes flagged, archived, deleted, and includes archived * show time for current day respones, month and day for older responses this year, and MM/DD/YYYY for responses from last calendar year or prior * preview the contents of a submission * implements soft-delete for responses (responses can be moved to Trash) * update locales to rails defaults * restyle and refactor pagination * change submissions cursor to a pointer --- Gemfile | 1 + Gemfile.lock | 9 + app/assets/stylesheets/site.scss | 18 +- app/controllers/admin/forms_controller.rb | 23 +- app/controllers/admin/reporting_controller.rb | 4 +- .../admin/submissions_controller.rb | 130 +++++++++-- app/helpers/application_helper.rb | 14 ++ app/mailers/user_mailer.rb | 2 +- app/models/event.rb | 4 +- app/models/form.rb | 46 ++-- app/models/form_cache.rb | 2 +- app/models/submission.rb | 33 ++- app/serializers/submission_serializer.rb | 2 + app/views/admin/forms/_ui_form.html.erb | 59 ----- app/views/admin/forms/responses.html.erb | 63 ++++- app/views/admin/reporting/form_logos.html.erb | 2 +- .../admin/reporting/form_whitelist.html.erb | 2 +- app/views/admin/submissions/_archive.html.erb | 4 +- app/views/admin/submissions/_delete.html.erb | 11 + app/views/admin/submissions/_flag.html.erb | 2 +- app/views/admin/submissions/_mark.html.erb | 11 + .../submissions/_response_pagination.html.erb | 6 + .../admin/submissions/_status_form.html.erb | 14 -- .../admin/submissions/_submissions.html.erb | 221 +++++++++--------- .../admin/submissions/_unarchive.html.erb | 4 +- .../admin/submissions/_undelete.html.erb | 11 + app/views/admin/submissions/_unflag.html.erb | 2 +- app/views/admin/submissions/_unmark.html.erb | 11 + app/views/admin/submissions/delete.js.erb | 1 + app/views/admin/submissions/flag.js.erb | 2 +- app/views/admin/submissions/index.html.erb | 19 +- app/views/admin/submissions/mark.js.erb | 1 + app/views/admin/submissions/show.html.erb | 32 ++- .../submissions/submissions_table.js.erb | 3 +- app/views/admin/submissions/undelete.js.erb | 1 + app/views/admin/submissions/unmark.js.erb | 1 + .../components/_responses_by_status.html.erb | 48 +++- app/views/kaminari/_first_page.html.erb | 2 +- app/views/kaminari/_paginator.html.erb | 2 +- config/application.rb | 4 +- config/initializers/kaminari_config.rb | 2 +- config/locales/en-US.yml | 117 ---------- config/locales/en.yml | 33 ++- config/locales/es.yml | 2 +- config/locales/{zh-CN.yml => zh.yml} | 2 +- config/routes.rb | 5 +- .../20250204180800_archived_submissions.rb | 10 + .../20250211191435_submission_deleted_at.rb | 8 + db/schema.rb | 7 +- db/seeds.rb | 10 +- .../admin/forms_controller_spec.rb | 6 +- .../admin/submissions_controller_spec.rb | 14 +- spec/factories/form.rb | 2 +- spec/features/admin/forms_spec.rb | 2 +- spec/features/admin/submissions_spec.rb | 185 ++++++++------- spec/features/touchpoints_spec.rb | 44 ++-- spec/helpers/application_helper_spec.rb | 23 ++ spec/models/form_spec.rb | 15 +- spec/models/submission_spec.rb | 38 +++ 59 files changed, 783 insertions(+), 569 deletions(-) delete mode 100644 app/views/admin/forms/_ui_form.html.erb create mode 100644 app/views/admin/submissions/_delete.html.erb create mode 100644 app/views/admin/submissions/_mark.html.erb create mode 100644 app/views/admin/submissions/_response_pagination.html.erb create mode 100644 app/views/admin/submissions/_undelete.html.erb create mode 100644 app/views/admin/submissions/_unmark.html.erb create mode 100644 app/views/admin/submissions/delete.js.erb create mode 100644 app/views/admin/submissions/mark.js.erb create mode 100644 app/views/admin/submissions/undelete.js.erb create mode 100644 app/views/admin/submissions/unmark.js.erb delete mode 100644 config/locales/en-US.yml rename config/locales/{zh-CN.yml => zh.yml} (98%) create mode 100644 db/migrate/20250204180800_archived_submissions.rb create mode 100644 db/migrate/20250211191435_submission_deleted_at.rb diff --git a/Gemfile b/Gemfile index 9104b7e25..9495c3401 100644 --- a/Gemfile +++ b/Gemfile @@ -83,6 +83,7 @@ group :development do gem "brakeman" gem 'bullet' gem "bundler-audit" + gem 'faker', git: 'https://github.com/faker-ruby/faker.git', branch: 'main' gem 'listen' gem 'rails-erd' gem "rubocop-rails" diff --git a/Gemfile.lock b/Gemfile.lock index bbec9a594..3a6f4a6ef 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,6 +10,14 @@ GIT multi_json (~> 1.14) omniauth (~> 2.0) +GIT + remote: https://github.com/faker-ruby/faker.git + revision: 3a65e1e2e567cb3be3f6b9582484ba4d5ee5d8c6 + branch: main + specs: + faker (3.5.1) + i18n (>= 1.8.11, < 2) + GEM remote: https://rubygems.org/ specs: @@ -660,6 +668,7 @@ DEPENDENCIES devise (>= 4.8.1) dotenv factory_bot_rails + faker! fog-aws (>= 3.15.0) image_processing (~> 1.12) importmap-rails (>= 2.0.0) diff --git a/app/assets/stylesheets/site.scss b/app/assets/stylesheets/site.scss index c2ed6f495..2d54e79de 100644 --- a/app/assets/stylesheets/site.scss +++ b/app/assets/stylesheets/site.scss @@ -33,22 +33,10 @@ footer { } // For the extra-wide Responses table -.table-scroll { - position:relative; - max-width: 100%; - margin: auto; -} .table-wrap { width:100%; overflow:auto; } -.table-scroll table { - width:100%; -} -.table-scroll th, -.table-scroll td { - max-width: 600px; -} .dark-blue-bg { background-color: #162D50; @@ -617,4 +605,10 @@ abbr[title=required] { z-index: 1000; padding: 10px; border-bottom: 1px solid #ccc; +} + +.usa-table.submissions tbody tr:hover, +.usa-table.submissions tbody tr:hover td { + cursor: pointer !important; + } \ No newline at end of file diff --git a/app/controllers/admin/forms_controller.rb b/app/controllers/admin/forms_controller.rb index 708cc939a..cab7f5cd1 100644 --- a/app/controllers/admin/forms_controller.rb +++ b/app/controllers/admin/forms_controller.rb @@ -27,7 +27,6 @@ class FormsController < AdminController archive reset add_tag remove_tag - update_ui_truncation update_title update_instructions update_disclaimer_text update_success_text update_display_logo update_notification_emails @@ -187,7 +186,8 @@ def export start_date = params[:start_date] ? Date.parse(params[:start_date]).to_date.beginning_of_day : Time.zone.now.beginning_of_quarter end_date = params[:end_date] ? Date.parse(params[:end_date]).to_date.end_of_day : Time.zone.now.end_of_quarter - count = Form.find_by_short_uuid(@form.short_uuid).non_flagged_submissions(start_date:, end_date:).count + count = Form.find_by_short_uuid(@form.short_uuid) + .reportable_submissions(start_date:, end_date:).count if count > MAX_ROWS_TO_EXPORT render status: :bad_request, plain: "Your response set contains #{helpers.number_with_delimiter count} responses and is too big to be exported from the Touchpoints app. Consider using the Touchpoints API to download large response sets (over #{helpers.number_with_delimiter MAX_ROWS_TO_EXPORT} responses)." return @@ -223,6 +223,8 @@ def questions end def responses + @search_params = search_params + @search_params.merge!(form_id: @form.short_uuid) FormCache.invalidate_reports(@form.short_uuid) if params['use_cache'].present? && params['use_cache'] == 'false' ensure_response_viewer(form: @form) unless @form.template? end @@ -315,18 +317,6 @@ def copy_by_id copy end - def update_ui_truncation - ensure_response_viewer(form: @form) - - respond_to do |format| - if @form.update(ui_truncate_text_responses: !@form.ui_truncate_text_responses) - format.json { render json: {}, status: :ok, location: @form } - else - format.json { render json: @form.errors, status: :unprocessable_entity } - end - end - end - def update ensure_form_manager(form: @form) @@ -537,7 +527,6 @@ def form_params :load_css, :tag_list, :verify_csrf, - :ui_truncate_text_responses, :question_text_01, :question_text_02, :question_text_03, @@ -610,5 +599,9 @@ def transition_state def invite_params params.require(:user).permit(:refer_user) end + + def search_params + params.permit(:form_id, :flagged, :spam, :archived, :deleted) + end end end diff --git a/app/controllers/admin/reporting_controller.rb b/app/controllers/admin/reporting_controller.rb index edd9d885c..415173f48 100644 --- a/app/controllers/admin/reporting_controller.rb +++ b/app/controllers/admin/reporting_controller.rb @@ -9,7 +9,9 @@ def hisps end def lifespan - @form_lifespans = Submission.select('form_id, count(*) as num_submissions, (max(submissions.created_at) - min(submissions.created_at)) as lifespan').group(:form_id) + @form_lifespans = Submission + .select('form_id, count(*) as num_submissions, (max(submissions.created_at) - min(submissions.created_at)) as lifespan') + .group("form_id") @forms = Form.select(:id, :name, :organization_id, :uuid, :short_uuid).where('exists (select id from submissions where submissions.form_id = forms.id)') @orgs = Organization.order(:name) @org_summary = [] diff --git a/app/controllers/admin/submissions_controller.rb b/app/controllers/admin/submissions_controller.rb index 5e0e71296..a63d2f9e0 100644 --- a/app/controllers/admin/submissions_controller.rb +++ b/app/controllers/admin/submissions_controller.rb @@ -44,12 +44,12 @@ def update end def search - @all_submissions = @form.submissions + @all_submissions = @form.submissions.ordered @all_submissions = @all_submissions.where(":tags = ANY (tags)", tags: params[:tag]) if params[:tag] if params[:archived] - @submissions = @all_submissions.order('submissions.created_at DESC').page params[:page] + @submissions = @all_submissions.page params[:page] else - @submissions = @all_submissions.non_archived.order('submissions.created_at DESC').page params[:page] + @submissions = @all_submissions.active.page params[:page] end end @@ -92,7 +92,13 @@ def a11_chart def responses_per_day @dates = (45.days.ago.to_date..Date.today).map { |date| date } - @response_groups = @form.submissions.where("created_at >= ?", 45.days.ago).group('date(created_at)').size.sort.last(45) + + @response_groups = Submission + .where(form_id: @form.id) + .where("created_at >= ?", 45.days.ago) + .group(Arel.sql("DATE(created_at)")) + .count.sort + # Add in 0 count days to fetched analytics @dates.each do |date| @response_groups << [date, 0] unless @response_groups.detect { |row| row[0].strftime('%m %d %Y') == date.strftime('%m %d %Y') } @@ -101,9 +107,19 @@ def responses_per_day end def responses_by_status - responses_by_aasm = @form.submissions.group(:aasm_state).count - flagged_count = @form.submissions.where(flagged: true).size - @responses_by_status = { **responses_by_aasm, 'flagged' => flagged_count, 'total' => responses_by_aasm.values.sum } + form_submissions = @form.submissions + + responses_by_aasm = form_submissions.group(:aasm_state).count + flagged_count = form_submissions.flagged.count + archived_count = form_submissions.archived.count + marked_count = form_submissions.marked_as_spam.count + deleted_count = form_submissions.deleted.count + @responses_by_status = { **responses_by_aasm, + 'flagged' => flagged_count, + 'marked' => marked_count, + 'archived' => archived_count, + 'deleted' => deleted_count, + 'total' => responses_by_aasm.values.sum } @responses_by_status.default = 0 end @@ -112,41 +128,88 @@ def performance_gov end def submissions_table - @show_archived = true if params[:archived] - all_submissions = @form.submissions - all_submissions = all_submissions.where(":tags = ANY (tags)", tags: params[:tag]) if params[:tag] - if params[:archived] - @submissions = all_submissions.order('submissions.created_at DESC').page params[:page] + @show_flagged = search_params[:flagged] == "1" + @show_marked_as_spam = search_params[:spam] == "1" + @show_archived = search_params[:archived] == "1" + @show_deleted = search_params[:deleted] == "1" + + @submissions = @form.submissions + + # Apply filters based on query params + if search_params[:tag] + @submissions = @submissions.where(":tags = ANY (tags)", tags: search_params[:tag]) + end + + if @show_flagged + @submissions = @submissions.flagged + elsif @show_marked_as_spam + @submissions = @submissions.marked_as_spam + elsif @show_archived + @submissions = @submissions.archived + elsif @show_deleted + @submissions = @submissions.deleted + elsif @show_deleted + @submissions = @submissions.deleted else - @submissions = all_submissions.non_archived.order('submissions.created_at DESC').page params[:page] + @submissions = @submissions.active end + + @submissions = @submissions.ordered.page(params[:page]) end def archive ensure_form_manager(form: @form) Event.log_event(Event.names[:response_archived], 'Submission', @submission.id, "Submission #{@submission.id} archived at #{DateTime.now}", current_user.id) - @submission.archive_without_validation! + @submission.update_attribute(:archived, true) end def unarchive ensure_form_manager(form: @form) Event.log_event(Event.names[:response_unarchived], 'Submission', @submission.id, "Submission #{@submission.id} unarchived at #{DateTime.now}", current_user.id) - @submission.reset_without_validation! + @submission.update_attribute(:archived, false) + end + + def mark + ensure_form_manager(form: @form) + + Event.log_event(Event.names[:response_marked_as_spam], 'Submission', @submission.id, "Submission #{@submission.id} marked as spam at #{DateTime.now}", current_user.id) + @submission.update_attribute(:spam, true) + end + + def unmark + ensure_form_manager(form: @form) + + Event.log_event(Event.names[:response_unmarked_as_spam], 'Submission', @submission.id, "Submission #{@submission.id} unmarked as spam at #{DateTime.now}", current_user.id) + @submission.update_attribute(:spam, false) + end + + def delete + ensure_form_manager(form: @form) + + Event.log_event(Event.names[:response_deleted], 'Submission', @submission.id, "Submission #{@submission.id} undeleted at #{DateTime.now}", current_user.id) + @submission.update(deleted: true, deleted_at: Time.now) end def destroy ensure_form_manager(form: @form) - Event.log_event(Event.names[:response_deleted], 'Submission', @submission.id, "Submission #{@submission.id} deleted at #{DateTime.now}", current_user.id) + Event.log_event(Event.names[:response_deleted], 'Submission', @submission.id, "Submission #{@submission.id} undeleted at #{DateTime.now}", current_user.id) + @submission.update(deleted: true, deleted_at: Time.now) - @submission.destroy respond_to do |format| format.js { render :destroy } end end + def undelete + ensure_form_manager(form: @form) + + Event.log_event(Event.names[:response_undeleted], 'Submission', @submission.id, "Submission #{@submission.id} deleted at #{DateTime.now}", current_user.id) + @submission.update(deleted: false, deleted_at: nil) + end + def feed @days_limit = (params[:days_limit].present? ? params[:days_limit].to_i : 1) @feed = get_feed_data(@days_limit) @@ -178,7 +241,7 @@ def get_feed_data(days_limit) all_question_responses = [] Form.all.each do |form| - submissions = form.submissions + submissions = form.submissions.ordered submissions = submissions.where('created_at >= ?', days_limit.days.ago) if days_limit.positive? submissions.each do |submission| form.ordered_questions.each do |question| @@ -207,30 +270,38 @@ def get_feed_data(days_limit) def bulk_update submission_ids = params[:submission_ids] # Array of selected submission_ids - bulk_action = params[:bulk_action] # The selected action ('flag' or 'archive') + bulk_action = params[:bulk_action] # The selected action ('flag', 'archive', or 'spam') if submission_ids.present? - submissions = @form.submissions.where(id: submission_ids) + submissions = @form.submissions + .where(id: submission_ids) + .ordered case bulk_action when 'archive' submissions.each do |submission| Event.log_event(Event.names[:response_archived], 'Submission', submission.id, "Submission #{submission.id} archived at #{DateTime.now}", current_user.id) - submission.archive_without_validation! + submission.update_attribute(:archived, true) end - flash[:notice] = "#{submissions.count} Submissions archived." + flash[:notice] = "#{view_context.pluralize(submissions.count, 'Submission')} archived." when 'flag' submissions.each do |submission| Event.log_event(Event.names[:response_flagged], 'Submission', submission.id, "Submission #{submission.id} flagged at #{DateTime.now}", current_user.id) submission.update_attribute(:flagged, true) end - flash[:notice] = "#{submissions.count} Submissions flagged." + flash[:notice] = "#{view_context.pluralize(submissions.count, 'Submission')} flagged." when 'spam' submissions.each do |submission| Event.log_event(Event.names[:response_marked_as_spam], 'Submission', submission.id, "Submission #{submission.id} marked as spam at #{DateTime.now}", current_user.id) submission.update_attribute(:spam, true) end - flash[:notice] = "#{submissions.count} Submissions marked as spam." + flash[:notice] = "#{view_context.pluralize(submissions.count, 'Submission')} marked as spam." + when 'delete' + submissions.each do |submission| + Event.log_event(Event.names[:response_deleted], 'Submission', submission.id, "Submission #{submission.id} deleted at #{DateTime.now}", current_user.id) + submission.update(deleted: true, deleted_at: Time.now) + end + flash[:notice] = "#{view_context.pluralize(submissions.count, 'Submission')} deleted." else flash[:alert] = "Invalid action selected." end @@ -273,5 +344,16 @@ def status_params def tag_params params.require(:submission).permit(:tag) end + + def search_params + params.permit( + :form_id, + :flagged, + :spam, + :archived, + :deleted, + :tags, + ) + end end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index d6cce44e7..6259721a4 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -214,6 +214,20 @@ def format_time(time, timezone) I18n.l time.to_time.in_time_zone(timezone), format: :with_timezone end + def format_submission_time(datetime, time_zone) + created_at = datetime.in_time_zone(time_zone) + today = Date.today.in_time_zone(time_zone).beginning_of_day + start_of_year = today.beginning_of_year + + if created_at >= today + created_at.strftime("%-I:%M %p") # Today: 1:23 PM + elsif created_at >= start_of_year + created_at.strftime("%b %e") # Current year: Jan 5 + else + created_at.strftime("%m/%d/%Y") # Last year: 01/05/2024 + end + end + def timezone_abbreviation(timezone) zone = ActiveSupport::TimeZone.new(timezone) zone.now.strftime('%Z') diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 9e03c8dbd..7710fddb5 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -96,7 +96,7 @@ def submissions_digest(form_id, days_ago) return unless @form.send_notifications? set_logo - @submissions = Submission.where(id: form_id).where('created_at > ?', @begin_day).order('created_at desc') + @submissions = @form.submissions.where('created_at > ?', @begin_day) return unless @submissions.present? emails = @form.notification_emails.split(',') diff --git a/app/models/event.rb b/app/models/event.rb index 775a130cb..041fbe54f 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -57,12 +57,14 @@ class Generic cx_collection_detail_upload_created: 'cx_collection_detail_upload_created', cx_collection_detail_upload_deleted: 'cx_collection_detail_upload_deleted', - response_marked_as_spam: 'response_marked_as_spam', response_flagged: 'response_flagged', response_unflagged: 'response_unflagged', response_archived: 'response_archived', response_unarchived: 'response_unarchived', + response_marked_as_spam: 'response_marked_as_spam', + response_unmarked_as_spam: 'response_unmarked_as_spam', response_deleted: 'response_deleted', + response_undeleted: 'response_undeleted', response_status_changed: 'response_status_changed', website_created: 'website_created', diff --git a/app/models/form.rb b/app/models/form.rb index fac749083..f48ced3e8 100644 --- a/app/models/form.rb +++ b/app/models/form.rb @@ -299,17 +299,17 @@ def touchpoints_js_string ApplicationController.new.render_to_string(partial: 'components/widget/fba', formats: :js, locals: { form: self }) end - def non_flagged_submissions(start_date: nil, end_date: nil) + def reportable_submissions(start_date: nil, end_date: nil) submissions - .non_flagged + .reportable .where(created_at: start_date..) .where(created_at: ..end_date) end def to_csv(start_date: nil, end_date: nil) - non_flagged_submissions = non_flagged_submissions(start_date:, end_date:) + reportable_submissions = reportable_submissions(start_date:, end_date:) .order('created_at') - return nil if non_flagged_submissions.blank? + return nil if reportable_submissions.blank? header_attributes = hashed_fields_for_export.values attributes = fields_for_export @@ -317,16 +317,16 @@ def to_csv(start_date: nil, end_date: nil) CSV.generate(headers: true) do |csv| csv << header_attributes - non_flagged_submissions.each do |submission| + reportable_submissions.each do |submission| csv << attributes.map { |attr| submission.send(attr) } end end end def to_combined_a11_v2_csv(start_date: nil, end_date: nil) - non_flagged_submissions = non_flagged_submissions(start_date:, end_date:) + reportable_submissions = reportable_submissions(start_date:, end_date:) .order('created_at') - return nil if non_flagged_submissions.blank? + return nil if reportable_submissions.blank? header_attributes = hashed_fields_for_export.values attributes = fields_for_export @@ -360,7 +360,7 @@ def to_combined_a11_v2_csv(start_date: nil, end_date: nil) CSV.generate(headers: true) do |csv| csv << header_attributes + a11_v2_header_attributes - non_flagged_submissions.each do |submission| + reportable_submissions.each do |submission| csv << attributes.map { |attr| submission.send(attr) } + [ submission.id, submission.answer_01, @@ -387,12 +387,12 @@ def to_combined_a11_v2_csv(start_date: nil, end_date: nil) end def to_a11_v2_csv(start_date: nil, end_date: nil) - non_flagged_submissions = submissions - .non_flagged + reportable_submissions = submissions + .reportable .where('created_at >= ?', start_date) .where('created_at <= ?', end_date) .order('created_at') - return nil if non_flagged_submissions.blank? + return nil if reportable_submissions.blank? header_attributes = hashed_fields_for_export.values header_attributes = [ @@ -423,7 +423,7 @@ def to_a11_v2_csv(start_date: nil, end_date: nil) CSV.generate(headers: true) do |csv| csv << header_attributes - non_flagged_submissions.each do |submission| + reportable_submissions.each do |submission| csv << [ submission.id, submission.answer_01, @@ -459,8 +459,8 @@ def user_role?(user:) # Generates 1 of 2 exported files for the A11 # This is a one record metadata file def to_a11_header_csv(start_date:, end_date:) - non_flagged_submissions = submissions.non_flagged.where('created_at >= ?', start_date).where('created_at <= ?', end_date) - return nil if non_flagged_submissions.blank? + reportable_submissions = submissions.reportable.where('created_at >= ?', start_date).where('created_at <= ?', end_date) + return nil if reportable_submissions.blank? header_attributes = [ 'submission comment', @@ -482,7 +482,7 @@ def to_a11_header_csv(start_date:, end_date:) ] CSV.generate(headers: true) do |csv| - submission = non_flagged_submissions.first + submission = reportable_submissions.first csv << header_attributes csv << [ submission.form.data_submission_comment, @@ -498,7 +498,7 @@ def to_a11_header_csv(start_date:, end_date:) end_date, submission.form.anticipated_delivery_count, submission.form.survey_form_activations, - non_flagged_submissions.length, + reportable_submissions.length, submission.form.omb_approval_number, submission.form.federal_register_url, ] @@ -508,8 +508,8 @@ def to_a11_header_csv(start_date:, end_date:) # Generates the 2nd of 2 exported files for the A11 # This is a 7 record detail file; one for each question def to_a11_submissions_csv(start_date:, end_date:) - non_flagged_submissions = submissions.non_flagged.where('created_at >= ?', start_date).where('created_at <= ?', end_date) - return nil if non_flagged_submissions.blank? + reportable_submissions = submissions.reportable.where('created_at >= ?', start_date).where('created_at <= ?', end_date) + return nil if reportable_submissions.blank? header_attributes = %w[ standardized_question_number @@ -537,7 +537,7 @@ def to_a11_submissions_csv(start_date:, end_date:) } # Aggregate likert scale responses - non_flagged_submissions.each do |submission| + reportable_submissions.each do |submission| @hash.each_key do |field| response = submission.send(field) @hash[field][submission.send(field)] += 1 if response.present? @@ -629,11 +629,13 @@ def hashed_fields_for_export aasm_state: 'Status', archived: 'Archived', flagged: 'Flagged', + deleted: 'Deleted', + deleted_at: 'Deleted at', page: 'Page', query_string: 'Query string', hostname: 'Hostname', referer: 'Referrer', - created_at: 'Created At', + created_at: 'Created at', }) if organization.enable_ip_address? @@ -657,6 +659,10 @@ def ordered_questions array end + def rendered_questions + ordered_questions.select { |q| q.text.include?("email") || q.text.include?("name") } + end + def omb_number_with_expiration_date errors.add(:expiration_date, 'required with an OMB Number') if omb_approval_number.present? && expiration_date.blank? errors.add(:omb_approval_number, 'required with an Expiration Date') if expiration_date.present? && omb_approval_number.blank? diff --git a/app/models/form_cache.rb b/app/models/form_cache.rb index f9ca4841f..1e888ece3 100644 --- a/app/models/form_cache.rb +++ b/app/models/form_cache.rb @@ -37,7 +37,7 @@ def self.fetch_performance_gov_analysis(short_uuid) Rails.cache.fetch("#{NAMESPACE}-performance-gov-analysis-#{short_uuid}", expires_in: 1.day) do form = Form.find_by_short_uuid(short_uuid) report = {} - report[:quarterly_submissions] = form.submissions.order(:created_at).entries.map { |e| e.attributes.merge(quarter: e.created_at.beginning_of_quarter.to_date, end_of_quarter: e.created_at.end_of_quarter) } + report[:quarterly_submissions] = form.submissions.entries.map { |e| e.attributes.merge(quarter: e.created_at.beginning_of_quarter.to_date, end_of_quarter: e.created_at.end_of_quarter) } report[:quarters] = report[:quarterly_submissions].pluck(:quarter).uniq report end diff --git a/app/models/submission.rb b/app/models/submission.rb index 9248f5b80..ef1c76fa6 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -13,16 +13,23 @@ class Submission < ApplicationRecord after_create :update_form after_commit :send_notifications, on: :create - scope :archived, -> { where(aasm_state: :archived) } - scope :non_archived, -> { where("aasm_state != 'archived'") } + scope :active, -> { where(flagged: false, spam: false, archived: false, deleted: false) } + scope :reportable, -> { where(flagged: false, spam: false, deleted: false) } + scope :ordered, -> { order("created_at DESC") } + scope :archived, -> { where(archived: true) } + scope :non_archived, -> { where(archived: false) } + scope :flagged, -> { where(flagged: true) } scope :non_flagged, -> { where(flagged: false) } + scope :marked_as_spam, -> { where(spam: true) } + scope :not_marked_as_spam, -> { where(spam: false) } + scope :deleted, -> { where(deleted: true) } + scope :non_deleted, -> { where(deleted: false) } aasm do state :received, initial: true state :acknowledged state :dispatched state :responded - state :archived event :acknowledge do transitions from: [:received], to: :acknowledged @@ -33,18 +40,11 @@ class Submission < ApplicationRecord event :respond do transitions from: %i[dispatched], to: :responded end - event :archive do - transitions to: :archived - end event :reset do transitions to: :received end end - def archived - self.archived? - end - # Validate each submitted field against its question type def validate_custom_form # Isolate questions that were answered @@ -65,8 +65,13 @@ def validate_custom_form answered_questions.delete('aasm_state') answered_questions.delete('tags') answered_questions.delete('spam_score') + answered_questions.delete('flagged') + answered_questions.delete('spam') + answered_questions.delete('archived') + answered_questions.delete('deleted') answered_questions.delete('created_at') answered_questions.delete('updated_at') + answered_questions.delete('deleted_at') # Ensure only requested fields are submitted expected_submission_fields = form.questions.collect(&:answer_field) + ["location_code"] @@ -202,6 +207,14 @@ def organization_name form.organization.present? ? form.organization.name : 'Org Name' end + def preview + # only select the answer fields + fields = attributes.select { |attr| attr.include?("answer")} + # only select text fields + text_fields = fields.values.select { |v| v.is_a?(String) } + text_fields.join(" - ").truncate(120) + end + def set_uuid self.uuid = SecureRandom.uuid if uuid.blank? end diff --git a/app/serializers/submission_serializer.rb b/app/serializers/submission_serializer.rb index 7ac67208f..d90ef81e5 100644 --- a/app/serializers/submission_serializer.rb +++ b/app/serializers/submission_serializer.rb @@ -34,6 +34,8 @@ class SubmissionSerializer < ActiveModel::Serializer :location_code, :flagged, :archived, + :deleted, + :deleted_at, :aasm_state, :language, :uuid, diff --git a/app/views/admin/forms/_ui_form.html.erb b/app/views/admin/forms/_ui_form.html.erb deleted file mode 100644 index bb0b3a08c..000000000 --- a/app/views/admin/forms/_ui_form.html.erb +++ /dev/null @@ -1,59 +0,0 @@ -<%= form_with(model: form, url: update_ui_truncation_admin_form_path(form, format: :json), local: false) do |f| %> - <%- if form.errors.any? %> -
- <%= message %> -
-- <%= f.submit "Update options", class: "usa-button" %> -
-- | - | - | - | - | - | - <% form.ordered_questions.each do |question| %> - <% next if question.question_type == "text_display" %> -<%= question.answer_field %> | - <% end %> - <%- if form.kind == "yes_no" %> -- | -- | - <% end %> -||||
+ | <%= check_box_tag "submission_ids[]", id: "toggle-all-checkbox" %> | -View | -Flag | -Archive | -Status | -- Created At - | - <% form.ordered_questions.each do |question| %> - <% next if question.question_type == "text_display" %> -<%= question.text %> | - <% end %> - <%- if form.kind == "yes_no" %> -- Page - | -- Referer + | Preview | +Status | ++ Received | - <% end %>
---|---|---|---|---|---|---|---|---|---|---|---|---|
<%= check_box_tag "submission_ids[]", submission.id, class: "batch-checkbox" %> | - <%= link_to admin_form_submission_path(submission.form, submission), class: "usa-button usa-button--outline" do %> - View - <% end %> - | -- <%- if submission.flagged? %> - <%= render "admin/submissions/flag", { submission: submission } %> - <% else %> - <%= render "admin/submissions/unflag", { submission: submission } %> - <% end %> - | -- <%- if submission.archived? %> - <%= render "admin/submissions/archive", { submission: submission } %> - <% else %> - <%= render "admin/submissions/unarchive", { submission: submission } %> - <% end %> + <%= h(submission.preview) %> | <%= submission.aasm_state %> | -- <%= format_time(submission.created_at, submission.form.time_zone.present? ? submission.form.time_zone : current_user.time_zone) %> - | - <% form.ordered_questions.each do |question| %> - <% next if question.question_type == "text_display" %> - <%- if form.ui_truncate_text_responses? %> -- <%= h(submission.send(question.answer_field.to_sym).to_s).truncate(160) %> - | - <% else %> -- <%= h(submission.send(question.answer_field.to_sym).to_s) %> - | - <% end %> - <% end %> - <%- if form.kind == "yes_no" %> -- <%= submission.page %> - | -- <%= submission.referer %> + |
+ <%= format_submission_time(submission.created_at, current_user.time_zone) %>
+
+
+ <%- if submission.flagged? %>
+ <%= render "admin/submissions/flag", { submission: submission } %>
+ <% else %>
+ <%= render "admin/submissions/unflag", { submission: submission } %>
+ <% end %>
+
+
+ <%- if submission.spam? %>
+ <%= render "admin/submissions/mark", { submission: submission } %>
+ <% else %>
+ <%= render "admin/submissions/unmark", { submission: submission } %>
+ <% end %>
+
+
+ <%- if submission.archived? %>
+ <%= render "admin/submissions/archive", { submission: submission } %>
+ <% else %>
+ <%= render "admin/submissions/unarchive", { submission: submission } %>
+ <% end %>
+
+
+ <%- if submission.deleted? %>
+ <%= render "admin/submissions/delete", { submission: submission } %>
+ <% else %>
+ <%= render "admin/submissions/undelete", { submission: submission } %>
+ <% end %>
+
+
|
- <% end %>