diff --git a/Gemfile b/Gemfile
index a4c83f2c8..d4352ce40 100644
--- a/Gemfile
+++ b/Gemfile
@@ -4,25 +4,25 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '3.3.4'
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
-gem "rails", "~> 8.0"
+gem 'rails', '~> 8.0'
# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
-gem "sprockets-rails"
+gem 'sprockets-rails'
# Use postgresql as the database for Active Record
-gem "pg"
+gem 'pg'
# Use the Puma web server [https://github.com/puma/puma]
-gem "puma"
+gem 'puma'
-# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
-gem "importmap-rails", ">= 2.0.0"
+
+gem "importmap-rails", ">= 2.2.0"
# Hotwire"s SPA-like page accelerator [https://turbo.hotwired.dev]
-gem "turbo-rails"
+gem "turbo-rails", ">= 2.0.14"
# Hotwire"s modest JavaScript framework [https://stimulus.hotwired.dev]
-gem "stimulus-rails"
+gem 'stimulus-rails'
# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
# gem "kredis"
@@ -31,16 +31,16 @@ gem "stimulus-rails"
# gem "bcrypt", "~> 3.1.7"
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
-gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby]
+gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
# Reduces boot times through caching; required in config/boot.rb
-gem "bootsnap", require: false
+gem 'bootsnap', require: false
# Use Sass to process CSS
-gem "sassc-rails"
+gem 'sassc-rails'
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
-gem "image_processing", "~> 1.12"
+gem 'image_processing', '~> 1.12'
gem 'active_model_serializers'
gem 'acts-as-list'
@@ -51,7 +51,7 @@ gem 'carrierwave', '>= 2.2.1'
gem 'csv'
gem 'devise', '>= 4.8.1'
gem 'fog-aws', '>= 3.15.0'
-gem "jbuilder"
+gem 'jbuilder'
gem 'jquery-rails'
gem 'kaminari'
gem 'kramdown'
@@ -62,35 +62,39 @@ gem 'omniauth-github'
gem 'omniauth_login_dot_gov', git: 'https://github.com/18F/omniauth_login_dot_gov.git', branch: 'main'
gem 'omniauth-rails_csrf_protection'
gem 'rack-attack'
-gem 'rack-cors', require: 'rack/cors'
-# Use Redis to cache Touchpoints in all envs
+gem 'rack-cors', '>= 3.0.0', require: 'rack/cors'
+# Use Redis to cache Touchpoints in all envs=
gem 'redis-client'
gem 'redis-namespace'
-gem 'sidekiq', '>= 6.5.0'
+gem 'sidekiq', '>= 8.0.4'
gem 'json-jwt'
gem 'aasm'
+gem 'acts-as-taggable-on'
+gem 'json-jwt'
gem 'logstop'
gem 'paper_trail'
-gem 'acts-as-taggable-on'
-gem "rolify"
+gem 'redis-client'
+gem 'redis-namespace'
+gem 'rolify'
+gem 'sidekiq', '>= 6.5.0'
group :development, :test do
gem 'dotenv'
gem 'pry'
end
-group :development, :staging do
+group :development, :staging, :test do
gem 'faker'
end
group :development do
gem 'aasm-diagram'
- gem "brakeman"
+ gem 'brakeman'
gem 'bullet'
- gem "bundler-audit"
+ gem 'bundler-audit'
gem 'listen'
gem 'rails-erd'
- gem "rubocop-rails"
+ gem "rubocop-rails", ">= 2.32.0"
gem "rubocop-rspec"
gem 'web-console'
end
@@ -99,10 +103,10 @@ group :test do
gem 'axe-core-rspec'
gem 'capybara'
gem 'database_cleaner'
- gem 'factory_bot_rails'
+ gem 'factory_bot_rails', '>= 6.5.0'
gem 'rails-controller-testing'
gem 'rspec_junit_formatter'
- gem 'rspec-rails'
+ gem 'rspec-rails', '>= 8.0.1'
gem 'selenium-webdriver'
gem 'simplecov', require: false
end
diff --git a/Gemfile.lock b/Gemfile.lock
index be5895b16..2f9c31343 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -13,7 +13,7 @@ GIT
GEM
remote: https://rubygems.org/
specs:
- aasm (5.5.0)
+ aasm (5.5.1)
concurrent-ruby (~> 1.0)
aasm-diagram (0.1.3)
aasm (~> 5.0, >= 4.12)
@@ -106,32 +106,33 @@ GEM
actionmailer (>= 7.1.0)
aws-sdk-ses (~> 1, >= 1.50.0)
aws-sdk-sesv2 (~> 1, >= 1.34.0)
- aws-eventstream (1.3.2)
- aws-partitions (1.1096.0)
- aws-sdk-core (3.223.0)
+ aws-eventstream (1.4.0)
+ aws-partitions (1.1140.0)
+ aws-sdk-core (3.228.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
base64
+ bigdecimal
jmespath (~> 1, >= 1.6.1)
logger
- aws-sdk-kms (1.100.0)
- aws-sdk-core (~> 3, >= 3.216.0)
+ aws-sdk-kms (1.109.0)
+ aws-sdk-core (~> 3, >= 3.228.0)
aws-sigv4 (~> 1.5)
aws-sdk-rails (5.1.0)
aws-sdk-core (~> 3)
railties (>= 7.1.0)
- aws-sdk-s3 (1.185.0)
- aws-sdk-core (~> 3, >= 3.216.0)
+ aws-sdk-s3 (1.195.0)
+ aws-sdk-core (~> 3, >= 3.228.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
- aws-sdk-ses (1.83.0)
- aws-sdk-core (~> 3, >= 3.216.0)
+ aws-sdk-ses (1.87.0)
+ aws-sdk-core (~> 3, >= 3.228.0)
aws-sigv4 (~> 1.5)
- aws-sdk-sesv2 (1.75.0)
- aws-sdk-core (~> 3, >= 3.216.0)
+ aws-sdk-sesv2 (1.81.0)
+ aws-sdk-core (~> 3, >= 3.228.0)
aws-sigv4 (~> 1.5)
- aws-sigv4 (1.11.0)
+ aws-sigv4 (1.12.1)
aws-eventstream (~> 1, >= 1.0.2)
axe-core-api (4.10.3)
dumb_delegator
@@ -148,16 +149,16 @@ GEM
thread_safe (~> 0.3, >= 0.3.1)
base64 (0.2.0)
bcrypt (3.1.20)
- benchmark (0.4.0)
- bigdecimal (3.1.9)
+ benchmark (0.4.1)
+ bigdecimal (3.2.2)
bindata (2.5.1)
bindex (0.8.1)
- bootsnap (1.18.4)
+ bootsnap (1.18.6)
msgpack (~> 1.2)
- brakeman (7.0.2)
+ brakeman (7.1.0)
racc
builder (3.3.0)
- bullet (8.0.5)
+ bullet (8.0.8)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
bundler-audit (0.9.2)
@@ -188,12 +189,12 @@ GEM
concurrent-ruby (1.3.5)
connection_pool (2.5.3)
crass (1.0.6)
- csv (3.3.4)
+ csv (3.3.5)
database_cleaner (2.1.0)
database_cleaner-active_record (>= 2, < 3)
- database_cleaner-active_record (2.2.0)
+ database_cleaner-active_record (2.2.2)
activerecord (>= 5.a)
- database_cleaner-core (~> 2.0.0)
+ database_cleaner-core (~> 2.0)
database_cleaner-core (2.0.1)
date (3.4.1)
descendants_tracker (0.0.4)
@@ -204,28 +205,29 @@ GEM
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
- diff-lcs (1.6.1)
+ diff-lcs (1.6.2)
docile (1.4.1)
dotenv (3.1.8)
- drb (2.2.1)
+ drb (2.2.3)
dumb_delegator (1.1.0)
+ erb (5.0.2)
erubi (1.13.1)
- excon (1.2.5)
+ excon (1.2.8)
logger
- factory_bot (6.5.1)
+ factory_bot (6.5.4)
activesupport (>= 6.1.0)
- factory_bot_rails (6.4.4)
+ factory_bot_rails (6.5.0)
factory_bot (~> 6.5)
- railties (>= 5.0.0)
- faker (3.5.1)
+ railties (>= 6.1.0)
+ faker (3.5.2)
i18n (>= 1.8.11, < 2)
- faraday (2.13.1)
+ faraday (2.13.4)
faraday-net_http (>= 2.0, < 3.5)
json
logger
faraday-follow_redirects (0.3.0)
faraday (>= 1, < 3)
- faraday-net_http (3.4.0)
+ faraday-net_http (3.4.1)
net-http (>= 0.5.0)
ffi (1.17.2-aarch64-linux-gnu)
ffi (1.17.2-aarch64-linux-musl)
@@ -237,7 +239,7 @@ GEM
ffi (1.17.2-x86_64-darwin)
ffi (1.17.2-x86_64-linux-gnu)
ffi (1.17.2-x86_64-linux-musl)
- fog-aws (3.31.0)
+ fog-aws (3.32.0)
base64 (~> 0.2.0)
fog-core (~> 2.6)
fog-json (~> 1.1)
@@ -253,7 +255,7 @@ GEM
fog-xml (0.1.5)
fog-core
nokogiri (>= 1.5.11, < 2.0.0)
- formatador (1.1.0)
+ formatador (1.1.1)
globalid (1.2.1)
activesupport (>= 6.1)
hashie (5.0.0)
@@ -263,11 +265,11 @@ GEM
image_processing (1.14.0)
mini_magick (>= 4.9.5, < 6)
ruby-vips (>= 2.0.17, < 3)
- importmap-rails (2.1.0)
+ importmap-rails (2.2.2)
actionpack (>= 6.0.0)
activesupport (>= 6.0.0)
railties (>= 6.0.0)
- io-console (0.8.0)
+ io-console (0.8.1)
irb (1.15.2)
pp (>= 0.6.0)
rdoc (>= 4.0.0)
@@ -280,7 +282,7 @@ GEM
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
- json (2.11.3)
+ json (2.13.2)
json-jwt (1.16.7)
activesupport (>= 4.2)
aes_key_wrap
@@ -289,7 +291,7 @@ GEM
faraday (~> 2.0)
faraday-follow_redirects
jsonapi-renderer (0.2.2)
- jwt (2.10.1)
+ jwt (2.10.2)
base64
kaminari (1.2.2)
activesupport (>= 4.1.0)
@@ -305,7 +307,7 @@ GEM
kaminari-core (1.2.2)
kramdown (2.5.1)
rexml (>= 3.3.9)
- language_server-protocol (3.17.0.4)
+ language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
@@ -313,7 +315,7 @@ GEM
logger (1.7.0)
logstop (0.4.1)
logger
- loofah (2.24.0)
+ loofah (2.24.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
@@ -322,25 +324,24 @@ GEM
net-pop
net-smtp
marcel (1.0.4)
- matrix (0.4.2)
+ matrix (0.4.3)
method_source (1.1.0)
- mime-types (3.6.2)
+ mime-types (3.7.0)
logger
- mime-types-data (~> 3.2015)
- mime-types-data (3.2025.0429)
- mini_magick (5.2.0)
- benchmark
+ mime-types-data (~> 3.2025, >= 3.2025.0507)
+ mime-types-data (3.2025.0729)
+ mini_magick (5.3.0)
logger
mini_mime (1.1.5)
- mini_portile2 (2.8.8)
+ mini_portile2 (2.8.9)
minitest (5.25.5)
msgpack (1.8.0)
- multi_json (1.15.0)
+ multi_json (1.17.0)
multi_xml (0.7.2)
bigdecimal (~> 3.1)
net-http (0.6.0)
uri
- net-imap (0.5.8)
+ net-imap (0.5.9)
date
net-protocol
net-pop (0.1.2)
@@ -349,34 +350,35 @@ GEM
timeout
net-smtp (0.5.1)
net-protocol
- newrelic_rpm (9.19.0)
+ newrelic_rpm (9.20.0)
nio4r (2.7.4)
- nokogiri (1.18.8)
+ nokogiri (1.18.9)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
- nokogiri (1.18.8-aarch64-linux-gnu)
+ nokogiri (1.18.9-aarch64-linux-gnu)
racc (~> 1.4)
- nokogiri (1.18.8-aarch64-linux-musl)
+ nokogiri (1.18.9-aarch64-linux-musl)
racc (~> 1.4)
- nokogiri (1.18.8-arm-linux-gnu)
+ nokogiri (1.18.9-arm-linux-gnu)
racc (~> 1.4)
- nokogiri (1.18.8-arm-linux-musl)
+ nokogiri (1.18.9-arm-linux-musl)
racc (~> 1.4)
- nokogiri (1.18.8-arm64-darwin)
+ nokogiri (1.18.9-arm64-darwin)
racc (~> 1.4)
- nokogiri (1.18.8-x86_64-darwin)
+ nokogiri (1.18.9-x86_64-darwin)
racc (~> 1.4)
- nokogiri (1.18.8-x86_64-linux-gnu)
+ nokogiri (1.18.9-x86_64-linux-gnu)
racc (~> 1.4)
- nokogiri (1.18.8-x86_64-linux-musl)
+ nokogiri (1.18.9-x86_64-linux-musl)
racc (~> 1.4)
- oauth2 (2.0.9)
- faraday (>= 0.17.3, < 3.0)
- jwt (>= 1.0, < 3.0)
+ oauth2 (2.0.12)
+ faraday (>= 0.17.3, < 4.0)
+ jwt (>= 1.0, < 4.0)
+ logger (~> 1.2)
multi_xml (~> 0.5)
rack (>= 1.2, < 4)
- snaky_hash (~> 2.0)
- version_gem (~> 1.1)
+ snaky_hash (~> 2.0, >= 2.0.3)
+ version_gem (>= 1.1.8, < 3)
omniauth (2.1.3)
hashie (>= 3.4.6)
rack (>= 2.2.3)
@@ -391,15 +393,19 @@ GEM
actionpack (>= 4.2)
omniauth (~> 2.0)
orm_adapter (0.5.0)
- ostruct (0.6.1)
+ ostruct (0.6.3)
paper_trail (16.0.0)
activerecord (>= 6.1)
request_store (~> 1.4)
parallel (1.27.0)
- parser (3.3.8.0)
+ parser (3.3.9.0)
ast (~> 2.4.1)
racc
- pg (1.5.9)
+ pg (1.6.0)
+ pg (1.6.0-aarch64-linux)
+ pg (1.6.0-arm64-darwin)
+ pg (1.6.0-x86_64-darwin)
+ pg (1.6.0-x86_64-linux)
pp (0.6.2)
prettyprint
prettyprint (0.2.0)
@@ -407,18 +413,19 @@ GEM
pry (0.15.2)
coderay (~> 1.1)
method_source (~> 1.0)
- psych (5.2.4)
+ psych (5.2.6)
date
stringio
public_suffix (6.0.2)
- puma (6.6.0)
+ puma (6.6.1)
nio4r (~> 2.0)
racc (1.8.1)
- rack (3.1.14)
+ rack (3.2.0)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
- rack-cors (2.0.2)
- rack (>= 2.0.0)
+ rack-cors (3.0.0)
+ logger
+ rack (>= 3.0.14)
rack-protection (4.1.1)
base64 (>= 0.1.0)
logger (>= 1.6.0)
@@ -448,7 +455,7 @@ GEM
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
- rails-dom-testing (2.2.0)
+ rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
@@ -469,20 +476,21 @@ GEM
thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.6)
rainbow (3.1.1)
- rake (13.2.1)
+ rake (13.3.0)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
- rdoc (6.13.1)
+ rdoc (6.14.2)
+ erb
psych (>= 4.0.0)
- redis (5.4.0)
+ redis (5.4.1)
redis-client (>= 0.22.0)
- redis-client (0.24.0)
+ redis-client (0.25.1)
connection_pool
redis-namespace (1.11.0)
redis (>= 4)
regexp_parser (2.10.0)
- reline (0.6.1)
+ reline (0.6.2)
io-console (~> 0.5)
request_store (1.7.0)
rack (>= 1.4)
@@ -491,15 +499,15 @@ GEM
railties (>= 5.2)
rexml (3.4.1)
rolify (6.0.1)
- rspec-core (3.13.3)
+ rspec-core (3.13.5)
rspec-support (~> 3.13.0)
- rspec-expectations (3.13.4)
+ rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
- rspec-mocks (3.13.3)
+ rspec-mocks (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
- rspec-rails (8.0.0)
+ rspec-rails (8.0.1)
actionpack (>= 7.2)
activesupport (>= 7.2)
railties (>= 7.2)
@@ -507,10 +515,10 @@ GEM
rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
- rspec-support (3.13.3)
+ rspec-support (3.13.4)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
- rubocop (1.75.4)
+ rubocop (1.79.1)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@@ -518,25 +526,25 @@ GEM
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
- rubocop-ast (>= 1.44.0, < 2.0)
+ rubocop-ast (>= 1.46.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
- rubocop-ast (1.44.1)
+ rubocop-ast (1.46.0)
parser (>= 3.3.7.2)
prism (~> 1.4)
- rubocop-rails (2.31.0)
+ rubocop-rails (2.32.0)
activesupport (>= 4.2.0)
lint_roller (~> 1.1)
rack (>= 1.1)
rubocop (>= 1.75.0, < 2.0)
- rubocop-ast (>= 1.38.0, < 2.0)
+ rubocop-ast (>= 1.44.0, < 2.0)
rubocop-rspec (3.6.0)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
ruby-graphviz (1.2.5)
rexml
ruby-progressbar (1.13.0)
- ruby-vips (2.2.3)
+ ruby-vips (2.2.4)
ffi (~> 1.12)
logger
rubyzip (2.4.1)
@@ -549,13 +557,13 @@ GEM
sprockets-rails
tilt
securerandom (0.4.1)
- selenium-webdriver (4.31.0)
+ selenium-webdriver (4.34.0)
base64 (~> 0.2)
logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
- sidekiq (8.0.3)
+ sidekiq (8.0.6)
connection_pool (>= 2.5.0)
json (>= 2.9.0)
logger (>= 1.6.2)
@@ -565,11 +573,11 @@ GEM
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
- simplecov-html (0.13.1)
+ simplecov-html (0.13.2)
simplecov_json_formatter (0.1.4)
- snaky_hash (2.0.1)
- hashie
- version_gem (~> 1.1, >= 1.1.1)
+ snaky_hash (2.0.3)
+ hashie (>= 0.1.0, < 6)
+ version_gem (>= 1.1.8, < 3)
sprockets (4.2.2)
concurrent-ruby (~> 1.0)
logger
@@ -578,15 +586,15 @@ GEM
actionpack (>= 6.1)
activesupport (>= 6.1)
sprockets (>= 3.0.0)
- ssrf_filter (1.2.0)
+ ssrf_filter (1.3.0)
stimulus-rails (1.3.4)
railties (>= 6.0.0)
stringio (3.1.7)
- thor (1.3.2)
+ thor (1.4.0)
thread_safe (0.3.6)
- tilt (2.6.0)
+ tilt (2.6.1)
timeout (0.4.3)
- turbo-rails (2.0.13)
+ turbo-rails (2.0.16)
actionpack (>= 7.1.0)
railties (>= 7.1.0)
tzinfo (2.0.6)
@@ -594,10 +602,10 @@ GEM
unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
- uniform_notifier (1.16.0)
+ uniform_notifier (1.17.0)
uri (1.0.3)
useragent (0.16.11)
- version_gem (1.1.7)
+ version_gem (1.1.8)
virtus (2.0.0)
axiom-types (~> 0.1)
coercible (~> 1.0)
@@ -610,13 +618,13 @@ GEM
bindex (>= 0.4.0)
railties (>= 6.0.0)
websocket (1.2.11)
- websocket-driver (0.7.7)
+ websocket-driver (0.8.0)
base64
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
- zeitwerk (2.7.2)
+ zeitwerk (2.7.3)
PLATFORMS
aarch64-linux
@@ -654,11 +662,11 @@ DEPENDENCIES
database_cleaner
devise (>= 4.8.1)
dotenv
- factory_bot_rails
+ factory_bot_rails (>= 6.5.0)
faker
fog-aws (>= 3.15.0)
image_processing (~> 1.12)
- importmap-rails (>= 2.0.0)
+ importmap-rails (>= 2.2.0)
jbuilder
jquery-rails
json-jwt
@@ -684,7 +692,7 @@ DEPENDENCIES
redis-client
redis-namespace
rolify
- rspec-rails
+ rspec-rails (>= 8.0.1)
rspec_junit_formatter
rubocop-rails
rubocop-rspec
@@ -694,7 +702,7 @@ DEPENDENCIES
simplecov
sprockets-rails
stimulus-rails
- turbo-rails
+ turbo-rails (>= 2.0.14)
tzinfo-data
web-console
diff --git a/app/controllers/admin/cx_collection_details_controller.rb b/app/controllers/admin/cx_collection_details_controller.rb
index 7ab54b0fe..54b2b399b 100644
--- a/app/controllers/admin/cx_collection_details_controller.rb
+++ b/app/controllers/admin/cx_collection_details_controller.rb
@@ -18,6 +18,18 @@ def show
def new
@cx_collection_detail = CxCollectionDetail.new
@cx_collection_detail.cx_collection_id = params[:collection_id]
+
+ if params[:form_id]
+ @form = Form.find_by_short_uuid(params[:form_id])
+ @cx_collection_detail.form = @form
+ @cx_collection_detail.service_stage_id = @form.service_stage_id
+ @cx_collection_detail.transaction_point = 'post_interaction'
+ @cx_collection_detail.survey_type = 'thumbs_up_down' if @form.kind == "a11_v2"
+ @cx_collection_detail.survey_title = @form.title
+ @cx_collection_detail.omb_control_number = @form.omb_approval_number
+ @cx_collection_detail.trust_question_text = @form.questions.first.text
+ @cx_collection_detail.volume_of_customers_provided_survey_opportunity = @form.survey_form_activations
+ end
end
def edit
@@ -35,6 +47,11 @@ def create
respond_to do |format|
if @cx_collection_detail.save
Event.log_event(Event.names[:cx_collection_detail_created], @cx_collection_detail.class.to_s, @cx_collection_detail.id, "CX Collection Detail #{@cx_collection_detail.id} created at #{DateTime.now}", current_user.id)
+
+ if @cx_collection_detail.form
+ CxCollectionDetailUpload.create!(user: current_user, cx_collection_detail: @cx_collection_detail)
+ end
+
format.html { redirect_to upload_admin_cx_collection_detail_url(@cx_collection_detail), notice: "CX Collection Detail was successfully created." }
format.json { render :upload, status: :created, location: @cx_collection_detail }
else
@@ -160,6 +177,22 @@ def set_cx_collections
end
def cx_collection_detail_params
- params.require(:cx_collection_detail).permit(:cx_collection_id, :transaction_point, :channel, :service_stage_id, :volume_of_customers, :volume_of_customers_provided_survey_opportunity, :volume_of_respondents, :omb_control_number, :federal_register_url, :reflection_text, :survey_type, :survey_title, :trust_question_text)
+ params.require(:cx_collection_detail)
+ .permit(
+ :cx_collection_id,
+ :transaction_point,
+ :channel,
+ :service_stage_id,
+ :volume_of_customers,
+ :volume_of_customers_provided_survey_opportunity,
+ :volume_of_respondents,
+ :omb_control_number,
+ :federal_register_url,
+ :reflection_text,
+ :survey_type,
+ :survey_title,
+ :trust_question_text,
+ :form_id,
+ )
end
end
diff --git a/app/controllers/admin/cx_collections_controller.rb b/app/controllers/admin/cx_collections_controller.rb
index 2c104ad1c..22f41a9c4 100644
--- a/app/controllers/admin/cx_collections_controller.rb
+++ b/app/controllers/admin/cx_collections_controller.rb
@@ -26,6 +26,8 @@ def show
def new
@cx_collection = CxCollection.new
+ @cx_collection.quarter = FiscalYear.fiscal_year_and_quarter(Date.today)[:quarter]
+ @cx_collection.fiscal_year = FiscalYear.fiscal_year_and_quarter(Date.today)[:year]
end
def edit
diff --git a/app/controllers/admin/questions_controller.rb b/app/controllers/admin/questions_controller.rb
index 5ed344ef6..3c938804c 100644
--- a/app/controllers/admin/questions_controller.rb
+++ b/app/controllers/admin/questions_controller.rb
@@ -101,7 +101,7 @@ def question_params
def first_unused_answer_field
answer_fields = Question.where(form_id: @form.id).collect(&:answer_field)
- (1..20).each do |ind|
+ (1..30).each do |ind|
af = "answer_#{ind.to_s.rjust(2, '0')}"
return af unless answer_fields.include?(af)
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index f7e22540a..be75124e6 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -167,7 +167,7 @@ def cx_collection_permissions?(cx_collection:)
return true if performance_manager_permissions?
cx_collection.organization == current_user.organization ||
- cx_collection.organization == (current_user.organization.parent ? current_user.organization.parent : nil)
+ cx_collection.organization == (current_user.organization.parent || nil)
end
helper_method :website_permissions?
diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb
index 3a6cdfd61..c24c9ef80 100644
--- a/app/controllers/submissions_controller.rb
+++ b/app/controllers/submissions_controller.rb
@@ -26,7 +26,7 @@ def create
# Catch SPAMMERS
if @form && submission_params[:fba_directive].present?
- ActiveSupport::Notifications.instrument("spam_subverted") do |payload|
+ ActiveSupport::Notifications.instrument('spam_subverted') do |payload|
payload[:request] = request
end
@@ -82,10 +82,10 @@ def create
def create_in_local_database(submission)
if submission.form.enable_turnstile?
- if verify_turnstile(params["cf-turnstile-response"])
+ if verify_turnstile(params['cf-turnstile-response'])
submission.spam_prevention_mechanism = :turnstile
else
- submission.errors.add(:base, "Turnstile verification failed")
+ submission.errors.add(:base, 'Turnstile verification failed')
end
end
@@ -96,9 +96,11 @@ def create_in_local_database(submission)
notice: 'Thank You. Response was submitted successfully.'
end
format.json do
- form_success_text = submission.form.append_id_to_success_text? ?
- submission.form.success_text + "
Your Response ID is: #{submission.uuid[-12..-1]}" :
- submission.form.success_text
+ form_success_text = if submission.form.append_id_to_success_text?
+ submission.form.success_text + "
Your Response ID is: #{submission.uuid[-12..-1]}"
+ else
+ submission.form.success_text
+ end
render json: {
submission: {
@@ -123,6 +125,16 @@ def create_in_local_database(submission)
answer_18: submission.answer_18,
answer_19: submission.answer_19,
answer_20: submission.answer_20,
+ answer_21: submission.answer_21,
+ answer_22: submission.answer_22,
+ answer_23: submission.answer_23,
+ answer_24: submission.answer_24,
+ answer_25: submission.answer_25,
+ answer_26: submission.answer_26,
+ answer_27: submission.answer_27,
+ answer_28: submission.answer_28,
+ answer_29: submission.answer_29,
+ answer_30: submission.answer_30,
form: {
id: submission.form.uuid,
name: submission.form.name,
@@ -173,20 +185,19 @@ def form_requires_verification
@form.verify_csrf?
end
-
private
def verify_turnstile(response_token)
- secret_key = ENV.fetch("TURNSTILE_SECRET_KEY", nil)
- uri = URI("https://challenges.cloudflare.com/turnstile/v0/siteverify")
+ secret_key = ENV.fetch('TURNSTILE_SECRET_KEY', nil)
+ uri = URI('https://challenges.cloudflare.com/turnstile/v0/siteverify')
response = Net::HTTP.post_form(uri, {
- "secret" => secret_key,
- "response" => response_token,
- "remoteip" => request.remote_ip
- })
+ 'secret' => secret_key,
+ 'response' => response_token,
+ 'remoteip' => request.remote_ip,
+ })
json = JSON.parse(response.body)
- json["success"] == true
+ json['success'] == true
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 368027753..b8e11ece5 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -4,7 +4,7 @@
module ApplicationHelper
def suppress_main_layout_flash?
- return true if flash && ['User successfully added', 'User successfully removed'].include?(flash.notice)
+ true if flash && ['User successfully added', 'User successfully removed'].include?(flash.notice)
end
def to_markdown(text)
@@ -23,11 +23,11 @@ def organization_abbreviation_dropdown_options
def us_timezones
[
- ActiveSupport::TimeZone["Eastern Time (US & Canada)"],
- ActiveSupport::TimeZone["Central Time (US & Canada)"],
- ActiveSupport::TimeZone["Mountain Time (US & Canada)"],
- ActiveSupport::TimeZone["Pacific Time (US & Canada)"],
- ActiveSupport::TimeZone["Hawaii"]
+ ActiveSupport::TimeZone['Eastern Time (US & Canada)'],
+ ActiveSupport::TimeZone['Central Time (US & Canada)'],
+ ActiveSupport::TimeZone['Mountain Time (US & Canada)'],
+ ActiveSupport::TimeZone['Pacific Time (US & Canada)'],
+ ActiveSupport::TimeZone['Hawaii'],
]
end
@@ -101,37 +101,31 @@ def website_status_label_tags(status)
end
def cx_collections_filters_applied?(quarter, year, status)
- [quarter, year, status].any? { |param| param.present? && param.downcase != "all" }
+ [quarter, year, status].any? { |param| param.present? && param.downcase != 'all' }
end
def cx_collections_filter_message(quarter, year, status)
parts = []
- if quarter.present? && quarter.downcase != "all"
- parts << "Q#{quarter}"
- end
+ parts << "Q#{quarter}" if quarter.present? && quarter.downcase != 'all'
- if year.present? && year.downcase != "all"
- parts << "FY#{year}"
- end
+ parts << "FY#{year}" if year.present? && year.downcase != 'all'
- if status.present? && status.downcase != "all"
- parts << status
- end
+ parts << status if status.present? && status.downcase != 'all'
- return "" if parts.empty?
+ return '' if parts.empty?
- "for " + parts.join(" ")
+ 'for ' + parts.join(' ')
end
def form_edit_component_path(question_type)
- case question_type
- when "radio_buttons", "combobox"
- "components/forms/edit/question_types/radio_button_option"
- when "dropdown"
- "components/forms/edit/question_types/dropdown_option"
- when "checkbox"
- "components/forms/edit/question_types/checkbox_option"
+ case question_type
+ when 'radio_buttons', 'combobox'
+ 'components/forms/edit/question_types/radio_button_option'
+ when 'dropdown'
+ 'components/forms/edit/question_types/dropdown_option'
+ when 'checkbox'
+ 'components/forms/edit/question_types/checkbox_option'
end
end
@@ -174,7 +168,7 @@ def question_type_javascript_params(question)
def is_at_least_form_manager?(user:, form:)
user.admin? ||
- user.organizational_form_approver && user.organization_id == form.organization_id ||
+ (user.organizational_form_approver && user.organization_id == form.organization_id) ||
form.user_role?(user:) == UserRole::Role::FormManager
end
@@ -204,6 +198,16 @@ def answer_fields
answer_18
answer_19
answer_20
+ answer_21
+ answer_22
+ answer_23
+ answer_24
+ answer_25
+ answer_26
+ answer_27
+ answer_28
+ answer_29
+ answer_30
]
end
@@ -222,11 +226,11 @@ def format_submission_time(datetime, time_zone)
start_of_year = today.beginning_of_year
if created_at >= today
- created_at.strftime("%-I:%M %p") # Today: 1:23 PM
+ 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
+ created_at.strftime('%b %e') # Current year: Jan 5
else
- created_at.strftime("%m/%d/%Y") # Last year: 01/05/2024
+ created_at.strftime('%m/%d/%Y') # Last year: 01/05/2024
end
end
@@ -240,7 +244,5 @@ def form_integrity_checksum(form:)
Digest::SHA256.base64digest(data_to_encode)
end
- def fiscal_year_and_quarter(date)
- FiscalYear.fiscal_year_and_quarter(date)
- end
+ delegate :fiscal_year_and_quarter, to: :FiscalYear
end
diff --git a/app/models/cx_collection_detail.rb b/app/models/cx_collection_detail.rb
index 06470351f..9e5541203 100644
--- a/app/models/cx_collection_detail.rb
+++ b/app/models/cx_collection_detail.rb
@@ -3,6 +3,7 @@
class CxCollectionDetail < ApplicationRecord
belongs_to :cx_collection
belongs_to :service_stage, optional: true
+ belongs_to :form, optional: true
has_many :cx_responses, dependent: :delete_all
has_many :cx_collection_detail_uploads
has_one :service_provider, through: :cx_collection
diff --git a/app/models/cx_collection_detail_upload.rb b/app/models/cx_collection_detail_upload.rb
index db825fb80..e972a089f 100644
--- a/app/models/cx_collection_detail_upload.rb
+++ b/app/models/cx_collection_detail_upload.rb
@@ -6,7 +6,7 @@ class CxCollectionDetailUpload < ApplicationRecord
belongs_to :cx_collection_detail
has_many :cx_responses, dependent: :delete_all
- after_create :process_csv_in_a_worker
+ after_create :process_records_in_a_worker
aasm do
state :created, initial: true
@@ -24,8 +24,15 @@ class CxCollectionDetailUpload < ApplicationRecord
end
end
- def process_csv_in_a_worker
- process_csv
+ def process_records_in_a_worker
+ if self.key?
+ process_csv
+ elsif self.cx_collection_detail.form
+ fiscal_quarter_dates = FiscalYear.fiscal_quarter_dates(self.cx_collection_detail.cx_collection.fiscal_year, self.cx_collection_detail.cx_collection.quarter)
+ start_date = fiscal_quarter_dates[:start_date]
+ end_date = fiscal_quarter_dates[:end_date]
+ upload_form_results(form_id: self.cx_collection_detail.form_id, start_date:, end_date:)
+ end
end
def process_csv
@@ -73,4 +80,39 @@ def process_csv
end
end
+ def upload_form_results(form_id:, start_date:, end_date:)
+ @form = Form.find(form_id)
+
+ job_id = SecureRandom.hex[0..9]
+ update_attribute(:job_id, job_id)
+
+ responses = @form.to_a11_v2_array(start_date:, end_date:)
+ responses.each do |response|
+ # Create the CxResponse record
+ CxResponse.create!({
+ cx_collection_detail_id: cx_collection_detail.id,
+ cx_collection_detail_upload_id: self.id,
+ job_id: job_id,
+ external_id: response[:id],
+ question_1: response[:answer_01],
+ positive_effectiveness: response[:answer_02_effectiveness],
+ positive_ease: response[:answer_02_ease],
+ positive_efficiency: response[:answer_02_efficiency],
+ positive_transparency: response[:answer_02_transparency],
+ positive_humanity: response[:answer_02_humanity],
+ positive_employee: response[:answer_02_employee],
+ positive_other: response[:answer_02_other],
+ negative_effectiveness: response[:answer_03_effectiveness],
+ negative_ease: response[:answer_03_ease],
+ negative_efficiency: response[:answer_03_efficiency],
+ negative_transparency: response[:answer_03_transparency],
+ negative_humanity: response[:answer_03_humanity],
+ negative_employee: response[:answer_03_employee],
+ negative_other: response[:answer_03_other],
+ question_4: response[:answer_04],
+ })
+ end
+
+ end
+
end
diff --git a/app/models/form.rb b/app/models/form.rb
index c9bbe5d03..dc1af4ae7 100644
--- a/app/models/form.rb
+++ b/app/models/form.rb
@@ -50,36 +50,34 @@ def self.filtered_forms(user, aasm_state)
end
items = items.non_templates
- items = items.where(aasm_state: aasm_state) if aasm_state.present? && aasm_state != "all"
+ items = items.where(aasm_state: aasm_state) if aasm_state.present? && aasm_state != 'all'
items
end
def self.kinds
[
- "a11",
- "a11_v2", # launched Fall 2023
- "a11_v2_radio", # launched May 2025
- "a11_yes_no",
- "open_ended",
- "other", # TODO: deprecate in favor of custom,
- "recruiter",
- "yes_no",
- "custom"
+ 'a11',
+ 'a11_v2', # launched Fall 2023
+ 'a11_v2_radio', # launched May 2025
+ 'a11_yes_no',
+ 'open_ended',
+ 'other', # TODO: deprecate in favor of custom,
+ 'recruiter',
+ 'yes_no',
+ 'custom',
]
end
def valid_form_kinds
- if !Form.kinds.include?(kind)
- errors.add(:kind, "kind must be one of the following: #{Form.kinds.sort.join(', ')}")
- end
+ errors.add(:kind, "kind must be one of the following: #{Form.kinds.sort.join(', ')}") unless Form.kinds.include?(kind)
end
def target_for_delivery_method
- errors.add(:element_selector, "can't be blank for an inline form") if (delivery_method == 'custom-button-modal' || delivery_method == 'inline') && (element_selector == '')
+ errors.add(:element_selector, "can't be blank for an inline form") if %w[custom-button-modal inline].include?(delivery_method) && (element_selector == '')
end
def roles
- user_roles.map { |role| { role: role.role, user: role.user }}
+ user_roles.map { |role| { role: role.role, user: role.user } }
end
def form_managers
@@ -87,7 +85,7 @@ def form_managers
end
def response_viewers
- roles.select { |role| role[:role] == 'response_viewer' }.map { |r| r[:user]}
+ roles.select { |role| role[:role] == 'response_viewer' }.map { |r| r[:user] }
end
def ensure_modal_text
@@ -154,18 +152,18 @@ def create_first_form_section
# used to initially set tags (or reset them, if necessary)
def set_submission_tags!
submission_tags = submissions.collect(&:tags).uniq.sort_by { |i| i.name }
- self.update!(submission_tags: submission_tags)
+ update!(submission_tags: submission_tags)
end
# called when a tag is added to a submission
def update_submission_tags!(tag_list)
submission_tags = (self.submission_tags + tag_list).uniq.compact.sort
- self.update!(submission_tags: submission_tags)
+ update!(submission_tags: submission_tags)
end
# lazily called from a view when a tag is used to search, but returns 0 results
def remove_submission_tag!(tag)
- self.update!(submission_tags: submission_tags - [tag])
+ update!(submission_tags: submission_tags - [tag])
end
aasm do
@@ -177,15 +175,15 @@ def remove_submission_tag!(tag)
event :submit do
transitions from: %i[created],
- to: :submitted,
- guard: :organization_has_form_approval_enabled?,
- after: :set_submitted_at
+ to: :submitted,
+ guard: :organization_has_form_approval_enabled?,
+ after: :set_submitted_at
end
event :approve do
transitions from: %i[submitted],
- to: :approved,
- guard: :organization_has_form_approval_enabled?,
- after: :set_approved_at
+ to: :approved,
+ guard: :organization_has_form_approval_enabled?,
+ after: :set_approved_at
end
event :publish do
transitions from: %i[created approved], to: :published
@@ -208,7 +206,7 @@ def all_states
end
def events
- Event.where(object_type: 'Form', object_uuid: self.uuid).order(:created_at)
+ Event.where(object_type: 'Form', object_uuid: uuid).order(:created_at)
end
def duplicate!(new_user:)
@@ -266,7 +264,7 @@ def check_expired
end
def has_rich_text_questions?
- questions.where(question_type: "rich_textarea").exists?
+ questions.where(question_type: 'rich_textarea').exists?
end
def self.archive_expired!
@@ -290,7 +288,7 @@ def self.send_inactive_form_emails_since(days_ago)
def self.find_inactive_forms_since(days_ago)
min_time = Time.now - days_ago.days
max_time = Time.now - (days_ago - 1).days
- Form.non_templates.published.where("last_response_created_at BETWEEN ? AND ?", min_time, max_time)
+ Form.non_templates.published.where('last_response_created_at BETWEEN ? AND ?', min_time, max_time)
end
def deployable_form?
@@ -336,56 +334,112 @@ def to_combined_a11_v2_csv(start_date: nil, end_date: nil)
attributes = fields_for_export
header_attributes = hashed_fields_for_export.values
- a11_v2_header_attributes = [
- :external_id,
- :question_1,
- :positive_effectiveness,
- :positive_ease,
- :positive_efficiency,
- :positive_transparency,
- :positive_humanity,
- :positive_employee,
- :positive_other,
- :negative_effectiveness,
- :negative_ease,
- :negative_efficiency,
- :negative_transparency,
- :negative_humanity,
- :negative_employee,
- :negative_other,
- :question_4
+ a11_v2_header_attributes = %i[
+ external_id
+ question_1
+ positive_effectiveness
+ positive_ease
+ positive_efficiency
+ positive_transparency
+ positive_humanity
+ positive_employee
+ positive_other
+ negative_effectiveness
+ negative_ease
+ negative_efficiency
+ negative_transparency
+ negative_humanity
+ negative_employee
+ negative_other
+ question_4
]
attributes = fields_for_export
- answer_02_options = self.questions.where(answer_field: "answer_02").first.question_options.collect(&:value)
- answer_03_options = self.questions.where(answer_field: "answer_03").first.question_options.collect(&:value)
+ answer_02_options = questions.where(answer_field: 'answer_02').first.question_options.collect(&:value)
+ answer_03_options = questions.where(answer_field: 'answer_03').first.question_options.collect(&:value)
CSV.generate(headers: true) do |csv|
- csv << header_attributes + a11_v2_header_attributes
+ csv << (header_attributes + a11_v2_header_attributes)
reportable_submissions.each do |submission|
- csv << attributes.map { |attr| submission.send(attr) } + [
+ csv << (attributes.map { |attr| submission.send(attr) } + [
submission.id,
submission.answer_01,
- submission.answer_02 && submission.answer_02.split(",").include?("effectiveness") ? 1 :(answer_02_options.include?("effectiveness") ? 0 : 'null'),
- submission.answer_02 && submission.answer_02.split(",").include?("ease") ? 1 : (answer_02_options.include?("ease") ? 0 : 'null'),
- submission.answer_02 && submission.answer_02.split(",").include?("efficiency") ? 1 : (answer_02_options.include?("efficiency") ? 0 : 'null'),
- submission.answer_02 && submission.answer_02.split(",").include?("transparency") ? 1 : (answer_02_options.include?("transparency") ? 0 : 'null'),
- submission.answer_02 && submission.answer_02.split(",").include?("humanity") ? 1 : (answer_02_options.include?("humanity") ? 0 : 'null'),
- submission.answer_02 && submission.answer_02.split(",").include?("employee") ? 1 : (answer_02_options.include?("employee") ? 0 : 'null'),
- submission.answer_02 && submission.answer_02.split(",").include?("other") ? 1 : (answer_02_options.include?("other") ? 0 : 'null'),
-
- submission.answer_03 && submission.answer_03.split(",").include?("effectiveness") ? 1 : (answer_03_options.include?("effectiveness") ? 0 : 'null'),
- submission.answer_03 && submission.answer_03.split(",").include?("ease") ? 1 : (answer_03_options.include?("ease") ? 0 : 'null'),
- submission.answer_03 && submission.answer_03.split(",").include?("efficiency") ? 1 : (answer_03_options.include?("efficiency") ? 0 : 'null'),
- submission.answer_03 && submission.answer_03.split(",").include?("transparency") ? 1 : (answer_03_options.include?("transparency") ? 0 : 'null'),
- submission.answer_03 && submission.answer_03.split(",").include?("humanity") ? 1 : (answer_03_options.include?("humanity") ? 0 : 'null'),
- submission.answer_03 && submission.answer_03.split(",").include?("employee") ? 1 : (answer_03_options.include?("employee") ? 0 : 'null'),
- submission.answer_03 && submission.answer_03.split(",").include?("other") ? 1 : (answer_03_options.include?("other") ? 0 : 'null'),
-
- submission.answer_04
- ]
+ if submission.answer_02 && submission.answer_02.split(',').include?('effectiveness')
+ 1
+ else
+ (answer_02_options.include?('effectiveness') ? 0 : 'null')
+ end,
+ if submission.answer_02 && submission.answer_02.split(',').include?('ease')
+ 1
+ else
+ (answer_02_options.include?('ease') ? 0 : 'null')
+ end,
+ if submission.answer_02 && submission.answer_02.split(',').include?('efficiency')
+ 1
+ else
+ (answer_02_options.include?('efficiency') ? 0 : 'null')
+ end,
+ if submission.answer_02 && submission.answer_02.split(',').include?('transparency')
+ 1
+ else
+ (answer_02_options.include?('transparency') ? 0 : 'null')
+ end,
+ if submission.answer_02 && submission.answer_02.split(',').include?('humanity')
+ 1
+ else
+ (answer_02_options.include?('humanity') ? 0 : 'null')
+ end,
+ if submission.answer_02 && submission.answer_02.split(',').include?('employee')
+ 1
+ else
+ (answer_02_options.include?('employee') ? 0 : 'null')
+ end,
+ if submission.answer_02 && submission.answer_02.split(',').include?('other')
+ 1
+ else
+ (answer_02_options.include?('other') ? 0 : 'null')
+ end,
+
+ if submission.answer_03 && submission.answer_03.split(',').include?('effectiveness')
+ 1
+ else
+ (answer_03_options.include?('effectiveness') ? 0 : 'null')
+ end,
+ if submission.answer_03 && submission.answer_03.split(',').include?('ease')
+ 1
+ else
+ (answer_03_options.include?('ease') ? 0 : 'null')
+ end,
+ if submission.answer_03 && submission.answer_03.split(',').include?('efficiency')
+ 1
+ else
+ (answer_03_options.include?('efficiency') ? 0 : 'null')
+ end,
+ if submission.answer_03 && submission.answer_03.split(',').include?('transparency')
+ 1
+ else
+ (answer_03_options.include?('transparency') ? 0 : 'null')
+ end,
+ if submission.answer_03 && submission.answer_03.split(',').include?('humanity')
+ 1
+ else
+ (answer_03_options.include?('humanity') ? 0 : 'null')
+ end,
+ if submission.answer_03 && submission.answer_03.split(',').include?('employee')
+ 1
+ else
+ (answer_03_options.include?('employee') ? 0 : 'null')
+ end,
+ if submission.answer_03 && submission.answer_03.split(',').include?('other')
+ 1
+ else
+ (answer_03_options.include?('other') ? 0 : 'null')
+ end,
+
+ submission.answer_04,
+ ])
end
end
end
@@ -399,30 +453,30 @@ def to_a11_v2_csv(start_date: nil, end_date: nil)
return nil if reportable_submissions.blank?
header_attributes = hashed_fields_for_export.values
- header_attributes = [
- :external_id,
- :question_1,
- :positive_effectiveness,
- :positive_ease,
- :positive_efficiency,
- :positive_transparency,
- :positive_humanity,
- :positive_employee,
- :positive_other,
- :negative_effectiveness,
- :negative_ease,
- :negative_efficiency,
- :negative_transparency,
- :negative_humanity,
- :negative_employee,
- :negative_other,
- :question_4
+ header_attributes = %i[
+ external_id
+ question_1
+ positive_effectiveness
+ positive_ease
+ positive_efficiency
+ positive_transparency
+ positive_humanity
+ positive_employee
+ positive_other
+ negative_effectiveness
+ negative_ease
+ negative_efficiency
+ negative_transparency
+ negative_humanity
+ negative_employee
+ negative_other
+ question_4
]
attributes = fields_for_export
- answer_02_options = self.questions.where(answer_field: "answer_02").first.question_options.collect(&:value)
- answer_03_options = self.questions.where(answer_field: "answer_03").first.question_options.collect(&:value)
+ answer_02_options = questions.where(answer_field: 'answer_02').first.question_options.collect(&:value)
+ answer_03_options = questions.where(answer_field: 'answer_03').first.question_options.collect(&:value)
CSV.generate(headers: true) do |csv|
csv << header_attributes
@@ -431,28 +485,117 @@ def to_a11_v2_csv(start_date: nil, end_date: nil)
csv << [
submission.id,
submission.answer_01,
- submission.answer_02 && submission.answer_02.split(",").include?("effectiveness") ? 1 :(answer_02_options.include?("effectiveness") ? 0 : 'null'),
- submission.answer_02 && submission.answer_02.split(",").include?("ease") ? 1 : (answer_02_options.include?("ease") ? 0 : 'null'),
- submission.answer_02 && submission.answer_02.split(",").include?("efficiency") ? 1 : (answer_02_options.include?("efficiency") ? 0 : 'null'),
- submission.answer_02 && submission.answer_02.split(",").include?("transparency") ? 1 : (answer_02_options.include?("transparency") ? 0 : 'null'),
- submission.answer_02 && submission.answer_02.split(",").include?("humanity") ? 1 : (answer_02_options.include?("humanity") ? 0 : 'null'),
- submission.answer_02 && submission.answer_02.split(",").include?("employee") ? 1 : (answer_02_options.include?("employee") ? 0 : 'null'),
- submission.answer_02 && submission.answer_02.split(",").include?("other") ? 1 : (answer_02_options.include?("other") ? 0 : 'null'),
-
- submission.answer_03 && submission.answer_03.split(",").include?("effectiveness") ? 1 : (answer_03_options.include?("effectiveness") ? 0 : 'null'),
- submission.answer_03 && submission.answer_03.split(",").include?("ease") ? 1 : (answer_03_options.include?("ease") ? 0 : 'null'),
- submission.answer_03 && submission.answer_03.split(",").include?("efficiency") ? 1 : (answer_03_options.include?("efficiency") ? 0 : 'null'),
- submission.answer_03 && submission.answer_03.split(",").include?("transparency") ? 1 : (answer_03_options.include?("transparency") ? 0 : 'null'),
- submission.answer_03 && submission.answer_03.split(",").include?("humanity") ? 1 : (answer_03_options.include?("humanity") ? 0 : 'null'),
- submission.answer_03 && submission.answer_03.split(",").include?("employee") ? 1 : (answer_03_options.include?("employee") ? 0 : 'null'),
- submission.answer_03 && submission.answer_03.split(",").include?("other") ? 1 : (answer_03_options.include?("other") ? 0 : 'null'),
-
- submission.answer_04
+ if submission.answer_02 && submission.answer_02.split(',').include?('effectiveness')
+ 1
+ else
+ (answer_02_options.include?('effectiveness') ? 0 : 'null')
+ end,
+ if submission.answer_02 && submission.answer_02.split(',').include?('ease')
+ 1
+ else
+ (answer_02_options.include?('ease') ? 0 : 'null')
+ end,
+ if submission.answer_02 && submission.answer_02.split(',').include?('efficiency')
+ 1
+ else
+ (answer_02_options.include?('efficiency') ? 0 : 'null')
+ end,
+ if submission.answer_02 && submission.answer_02.split(',').include?('transparency')
+ 1
+ else
+ (answer_02_options.include?('transparency') ? 0 : 'null')
+ end,
+ if submission.answer_02 && submission.answer_02.split(',').include?('humanity')
+ 1
+ else
+ (answer_02_options.include?('humanity') ? 0 : 'null')
+ end,
+ if submission.answer_02 && submission.answer_02.split(',').include?('employee')
+ 1
+ else
+ (answer_02_options.include?('employee') ? 0 : 'null')
+ end,
+ if submission.answer_02 && submission.answer_02.split(',').include?('other')
+ 1
+ else
+ (answer_02_options.include?('other') ? 0 : 'null')
+ end,
+
+ if submission.answer_03 && submission.answer_03.split(',').include?('effectiveness')
+ 1
+ else
+ (answer_03_options.include?('effectiveness') ? 0 : 'null')
+ end,
+ if submission.answer_03 && submission.answer_03.split(',').include?('ease')
+ 1
+ else
+ (answer_03_options.include?('ease') ? 0 : 'null')
+ end,
+ if submission.answer_03 && submission.answer_03.split(',').include?('efficiency')
+ 1
+ else
+ (answer_03_options.include?('efficiency') ? 0 : 'null')
+ end,
+ if submission.answer_03 && submission.answer_03.split(',').include?('transparency')
+ 1
+ else
+ (answer_03_options.include?('transparency') ? 0 : 'null')
+ end,
+ if submission.answer_03 && submission.answer_03.split(',').include?('humanity')
+ 1
+ else
+ (answer_03_options.include?('humanity') ? 0 : 'null')
+ end,
+ if submission.answer_03 && submission.answer_03.split(',').include?('employee')
+ 1
+ else
+ (answer_03_options.include?('employee') ? 0 : 'null')
+ end,
+ if submission.answer_03 && submission.answer_03.split(',').include?('other')
+ 1
+ else
+ (answer_03_options.include?('other') ? 0 : 'null')
+ end,
+
+ submission.answer_04,
]
end
end
end
+ def to_a11_v2_array(start_date: nil, end_date: nil)
+ non_flagged_submissions = submissions
+ .non_flagged
+ .where(created_at: start_date..end_date)
+ .order('created_at')
+ return nil if non_flagged_submissions.blank?
+
+ answer_02_options = self.questions.where(answer_field: "answer_02").first.question_options.collect(&:value)
+ answer_03_options = self.questions.where(answer_field: "answer_03").first.question_options.collect(&:value)
+
+ non_flagged_submissions.map do |submission|
+ {
+ id: submission.id,
+ answer_01: submission.answer_01,
+ answer_02_effectiveness: submission.answer_02 && submission.answer_02.split(",").include?("effectiveness") ? 1 :(answer_02_options.include?("effectiveness") ? 0 : 'null'),
+ answer_02_ease: submission.answer_02 && submission.answer_02.split(",").include?("ease") ? 1 : (answer_02_options.include?("ease") ? 0 : 'null'),
+ answer_02_efficiency: submission.answer_02 && submission.answer_02.split(",").include?("efficiency") ? 1 : (answer_02_options.include?("efficiency") ? 0 : 'null'),
+ answer_02_transparency: submission.answer_02 && submission.answer_02.split(",").include?("transparency") ? 1 : (answer_02_options.include?("transparency") ? 0 : 'null'),
+ answer_02_humanity: submission.answer_02 && submission.answer_02.split(",").include?("humanity") ? 1 : (answer_02_options.include?("humanity") ? 0 : 'null'),
+ answer_02_employee: submission.answer_02 && submission.answer_02.split(",").include?("employee") ? 1 : (answer_02_options.include?("employee") ? 0 : 'null'),
+ answer_02_other: submission.answer_02 && submission.answer_02.split(",").include?("other") ? 1 : (answer_02_options.include?("other") ? 0 : 'null'),
+ answer_03_effectiveness: submission.answer_03 && submission.answer_03.split(",").include?("effectiveness") ? 1 : (answer_03_options.include?("effectiveness") ? 0 : 'null'),
+ answer_03_ease: submission.answer_03 && submission.answer_03.split(",").include?("ease") ? 1 : (answer_03_options.include?("ease") ? 0 : 'null'),
+ answer_03_efficiency: submission.answer_03 && submission.answer_03.split(",").include?("efficiency") ? 1 : (answer_03_options.include?("efficiency") ? 0 : 'null'),
+ answer_03_transparency: submission.answer_03 && submission.answer_03.split(",").include?("transparency") ? 1 : (answer_03_options.include?("transparency") ? 0 : 'null'),
+ answer_03_humanity: submission.answer_03 && submission.answer_03.split(",").include?("humanity") ? 1 : (answer_03_options.include?("humanity") ? 0 : 'null'),
+ answer_03_employee: submission.answer_03 && submission.answer_03.split(",").include?("employee") ? 1 : (answer_03_options.include?("employee") ? 0 : 'null'),
+ answer_03_other: submission.answer_03 && submission.answer_03.split(",").include?("other") ? 1 : (answer_03_options.include?("other") ? 0 : 'null'),
+ answer_04: submission.answer_04,
+ }
+ end
+ end
+
def user_role?(user:)
role = user_roles.find_by_user_id(user.id)
role.present? ? role.role : nil
@@ -582,12 +725,14 @@ def to_a11_submissions_csv(start_date:, end_date:)
when :answer_06
question = questions.where(answer_field: key).first
next unless question.present?
+
response_volume = values.values.collect(&:to_i).sum
@question_text = question.text
standardized_question_number = 6
when :answer_07
question = questions.where(answer_field: key).first
next unless question.present?
+
response_volume = values.values.collect(&:to_i).sum
@question_text = question.text
standardized_question_number = 7
@@ -621,36 +766,36 @@ def fields_for_export
def hashed_fields_for_export
ordered_hash = ActiveSupport::OrderedHash.new
ordered_hash.merge!({
- id: 'ID',
- uuid: 'UUID',
- })
+ id: 'ID',
+ uuid: 'UUID',
+ })
ordered_questions.map { |q| ordered_hash[q.answer_field] = q.text }
ordered_hash.merge!({
- location_code: 'Location Code',
- user_agent: 'User Agent',
- 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',
- })
+ location_code: 'Location Code',
+ user_agent: 'User Agent',
+ 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',
+ })
if organization.enable_ip_address?
ordered_hash.merge!({
- ip_address: 'IP Address',
- })
+ ip_address: 'IP Address',
+ })
end
ordered_hash.merge!({
- tags: 'Tags',
- })
+ tags: 'Tags',
+ })
ordered_hash
end
@@ -664,7 +809,7 @@ def ordered_questions
end
def rendered_questions
- ordered_questions.select { |q| q.text.include?("email") || q.text.include?("name") }
+ ordered_questions.select { |q| q.text.include?('email') || q.text.include?('name') }
end
def omb_number_with_expiration_date
@@ -701,78 +846,62 @@ def organization_has_form_approval_enabled?
# use this validator to provide soft UI guidance, rather than strong model validation
def ensure_a11_v2_format
# ensure `answer_01` is a big thumbs question
- question_1 = self.ordered_questions.find { |q| q.answer_field == "answer_01" }
- if question_1.question_type != 'big_thumbs_up_down_buttons'
- errors.add(:base, "The question for `answer_01` must be a \"Big Thumbs Up/Down\" component")
- end
+ question_1 = ordered_questions.find { |q| q.answer_field == 'answer_01' }
+ errors.add(:base, 'The question for `answer_01` must be a "Big Thumbs Up/Down" component') if question_1.question_type != 'big_thumbs_up_down_buttons'
# ensure the form has the 4 required questions
- required_elements = ["answer_01", "answer_02", "answer_03", "answer_04"]
- unless contains_elements?(questions.collect(&:answer_field), required_elements)
- errors.add(:base, "The A-11 v2 form must have questions for #{required_elements.to_sentence}")
- end
+ required_elements = %w[answer_01 answer_02 answer_03 answer_04]
+ errors.add(:base, "The A-11 v2 form must have questions for #{required_elements.to_sentence}") unless contains_elements?(questions.collect(&:answer_field), required_elements)
# ensure the positive indicators include ease and effectiveness
- question_2 = self.ordered_questions.find { |q| q.answer_field == "answer_02" }
+ question_2 = ordered_questions.find { |q| q.answer_field == 'answer_02' }
question_options = question_2.question_options
question_option_values = question_options.collect(&:value)
- required_options = ["effectiveness", "ease"]
+ required_options = %w[effectiveness ease]
missing_options = required_options - question_option_values
- if missing_options.any?
- errors.add(:base, "The question options for Question 2 must include: #{missing_options.join(', ')}")
- end
+ errors.add(:base, "The question options for Question 2 must include: #{missing_options.join(', ')}") if missing_options.any?
# ensure the positive indicators include ease and effectiveness
- question_3 = self.ordered_questions.find { |q| q.answer_field == "answer_03" }
+ question_3 = ordered_questions.find { |q| q.answer_field == 'answer_03' }
question_options = question_3.question_options
question_option_values = question_options.collect(&:value)
- required_options = ["effectiveness", "ease"]
+ required_options = %w[effectiveness ease]
missing_options = required_options - question_option_values
- if missing_options.any?
- errors.add(:base, "The question options for Question 3 must include: #{missing_options.join(', ')}")
- end
+ errors.add(:base, "The question options for Question 3 must include: #{missing_options.join(', ')}") if missing_options.any?
end
def ensure_a11_v2_radio_format
- question_1 = self.ordered_questions.find { |q| q.answer_field == "answer_01" }
- if question_1.question_type != 'radio_buttons'
- errors.add(:base, "The question for `answer_01` must be a Radio Buttons component with 5 options, with values 1-5")
- end
+ question_1 = ordered_questions.find { |q| q.answer_field == 'answer_01' }
+ errors.add(:base, 'The question for `answer_01` must be a Radio Buttons component with 5 options, with values 1-5') if question_1.question_type != 'radio_buttons'
# ensure the form has the 4 required questions
- required_elements = ["answer_01", "answer_02", "answer_03", "answer_04"]
- unless contains_elements?(questions.collect(&:answer_field), required_elements)
- errors.add(:base, "The A-11 v2 form must have questions for #{required_elements.to_sentence}")
- end
+ required_elements = %w[answer_01 answer_02 answer_03 answer_04]
+ errors.add(:base, "The A-11 v2 form must have questions for #{required_elements.to_sentence}") unless contains_elements?(questions.collect(&:answer_field), required_elements)
# ensure the positive indicators include ease and effectiveness
- question_2 = self.ordered_questions.find { |q| q.answer_field == "answer_02" }
+ question_2 = ordered_questions.find { |q| q.answer_field == 'answer_02' }
question_options = question_2.question_options
question_option_values = question_options.collect(&:value)
- required_options = ["effectiveness", "ease"]
+ required_options = %w[effectiveness ease]
missing_options = required_options - question_option_values
- if missing_options.any?
- errors.add(:base, "The question options for Question 2 must include: #{missing_options.join(', ')}")
- end
+ errors.add(:base, "The question options for Question 2 must include: #{missing_options.join(', ')}") if missing_options.any?
# ensure the positive indicators include ease and effectiveness
- question_3 = self.ordered_questions.find { |q| q.answer_field == "answer_03" }
+ question_3 = ordered_questions.find { |q| q.answer_field == 'answer_03' }
question_options = question_3.question_options
question_option_values = question_options.collect(&:value)
- required_options = ["effectiveness", "ease"]
+ required_options = %w[effectiveness ease]
missing_options = required_options - question_option_values
- if missing_options.any?
- errors.add(:base, "The question options for Question 3 must include: #{missing_options.join(', ')}")
- end
+ errors.add(:base, "The question options for Question 3 must include: #{missing_options.join(', ')}") if missing_options.any?
end
def warn_about_not_too_many_questions
- if questions.size > 12
- errors.add(:base, "Touchpoints supports a maximum of 20 questions. There are currently #{questions_count} questions. Fewer questions tend to yield higher response rates.")
+ if questions.size >= 30
+ errors.add(:base, "Touchpoints supports a maximum of 30 questions. There are currently #{questions_count} questions. Fewer questions tend to yield higher response rates.")
end
end
@@ -784,7 +913,7 @@ def self.forms_whose_retention_period_has_passed
cutoff_year = FiscalYear.last_fiscal_year - 3
cutoff_date = FiscalYear.last_day_of_fiscal_quarter(cutoff_year, 4)
Form.where("aasm_state = 'archived'")
- .where("archived_at < ?", cutoff_date)
+ .where('archived_at < ?', cutoff_date)
end
private
@@ -795,14 +924,14 @@ def set_uuid
end
def set_submitted_at
- self.update(submitted_at: Time.current)
+ update(submitted_at: Time.current)
end
def set_approved_at
- self.update(approved_at: Time.current)
+ update(approved_at: Time.current)
end
def set_archived_at
- self.update(archived_at: Time.current)
+ update(archived_at: Time.current)
end
end
diff --git a/app/serializers/submission_serializer.rb b/app/serializers/submission_serializer.rb
index d90ef81e5..3d89cb07e 100644
--- a/app/serializers/submission_serializer.rb
+++ b/app/serializers/submission_serializer.rb
@@ -30,6 +30,16 @@ class SubmissionSerializer < ActiveModel::Serializer
:answer_18,
:answer_19,
:answer_20,
+ :answer_21,
+ :answer_22,
+ :answer_23,
+ :answer_24,
+ :answer_25,
+ :answer_26,
+ :answer_27,
+ :answer_28,
+ :answer_29,
+ :answer_30,
:ip_address,
:location_code,
:flagged,
diff --git a/app/views/admin/cx_collection_details/_form.html.erb b/app/views/admin/cx_collection_details/_form.html.erb
index 7cd41dff1..792742210 100644
--- a/app/views/admin/cx_collection_details/_form.html.erb
+++ b/app/views/admin/cx_collection_details/_form.html.erb
@@ -132,6 +132,17 @@
+ <% if @cx_collection_detail.form %>
+ <%= form.hidden_field :form_id, value: @cx_collection_detail.form_id %>
+
+ CxResponses will be created for the form titled "<%= @cx_collection_detail.form.title %>" + for Q<%= @cx_collection_detail.cx_collection.quarter %>FY<%= @cx_collection_detail.cx_collection.fiscal_year %>. +
+@@ -140,6 +151,7 @@
<%= form.submit class: "usa-button" %> diff --git a/app/views/admin/cx_collection_details/upload.html.erb b/app/views/admin/cx_collection_details/upload.html.erb index 30eb620ae..12694fed6 100644 --- a/app/views/admin/cx_collection_details/upload.html.erb +++ b/app/views/admin/cx_collection_details/upload.html.erb @@ -61,7 +61,7 @@ <% end %> -
| User | @@ -70,9 +70,9 @@Timestamp | Uploaded record count | <%- if service_manager_permissions? %> -- | - | + | Job ID | +Process file? | +Delete? | <% end %>- <%= link_to "Uploaded file", s3_presigned_url(upload.key) %> + <%= link_to "Uploaded file", s3_presigned_url(upload.key) if upload.key.present? %> |
<%= upload.size %>
diff --git a/app/views/admin/cx_collections/_form.html.erb b/app/views/admin/cx_collections/_form.html.erb
index 7491e10a9..ab045f5b8 100644
--- a/app/views/admin/cx_collections/_form.html.erb
+++ b/app/views/admin/cx_collections/_form.html.erb
@@ -74,7 +74,7 @@
- After creating this collection, you can add survey results in the following screen. + After creating this collection, you can add survey results on the following screen. |
|---|
+