diff --git a/app/jobs/reports/verification_errors_report.rb b/app/jobs/reports/verification_failures_report.rb similarity index 60% rename from app/jobs/reports/verification_errors_report.rb rename to app/jobs/reports/verification_failures_report.rb index f861517643e..1673ed6a297 100644 --- a/app/jobs/reports/verification_errors_report.rb +++ b/app/jobs/reports/verification_failures_report.rb @@ -1,9 +1,10 @@ require 'identity/hostdata' require 'csv' +require 'fugit' module Reports - class VerificationErrorsReport < BaseReport - REPORT_NAME = 'verification-errors-report'.freeze + class VerificationFailuresReport < BaseReport + REPORT_NAME = 'verification-failures-report'.freeze include GoodJob::ActiveJobExtensions::Concurrency @@ -31,7 +32,7 @@ def verification_errors_data_for_issuers(date, report_name, issuers) csv << %w[uuid welcome_view_at error_code] issuers.each do |issuer| transaction_with_timeout do - rows = ::VerificationErrorsReport.call( + rows = ::VerificationFailuresReport.call( issuer, (date.beginning_of_day - 1).beginning_of_day, date.beginning_of_day, @@ -42,7 +43,7 @@ def verification_errors_data_for_issuers(date, report_name, issuers) end end data = csv.string - save_report("#{REPORT_NAME}.#{report_name}", data, extension: 'csv') + save_report("#{REPORT_NAME}/#{report_name}", data, extension: 'csv') data end @@ -50,18 +51,28 @@ def ial2_error_code(row) welcome_at = row['welcome_view_at'] verify_at = row['verify_submit_at'] phone_submit_at = row['verify_phone_submit_at'] - doc_at = row['document_capture_submit_at'] encrypt_at = row['encrypt_view_at'] ssn_at = row['ssn_view_at'] phone_view_at = row['verify_phone_view_at'] return 'PHONE_FAIL' if submit_failed?(welcome_at, phone_submit_at, encrypt_at) return 'VERIFY_FAIL' if submit_failed?(welcome_at, verify_at, phone_view_at) - return 'DOCUMENT_FAIL' if submit_failed?(welcome_at, doc_at, ssn_at) + if submit_failed?(welcome_at, row['document_capture_submit_at'], ssn_at) || + submit_failed?(welcome_at, row['back_image_submit_at'], ssn_at) || + submit_failed?(welcome_at, row['capture_mobile_back_image_submit_at'], ssn_at) || + submit_failed?(welcome_at, row['mobile_back_image_submit_at'], ssn_at) + return 'DOCUMENT_FAIL' + end 'ABANDON' end def submit_failed?(welcome_at, submit_at, next_step_at) - submit_at && submit_at >= welcome_at && (!next_step_at || next_step_at < submit_at) + good_job_cron = Rails.application.config.good_job.cron + cron_entry = good_job_cron.values.find { |c| c[:class] == self.class.name }&.[](:cron) + interval = cron_entry ? (Fugit.parse(cron_entry).rough_frequency - 3600).seconds : 23.hours + return unless submit_at # need a submit + return unless (submit_at + interval) >= welcome_at # need to be in range + return true unless next_step_at # submit must have failed if we did not get to next step + (submit_at + interval) > next_step_at end end end diff --git a/app/services/verification_errors_report.rb b/app/services/verification_failures_report.rb similarity index 88% rename from app/services/verification_errors_report.rb rename to app/services/verification_failures_report.rb index b1fa87ddfe3..d8d40b82c06 100644 --- a/app/services/verification_errors_report.rb +++ b/app/services/verification_failures_report.rb @@ -1,9 +1,12 @@ -class VerificationErrorsReport +class VerificationFailuresReport def self.call(service_provider, start_time, end_time) report_sql = <<~SQL SELECT agency_identities.uuid, document_capture_submit_at, + back_image_submit_at, + capture_mobile_back_image_submit_at, + mobile_back_image_submit_at, encrypt_view_at, enter_info_view_at, ssn_view_at, diff --git a/config/initializers/job_configurations.rb b/config/initializers/job_configurations.rb index 218f6cd2acf..21fe8f3dac3 100644 --- a/config/initializers/job_configurations.rb +++ b/config/initializers/job_configurations.rb @@ -167,7 +167,7 @@ }, # Upload list of verification errors to S3 verification_errors_report: { - class: 'Reports::VerificationErrorsReport', + class: 'Reports::VerificationFailuresReport', cron: cron_24h, args: -> { [Time.zone.today] }, }, diff --git a/db/primary_migrate/20220517103312_add_back_image_submit_at_to_doc_auth_logs.rb b/db/primary_migrate/20220517103312_add_back_image_submit_at_to_doc_auth_logs.rb new file mode 100644 index 00000000000..2cffe315a2c --- /dev/null +++ b/db/primary_migrate/20220517103312_add_back_image_submit_at_to_doc_auth_logs.rb @@ -0,0 +1,7 @@ +class AddBackImageSubmitAtToDocAuthLogs < ActiveRecord::Migration[6.1] + def change + add_column :doc_auth_logs, :back_image_submit_at, :datetime + add_column :doc_auth_logs, :capture_mobile_back_image_submit_at, :datetime + add_column :doc_auth_logs, :mobile_back_image_submit_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index ac3ccd694cd..92047b882ad 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_04_22_193820) do +ActiveRecord::Schema.define(version: 2022_05_17_103312) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" @@ -174,7 +174,9 @@ t.integer "verify_phone_submit_count", default: 0 t.datetime "verify_phone_submit_at" t.datetime "document_capture_submit_at" - t.index ["issuer"], name: "index_doc_auth_logs_on_issuer" + t.datetime "back_image_submit_at" + t.datetime "capture_mobile_back_image_submit_at" + t.datetime "mobile_back_image_submit_at" t.index ["user_id"], name: "index_doc_auth_logs_on_user_id", unique: true t.index ["verified_view_at"], name: "index_doc_auth_logs_on_verified_view_at" end diff --git a/spec/features/reports/doc_auth_funnel_report_spec.rb b/spec/features/reports/doc_auth_funnel_report_spec.rb index 785ef48042d..79846f7491a 100644 --- a/spec/features/reports/doc_auth_funnel_report_spec.rb +++ b/spec/features/reports/doc_auth_funnel_report_spec.rb @@ -94,6 +94,9 @@ 'verify_phone_submit_percent' => 0.0, 'document_capture_submit_percent' => 100.0, 'verify_submit_percent' => 100.0, + 'back_image_submit_percent' => 0.0, + 'capture_mobile_back_image_submit_percent' => 0.0, + 'mobile_back_image_submit_percent' => 0.0, } end diff --git a/spec/jobs/reports/verification_errors_report_spec.rb b/spec/jobs/reports/verification_failures_report_spec.rb similarity index 62% rename from spec/jobs/reports/verification_errors_report_spec.rb rename to spec/jobs/reports/verification_failures_report_spec.rb index f8966472235..bb291236642 100644 --- a/spec/jobs/reports/verification_errors_report_spec.rb +++ b/spec/jobs/reports/verification_failures_report_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe Reports::VerificationErrorsReport do +describe Reports::VerificationFailuresReport do let(:issuer) { 'urn:gov:gsa:openidconnect:sp:sinatra' } let(:email) { 'foo@bar.com' } let(:name) { 'An SP' } @@ -64,6 +64,54 @@ expect(csv[1]).to eq([uuid, now.to_time.utc.iso8601, 'DOCUMENT_FAIL']) end + it 'sends out a document error if the user submits desktop back image and fails' do + DocAuthLog.create( + user_id: user.id, + welcome_view_at: now, + back_image_submit_at: now + 1.second, + issuer: issuer, + ) + + reports = run_reports + expect(reports.length).to eq(1) + csv = CSV.parse(reports[0]) + expect(csv.length).to eq(2) + expect(csv.first).to eq(['uuid', 'welcome_view_at', 'error_code']) + expect(csv[1]).to eq([uuid, now.to_time.utc.iso8601, 'DOCUMENT_FAIL']) + end + + it 'sends out a document error if the user submits hybrid back image and fails' do + DocAuthLog.create( + user_id: user.id, + welcome_view_at: now, + capture_mobile_back_image_submit_at: now + 1.second, + issuer: issuer, + ) + + reports = run_reports + expect(reports.length).to eq(1) + csv = CSV.parse(reports[0]) + expect(csv.length).to eq(2) + expect(csv.first).to eq(['uuid', 'welcome_view_at', 'error_code']) + expect(csv[1]).to eq([uuid, now.to_time.utc.iso8601, 'DOCUMENT_FAIL']) + end + + it 'sends out a document error if the user submits mobile back image and fails' do + DocAuthLog.create( + user_id: user.id, + welcome_view_at: now, + mobile_back_image_submit_at: now + 1.second, + issuer: issuer, + ) + + reports = run_reports + expect(reports.length).to eq(1) + csv = CSV.parse(reports[0]) + expect(csv.length).to eq(2) + expect(csv.first).to eq(['uuid', 'welcome_view_at', 'error_code']) + expect(csv[1]).to eq([uuid, now.to_time.utc.iso8601, 'DOCUMENT_FAIL']) + end + it 'sends out a verify error if the user submits PII but does not progress forward' do DocAuthLog.create( user_id: user.id, @@ -115,6 +163,38 @@ expect(csv[2]).to eq([uuid2, now.to_time.utc.iso8601, 'ABANDON']) end + it 'allows submit to be recent and not just after welcome' do + DocAuthLog.create( + user_id: user.id, + welcome_view_at: now, + mobile_back_image_submit_at: now - 12.hours, + issuer: issuer, + ) + + reports = run_reports + expect(reports.length).to eq(1) + csv = CSV.parse(reports[0]) + expect(csv.length).to eq(2) + expect(csv.first).to eq(['uuid', 'welcome_view_at', 'error_code']) + expect(csv[1]).to eq([uuid, now.to_time.utc.iso8601, 'DOCUMENT_FAIL']) + end + + it 'does not consider old submits as fails' do + DocAuthLog.create( + user_id: user.id, + welcome_view_at: now, + document_capture_submit_at: now - 24.hours, + issuer: issuer, + ) + + reports = run_reports + expect(reports.length).to eq(1) + csv = CSV.parse(reports[0]) + expect(csv.length).to eq(2) + expect(csv.first).to eq(['uuid', 'welcome_view_at', 'error_code']) + expect(csv[1]).to eq([uuid, now.to_time.utc.iso8601, 'ABANDON']) + end + describe '#good_job_concurrency_key' do let(:date) { Time.zone.today } @@ -133,6 +213,6 @@ def run_reports [{ 'name' => name, 'issuers' => [issuer], 'emails' => [email] }], ) - Reports::VerificationErrorsReport.new.perform(Time.zone.today) + Reports::VerificationFailuresReport.new.perform(Time.zone.today) end end