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? %> -
-

<%= pluralize(form.errors.count, "error") %> prohibited this form from being saved:

- - <% form.errors.full_messages.each do |message| %> -
-
-

Error

-

- <%= message %> -

-
-
- <% end %> -
- <% end %> - -

- Display options -

-
-
- - -
- HISP Form? -
- <%= f.check_box :ui_truncate_text_responses, class: "usa-checkbox__input" %> - <%= f.label :ui_truncate_text_responses, "Truncate responses to 160 characters?", class: "usa-checkbox__label" %> -
-
-
-
-
-
-

- <%= f.submit "Update options", class: "usa-button" %> -

-
-<% end %> - - diff --git a/app/views/admin/forms/responses.html.erb b/app/views/admin/forms/responses.html.erb index c103d3b62..9b287689d 100644 --- a/app/views/admin/forms/responses.html.erb +++ b/app/views/admin/forms/responses.html.erb @@ -19,9 +19,52 @@ <%- if @form.kind == "a11" %>
Loading A11 Chart...
<% end %> -
Loading Responses by Status...
+
Loading Responses by Status...
Loading Responses per Day...
-
Loading Response Detail...
+ +
+
+
+
+ <%= link_to responses_admin_form_path(@form), class: "usa-button #{"usa-button--outline" if params.keys.size > 3}" do %> + + Inbox + <% end %> +
+
+ <%= link_to responses_admin_form_path(@form, flagged: 1), class: "usa-button #{"usa-button--outline" unless params["flagged"]}" do %> + + Flagged + <% end %> +
+
+ <%= link_to responses_admin_form_path(@form, spam: 1), class: "usa-button #{"usa-button--outline" unless params["spam"]}" do %> + + Spam + <% end %> +
+
+ <%= link_to responses_admin_form_path(@form, archived: 1), class: "usa-button #{"usa-button--outline" unless params["archived"]}" do %> + + Archived + <% end %> +
+
+ <%= link_to responses_admin_form_path(@form, deleted: 1), class: "usa-button #{"usa-button--outline" unless params["deleted"]}" do %> + + Trash + <% end %> +
+
+
+ +
+
+
+ Loading Response Detail... +
<%- if @form.kind == "a11" %>
Loading Performance.gov report...
<% end %> @@ -32,34 +75,30 @@ $(function() { <%- if @form.kind == "a11" %> $.ajax({ - url: "/admin/submissions/a11_analysis?form_id=<%= @form.short_uuid %>", + url: "/admin/submissions/a11_analysis?<%= raw @search_params.to_query %>", type: 'get' }); $.ajax({ - url: "/admin/submissions/a11_chart?form_id=<%= @form.short_uuid %>", + url: "/admin/submissions/a11_chart?<%= raw @search_params.to_query %>", type: 'get' }); $.ajax({ - url: "/admin/submissions/performance_gov?form_id=<%= @form.short_uuid %>", + url: "/admin/submissions/performance_gov?<%= raw @search_params.to_query %>", type: 'get' }); <% end %> $.ajax({ - url: "/admin/submissions/responses_per_day?form_id=<%= @form.short_uuid %>", + url: "/admin/submissions/responses_per_day?<%= raw @search_params.to_query %>", type: 'get' }); $.ajax({ - url: "/admin/submissions/responses_by_status?form_id=<%= @form.short_uuid %>", + url: "/admin/submissions/responses_by_status?<%= raw @search_params.to_query %>", type: 'get' }); $.ajax({ - <%- if params["archived"].present? && params["archived"] == "1" %> - url: "/admin/submissions/submissions_table?form_id=<%= @form.short_uuid %>&archived=1", - <% else %> - url: "/admin/submissions/submissions_table?form_id=<%= @form.short_uuid %>", - <% end %> + url: "/admin/submissions/submissions_table?<%= raw @search_params.to_query %>", type: 'get' }); }); diff --git a/app/views/admin/reporting/form_logos.html.erb b/app/views/admin/reporting/form_logos.html.erb index 2d163f9f6..1eee842a9 100644 --- a/app/views/admin/reporting/form_logos.html.erb +++ b/app/views/admin/reporting/form_logos.html.erb @@ -9,7 +9,7 @@ Form logos <% end %>

-
+
diff --git a/app/views/admin/reporting/form_whitelist.html.erb b/app/views/admin/reporting/form_whitelist.html.erb index 2e3e00314..b1be52f2f 100644 --- a/app/views/admin/reporting/form_whitelist.html.erb +++ b/app/views/admin/reporting/form_whitelist.html.erb @@ -9,7 +9,7 @@ Where forms are hosted <% end %>

-
+
diff --git a/app/views/admin/submissions/_archive.html.erb b/app/views/admin/submissions/_archive.html.erb index b29ec87cc..d32126aa4 100644 --- a/app/views/admin/submissions/_archive.html.erb +++ b/app/views/admin/submissions/_archive.html.erb @@ -1,11 +1,11 @@ <%= link_to unarchive_admin_form_submission_path(submission.form, submission), id: "archive-submission-#{submission.id}", - class: "usa-button usa-button--secondary ", + class: "text-secondary ", "aria-label" => "Un-archive submission #{submission.id}", title: "Un-archive submission #{submission.id}", data: { confirm: 'Are you sure?' }, method: :post, remote: true, rel: "nofollow" do %> - + <% end %> diff --git a/app/views/admin/submissions/_delete.html.erb b/app/views/admin/submissions/_delete.html.erb new file mode 100644 index 000000000..ebda5bc77 --- /dev/null +++ b/app/views/admin/submissions/_delete.html.erb @@ -0,0 +1,11 @@ +<%= link_to undelete_admin_form_submission_path(submission.form, submission), + id: "delete-submission-#{submission.id}", + class: "text-secondary ", + "aria-label" => "Un-delete submission #{submission.id}", + title: "Un-delete submission #{submission.id}", + data: { confirm: 'Are you sure?' }, + method: :post, + remote: true, + rel: "nofollow" do %> + +<% end %> diff --git a/app/views/admin/submissions/_flag.html.erb b/app/views/admin/submissions/_flag.html.erb index da23915c7..7759967ac 100644 --- a/app/views/admin/submissions/_flag.html.erb +++ b/app/views/admin/submissions/_flag.html.erb @@ -1,6 +1,6 @@ <%= link_to unflag_admin_form_submission_path(submission.form, submission), id: "flag-submission-#{submission.id}", - class: "usa-button usa-button--secondary", + class: "text-secondary", "aria-label" => "Un-flag submission #{submission.id}", title: "Un-flag submission #{submission.id}", data: { confirm: 'Are you sure?' }, diff --git a/app/views/admin/submissions/_mark.html.erb b/app/views/admin/submissions/_mark.html.erb new file mode 100644 index 000000000..84b256845 --- /dev/null +++ b/app/views/admin/submissions/_mark.html.erb @@ -0,0 +1,11 @@ +<%= link_to unmark_admin_form_submission_path(submission.form, submission), + id: "mark-submission-#{submission.id}", + class: "text-secondary ", + "aria-label" => "Un-mark submission #{submission.id} as spam", + title: "Un-mark submission #{submission.id} as spam", + data: { confirm: 'Are you sure?' }, + method: :post, + remote: true, + rel: "nofollow" do %> + +<% end %> diff --git a/app/views/admin/submissions/_response_pagination.html.erb b/app/views/admin/submissions/_response_pagination.html.erb new file mode 100644 index 000000000..2a70ee4a2 --- /dev/null +++ b/app/views/admin/submissions/_response_pagination.html.erb @@ -0,0 +1,6 @@ +
+
+ <%= page_entries_info submissions %> +
+ <%= paginate submissions, remote: true %> +
diff --git a/app/views/admin/submissions/_status_form.html.erb b/app/views/admin/submissions/_status_form.html.erb index b550300fa..d4d7f503c 100644 --- a/app/views/admin/submissions/_status_form.html.erb +++ b/app/views/admin/submissions/_status_form.html.erb @@ -37,17 +37,3 @@ <% end %> - - diff --git a/app/views/admin/submissions/_submissions.html.erb b/app/views/admin/submissions/_submissions.html.erb index 1901b2b15..58b131918 100644 --- a/app/views/admin/submissions/_submissions.html.erb +++ b/app/views/admin/submissions/_submissions.html.erb @@ -1,20 +1,26 @@ -<% @tags = form.submission_tags %> + +<% @tags = form.submission_tags %> <%- if @tags.any? %>
@@ -22,17 +28,17 @@
Filter by tag + <%- if params[:tag].present? %> + · + + + Clear tag filter + + + <% end %>
- <%- if params[:tag].present? %> - · - - - Clear tag filter - - - <% end %>
<% @tags.uniq.each do |tag| %> @@ -48,26 +54,23 @@
<% end %> -

- <%= page_entries_info submissions, entry_name: 'Responses' %> + +
<%- if params[:tag] %> <%- if params[:tag] && submissions.size == 0 %> <% form.remove_submission_tag!(params[:tag])%> <% end %> tagged with <%= params[:tag] %> <% end %> -

-

- <%= paginate submissions, remote: true %> -

-
+
+
<%= form_with url: bulk_update_admin_form_submissions_path(form), method: :post do |f| %>
+ +
-
+
- - - - - - - <% form.ordered_questions.each do |question| %> - <% next if question.question_type == "text_display" %> - - <% end %> - <%- if form.kind == "yes_no" %> - - - <% end %> - - - - - - - - - <% form.ordered_questions.each do |question| %> - <% next if question.question_type == "text_display" %> - - <% end %> - <%- if form.kind == "yes_no" %> - - + + - <% end %> @@ -136,53 +122,51 @@ <% elsif submission.archived? %> <% next %> <% end %> - + - - - - <% form.ordered_questions.each do |question| %> - <% next if question.question_type == "text_display" %> - <%- if form.ui_truncate_text_responses? %> - - <% else %> - - <% end %> - <% end %> - <%- if form.kind == "yes_no" %> - - - <% end %> <% end %> @@ -251,4 +235,13 @@ updateBatchActionsVisibility(); }) + + document.querySelectorAll('tbody tr').forEach(row => { + row.addEventListener('click', function(event) { + // Prevent navigation if clicking inside a button or checkbox + if (!event.target.closest('a, button, input')) { + window.location.href = this.dataset.url; + } + }); + }); diff --git a/app/views/admin/submissions/_unarchive.html.erb b/app/views/admin/submissions/_unarchive.html.erb index 455283347..c5e4f2f5f 100644 --- a/app/views/admin/submissions/_unarchive.html.erb +++ b/app/views/admin/submissions/_unarchive.html.erb @@ -1,11 +1,11 @@ <%= link_to archive_admin_form_submission_path(submission.form, submission), id: "archive-submission-#{submission.id}", - class: "usa-button usa-button--accent-cool", + class: "text-base", "aria-label" => "Archive submission #{submission.id}", title: "Archive submission #{submission.id}", data: { confirm: 'Are you sure?' }, method: :post, remote: true, rel: "nofollow" do %> - + <% end %> diff --git a/app/views/admin/submissions/_undelete.html.erb b/app/views/admin/submissions/_undelete.html.erb new file mode 100644 index 000000000..633b27dba --- /dev/null +++ b/app/views/admin/submissions/_undelete.html.erb @@ -0,0 +1,11 @@ +<%= link_to delete_admin_form_submission_path(submission.form, submission), + id: "delete-submission-#{submission.id}", + class: "text-base", + "aria-label" => "Delete submission #{submission.id}", + title: "Delete submission #{submission.id}", + data: { confirm: 'Are you sure?' }, + method: :post, + remote: true, + rel: "nofollow" do %> + +<% end %> diff --git a/app/views/admin/submissions/_unflag.html.erb b/app/views/admin/submissions/_unflag.html.erb index 4741227f5..aed2c2d64 100644 --- a/app/views/admin/submissions/_unflag.html.erb +++ b/app/views/admin/submissions/_unflag.html.erb @@ -1,6 +1,6 @@ <%= link_to flag_admin_form_submission_path(submission.form, submission), id: "flag-submission-#{submission.id}", - class: "usa-button", + class: "text-base", "aria-label" => "Flag submission #{submission.id}", title: "Flag submission #{submission.id}", data: { confirm: 'Are you sure?' }, diff --git a/app/views/admin/submissions/_unmark.html.erb b/app/views/admin/submissions/_unmark.html.erb new file mode 100644 index 000000000..e9f4b7063 --- /dev/null +++ b/app/views/admin/submissions/_unmark.html.erb @@ -0,0 +1,11 @@ +<%= link_to mark_admin_form_submission_path(submission.form, submission), + id: "mark-submission-#{submission.id}", + class: "text-base", + "aria-label" => "Mark submission #{submission.id} as spam", + title: "Mark submission #{submission.id} as spam", + data: { confirm: 'Are you sure?' }, + method: :post, + remote: true, + rel: "nofollow" do %> + +<% end %> diff --git a/app/views/admin/submissions/delete.js.erb b/app/views/admin/submissions/delete.js.erb new file mode 100644 index 000000000..b8561e872 --- /dev/null +++ b/app/views/admin/submissions/delete.js.erb @@ -0,0 +1 @@ +$(".response[data-id='<%= @submission.uuid %>']").remove(); diff --git a/app/views/admin/submissions/flag.js.erb b/app/views/admin/submissions/flag.js.erb index de759b785..0b98b6488 100644 --- a/app/views/admin/submissions/flag.js.erb +++ b/app/views/admin/submissions/flag.js.erb @@ -1 +1 @@ -$(".response[data-id='<%= @submission.uuid %>'] .flagged").html("<%= j (render partial: 'flag', :locals => { submission: @submission }) %>") +$(".response[data-id='<%= @submission.uuid %>'] .flagged").html("<%= j (render partial: 'flag', locals: { submission: @submission }) %>") diff --git a/app/views/admin/submissions/index.html.erb b/app/views/admin/submissions/index.html.erb index b4ae49015..7c9939059 100644 --- a/app/views/admin/submissions/index.html.erb +++ b/app/views/admin/submissions/index.html.erb @@ -51,7 +51,7 @@ -
+
+
+ Reportable responses exclude flagged, spam, and deleted responses. +
<%- if form.kind == "a11_v2" %>
@@ -90,12 +93,14 @@ <% end %> <% else %> -
-
-

- Export is not available. - This Form has yet to receive any Responses. -

+
+
+
+

+ Export is not available. + This Form has yet to receive any Responses. +

+
<% end %> diff --git a/app/views/admin/submissions/mark.js.erb b/app/views/admin/submissions/mark.js.erb new file mode 100644 index 000000000..f487b494b --- /dev/null +++ b/app/views/admin/submissions/mark.js.erb @@ -0,0 +1 @@ +$(".response[data-id='<%= @submission.uuid %>'] .marked").html("<%= j (render partial: 'flag', locals: { submission: @submission }) %>") diff --git a/app/views/admin/submissions/show.html.erb b/app/views/admin/submissions/show.html.erb index e5a110b78..642ece8bd 100644 --- a/app/views/admin/submissions/show.html.erb +++ b/app/views/admin/submissions/show.html.erb @@ -94,7 +94,7 @@ Metadata
-
- <%= question.answer_field %> - -
+ <%= check_box_tag "submission_ids[]", id: "toggle-all-checkbox" %> ViewFlagArchiveStatus - Created At - <%= question.text %> - Page - - Referer + PreviewStatus + Received
<%= 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) %> - - <%= h(submission.send(question.answer_field.to_sym).to_s).truncate(160) %> - - <%= h(submission.send(question.answer_field.to_sym).to_s) %> - - <%= 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 %> + +
+
@@ -136,7 +136,7 @@ User agent @@ -144,7 +144,7 @@ Submitted from hostname @@ -187,6 +187,14 @@ <%= @submission.flagged %> + + + + + + + + + + + +
@@ -128,7 +128,7 @@ Location code - <%= sanitize(@submission.location_code) %> + <%= h(@submission.location_code) %>
- <%= sanitize(@submission.user_agent) %> + <%= h(@submission.user_agent) %>
- <%= sanitize(@submission.hostname) %> + <%= h(@submission.hostname) %>
+ Spam + + <%= @submission.spam? %> +
Archived @@ -195,6 +203,22 @@ <%= @submission.archived? %>
+ Deleted + + <%= @submission.deleted? %> +
+ Deleted at + + <%= @submission.deleted_at %> +
diff --git a/app/views/admin/submissions/submissions_table.js.erb b/app/views/admin/submissions/submissions_table.js.erb index 6d52fc203..037bf4f68 100644 --- a/app/views/admin/submissions/submissions_table.js.erb +++ b/app/views/admin/submissions/submissions_table.js.erb @@ -1 +1,2 @@ -$(".submissions-table-widget").html("<%= escape_javascript render(partial: 'admin/submissions/submissions_table', locals: { form: @form, submissions: @submissions }) %>"); \ No newline at end of file +$(".submissions-table-widget").html("<%= escape_javascript render('admin/submissions/submissions_table', { form: @form, submissions: @submissions }) %>"); +$("#response-pagination").html("<%= escape_javascript render('admin/submissions/response_pagination', { submissions: @submissions }) %>"); \ No newline at end of file diff --git a/app/views/admin/submissions/undelete.js.erb b/app/views/admin/submissions/undelete.js.erb new file mode 100644 index 000000000..3036d6d04 --- /dev/null +++ b/app/views/admin/submissions/undelete.js.erb @@ -0,0 +1 @@ +$(".response[data-id='<%= @submission.uuid %>'] .deleted").html("<%= j (render 'undeleted', { submission: @submission }) %>") diff --git a/app/views/admin/submissions/unmark.js.erb b/app/views/admin/submissions/unmark.js.erb new file mode 100644 index 000000000..0f966c131 --- /dev/null +++ b/app/views/admin/submissions/unmark.js.erb @@ -0,0 +1 @@ +$(".response[data-id='<%= @submission.uuid %>'] .marked").html("<%= j (render partial: 'unmark', locals: { submission: @submission }) %>") diff --git a/app/views/components/_responses_by_status.html.erb b/app/views/components/_responses_by_status.html.erb index f6f5eb5ca..ff882f5c8 100644 --- a/app/views/components/_responses_by_status.html.erb +++ b/app/views/components/_responses_by_status.html.erb @@ -1,7 +1,7 @@ -