From 2328eba14e254a733ee8890666b8ee8c00a48dc0 Mon Sep 17 00:00:00 2001
From: Ryan Wold
Date: Fri, 14 Feb 2025 08:35:21 -0800
Subject: [PATCH 1/2] update gems
---
Gemfile.lock | 28 ++++++++++++++++------------
1 file changed, 16 insertions(+), 12 deletions(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index bbec9a594..175a20b7c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -102,8 +102,8 @@ GEM
public_suffix (>= 2.0.2, < 7.0)
aes_key_wrap (1.1.0)
ast (2.4.2)
- aws-eventstream (1.3.0)
- aws-partitions (1.1048.0)
+ aws-eventstream (1.3.1)
+ aws-partitions (1.1050.0)
aws-record (2.13.2)
aws-sdk-dynamodb (~> 1, >= 1.85.0)
aws-sdk-core (3.218.1)
@@ -222,7 +222,7 @@ GEM
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
- diff-lcs (1.5.1)
+ diff-lcs (1.6.0)
docile (1.4.1)
dotenv (3.1.7)
drb (2.2.1)
@@ -275,8 +275,8 @@ GEM
i18n (1.14.7)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
- image_processing (1.13.0)
- mini_magick (>= 4.9.5, < 5)
+ image_processing (1.14.0)
+ mini_magick (>= 4.9.5, < 6)
ruby-vips (>= 2.0.17, < 3)
importmap-rails (2.1.0)
actionpack (>= 6.0.0)
@@ -295,7 +295,7 @@ GEM
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
- json (2.10.0)
+ json (2.10.1)
json-jwt (1.16.7)
activesupport (>= 4.2)
aes_key_wrap
@@ -321,10 +321,11 @@ GEM
kramdown (2.5.1)
rexml (>= 3.3.9)
language_server-protocol (3.17.0.4)
+ lint_roller (1.1.0)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
- logger (1.6.5)
+ logger (1.6.6)
logstop (0.4.1)
logger
loofah (2.24.0)
@@ -342,7 +343,9 @@ GEM
logger
mime-types-data (~> 3.2015)
mime-types-data (3.2025.0204)
- mini_magick (4.13.2)
+ mini_magick (5.1.2)
+ benchmark
+ logger
mini_mime (1.1.5)
mini_portile2 (2.8.8)
minitest (5.25.4)
@@ -425,7 +428,7 @@ GEM
puma (6.6.0)
nio4r (~> 2.0)
racc (1.8.1)
- rack (3.1.9)
+ rack (3.1.10)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-cors (2.0.2)
@@ -521,9 +524,10 @@ GEM
rspec-support (3.13.2)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
- rubocop (1.71.2)
+ rubocop (1.72.0)
json (~> 2.3)
- language_server-protocol (>= 3.17.0)
+ language_server-protocol (~> 3.17.0.2)
+ lint_roller (~> 1.1.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
@@ -587,7 +591,7 @@ GEM
ssrf_filter (1.2.0)
stimulus-rails (1.3.4)
railties (>= 6.0.0)
- stringio (3.1.2)
+ stringio (3.1.3)
thor (1.3.2)
thread_safe (0.3.6)
tilt (2.6.0)
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 2/2] 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? %>
-
-
<%= pluralize(form.errors.count, "error") %> prohibited this form from being saved:
-
- <% form.errors.full_messages.each do |message| %>
-
-
-
Error
-
- <%= message %>
-
-
-
- <% end %>
-
- <% end %>
-
-
- Display options
-
-
-
-
-
- <%= link_to "Include Archived Submissions", "?archived=1" %>
-
-
-
-
- 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...
+
+
+
+ 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 %>
-