Skip to content

Commit

Permalink
LG-15217 Add submit_attempts property to doc auth troubleshooting opt…
Browse files Browse the repository at this point in the history
…ion actions (#11682)

* LG-15217 Add submit_attempts property to idv_verify_in_person_troubleshooting_option_clicked and idv_capture_troubleshooting_dismissed

changelog: Internal, In-person proofing, add submit_attempts property to try again and IPP fallback doc auth troubleshooting events
  • Loading branch information
jennyverdeyen authored Jan 2, 2025
1 parent 0d65152 commit 2f476bb
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ function DocumentCapture({ onStepChange = () => {} }: DocumentCaptureProps) {
submissionError instanceof UploadFormEntriesError
? withProps({
remainingSubmitAttempts: submissionError.remainingSubmitAttempts,
submitAttempts: submissionError.submitAttempts,
isResultCodeInvalid: submissionError.isResultCodeInvalid,
isFailedResult: submissionError.isFailedResult,
isFailedDocType: submissionError.isFailedDocType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { Button } from '@18f/identity-components';
import { useInstanceId } from '@18f/identity-react-hooks';
import { t } from '@18f/identity-i18n';
import AnalyticsContext from '../context/analytics';
import UploadContext from '../context/upload';

function InPersonCallToAction() {
const instanceId = useInstanceId();
const { trackEvent } = useContext(AnalyticsContext);
const { submitAttempts } = useContext(UploadContext);

return (
<section
Expand All @@ -25,7 +27,9 @@ function InPersonCallToAction() {
isWide
className="margin-top-3 margin-bottom-1"
onClick={() => {
trackEvent('IdV: verify in person troubleshooting option clicked');
trackEvent('IdV: verify in person troubleshooting option clicked', {
submit_attempts: submitAttempts,
});
}}
>
{t('in_person_proofing.body.cta.button')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface ReviewIssuesStepValue {

interface ReviewIssuesStepProps extends FormStepComponentProps<ReviewIssuesStepValue> {
remainingSubmitAttempts?: number;
submitAttempts?: number;
isResultCodeInvalid?: boolean;
isFailedResult?: boolean;
isFailedSelfie?: boolean;
Expand All @@ -57,6 +58,7 @@ function ReviewIssuesStep({
registerField = () => undefined,
toPreviousStep = () => undefined,
remainingSubmitAttempts = Infinity,
submitAttempts,
isResultCodeInvalid = false,
isFailedResult = false,
isFailedDocType = false,
Expand Down Expand Up @@ -106,6 +108,7 @@ function ReviewIssuesStep({
function onWarningPageDismissed() {
trackEvent('IdV: Capture troubleshooting dismissed', {
liveness_checking_required: isSelfieCaptureEnabled,
submit_attempts: submitAttempts,
});

setHasDismissed(true);
Expand Down
25 changes: 22 additions & 3 deletions app/javascript/packages/document-capture/context/upload.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createContext } from 'react';
import { createContext, useState } from 'react';
import { useObjectMemo } from '@18f/identity-react-hooks';
import type { ReactNode } from 'react';
import defaultUpload from '../services/upload';
import defaultUpload, { UploadFormEntriesError } from '../services/upload';
import type { PII } from '../services/upload';

const UploadContext = createContext({
Expand All @@ -11,6 +11,7 @@ const UploadContext = createContext({
isMockClient: false,
flowPath: 'standard' as FlowPath,
formData: {} as Record<string, any>,
submitAttempts: 0,
});

UploadContext.displayName = 'UploadContext';
Expand Down Expand Up @@ -80,6 +81,11 @@ export interface UploadErrorResponse {
*/
remaining_submit_attempts?: number;

/**
* Number of submitted doc capture attempts for user
*/
submit_attempts?: number;

/**
* Boolean to decide if capture hints should be shown with error.
*/
Expand Down Expand Up @@ -189,7 +195,19 @@ function UploadContextProvider({
flowPath,
children,
}: UploadContextProviderProps) {
const uploadWithFormData = (payload) => upload({ ...payload, ...formData }, { endpoint });
const [submitAttempts, setSubmitAttempts] = useState(0);

const uploadWithFormData = async (payload) => {
try {
const result = await upload({ ...payload, ...formData }, { endpoint });
return result;
} catch (error) {
if (error instanceof UploadFormEntriesError && error.submitAttempts !== undefined) {
setSubmitAttempts(error.submitAttempts);
}
throw error;
}
};

const getStatus = () =>
statusEndpoint
Expand All @@ -203,6 +221,7 @@ function UploadContextProvider({
isMockClient,
flowPath,
formData,
submitAttempts,
});

return <UploadContext.Provider value={value}>{children}</UploadContext.Provider>;
Expand Down
6 changes: 6 additions & 0 deletions app/javascript/packages/document-capture/services/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export class UploadFormEntriesError extends FormError {

remainingSubmitAttempts = Infinity;

submitAttempts = 0;

isResultCodeInvalid = false;

isFailedResult = false;
Expand Down Expand Up @@ -120,6 +122,10 @@ const upload: UploadImplementation = async function (payload, { method = 'POST',
error.remainingSubmitAttempts = result.remaining_submit_attempts;
}

if (result.submit_attempts) {
error.submitAttempts = result.submit_attempts;
}

if (result.ocr_pii) {
error.pii = result.ocr_pii;
}
Expand Down
5 changes: 5 additions & 0 deletions app/presenters/image_upload_response_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ def remaining_submit_attempts
@form_response.to_h[:remaining_submit_attempts]
end

def submit_attempts
@form_response.to_h[:submit_attempts]
end

def status
if success?
:ok
Expand All @@ -41,6 +45,7 @@ def as_json(*)
json = { success: false,
errors: errors,
remaining_submit_attempts: remaining_submit_attempts,
submit_attempts: submit_attempts,
doc_type_supported: doc_type_supported? }
if remaining_submit_attempts&.zero?
if @form_response.extra[:flow_path] == 'standard'
Expand Down
6 changes: 6 additions & 0 deletions app/services/analytics_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1198,12 +1198,14 @@ def idv_cancellation_visited(
# @param ["hybrid","standard"] flow_path Document capture user flow
# @param [String] use_alternate_sdk
# @param [Boolean] liveness_checking_required
# @param [Integer] submit_attempts Times that user has tried submitting document capture
def idv_capture_troubleshooting_dismissed(
acuant_sdk_upgrade_a_b_testing_enabled:,
acuant_version:,
flow_path:,
use_alternate_sdk:,
liveness_checking_required:,
submit_attempts:,
**extra
)
track_event(
Expand All @@ -1213,6 +1215,7 @@ def idv_capture_troubleshooting_dismissed(
flow_path: flow_path,
use_alternate_sdk: use_alternate_sdk,
liveness_checking_required: liveness_checking_required,
submit_attempts: submit_attempts,
**extra,
)
end
Expand Down Expand Up @@ -5309,16 +5312,19 @@ def idv_verify_by_mail_enter_code_visited(

# @param ["hybrid","standard"] flow_path Document capture user flow
# @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing
# @param [Integer] submit_attempts Times that user has tried submitting document capture
# The user clicked the troubleshooting option to start in-person proofing
def idv_verify_in_person_troubleshooting_option_clicked(
flow_path:,
opted_in_to_in_person_proofing:,
submit_attempts:,
**extra
)
track_event(
'IdV: verify in person troubleshooting option clicked',
flow_path: flow_path,
opted_in_to_in_person_proofing: opted_in_to_in_person_proofing,
submit_attempts: submit_attempts,
**extra,
)
end
Expand Down
2 changes: 2 additions & 0 deletions spec/controllers/idv/image_uploads_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
ocr_pii: nil,
doc_type_supported: true,
failed_image_fingerprints: { front: [], back: [], selfie: [] },
submit_attempts: 2,
},
)
end
Expand All @@ -182,6 +183,7 @@
ocr_pii: nil,
doc_type_supported: true,
failed_image_fingerprints: { front: [], back: [], selfie: [] },
submit_attempts: IdentityConfig.store.doc_auth_max_attempts,
}
end

Expand Down
2 changes: 1 addition & 1 deletion spec/features/idv/analytics_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@
},
'IdV: doc auth image upload vendor submitted' => hash_including(success: true, flow_path: 'standard', attention_with_barcode: true, doc_auth_result: 'Attention', liveness_checking_required: boolean),
'IdV: verify in person troubleshooting option clicked' => {
flow_path: 'standard', opted_in_to_in_person_proofing: false
flow_path: 'standard', opted_in_to_in_person_proofing: false, submit_attempts: 1
},
'IdV: in person proofing location visited' => {
flow_path: 'standard', opted_in_to_in_person_proofing: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ describe('document-capture/context/upload', () => {
'getStatus',
'flowPath',
'formData',
'submitAttempts',
]);

expect(result.current.upload).to.equal(defaultUpload);
expect(result.current.getStatus).to.be.instanceOf(Function);
expect(result.current.statusPollInterval).to.be.undefined();
expect(result.current.isMockClient).to.be.false();
expect(await result.current.getStatus()).to.deep.equal({});
expect(result.current.submitAttempts).to.equal(0);
});

it('can be overridden with custom upload behavior', async () => {
Expand Down
26 changes: 18 additions & 8 deletions spec/presenters/image_upload_response_presenter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
include Rails.application.routes.url_helpers

let(:extra_attributes) do
{ remaining_submit_attempts: 3, flow_path: 'standard' }
{ remaining_submit_attempts: 3, flow_path: 'standard', submit_attempts: 2 }
end

let(:form_response) do
Expand Down Expand Up @@ -109,7 +109,8 @@
let(:extra_attributes) do
{ remaining_submit_attempts: 0,
flow_path: 'standard',
failed_image_fingerprints: { back: [], front: ['12345'], selfie: [] } }
failed_image_fingerprints: { back: [], front: ['12345'], selfie: [] },
submit_attempts: 5 }
end
let(:form_response) do
FormResponse.new(
Expand All @@ -132,6 +133,7 @@
ocr_pii: nil,
doc_type_supported: true,
failed_image_fingerprints: { back: [], front: ['12345'], selfie: [] },
submit_attempts: 5,
}

expect(presenter.as_json).to eq expected
Expand All @@ -141,7 +143,8 @@
let(:extra_attributes) do
{ remaining_submit_attempts: 0,
flow_path: 'hybrid',
failed_image_fingerprints: { back: [], front: ['12345'], selfie: [] } }
failed_image_fingerprints: { back: [], front: ['12345'], selfie: [] },
submit_attempts: 5 }
end

it 'returns hash of properties redirecting to capture_complete' do
Expand All @@ -155,6 +158,7 @@
ocr_pii: nil,
doc_type_supported: true,
failed_image_fingerprints: { back: [], front: ['12345'], selfie: [] },
submit_attempts: 5,
}

expect(presenter.as_json).to eq expected
Expand Down Expand Up @@ -185,6 +189,7 @@
ocr_pii: nil,
doc_type_supported: true,
failed_image_fingerprints: { back: [], front: [], selfie: [] },
submit_attempts: 2,
}

expect(presenter.as_json).to eq expected
Expand All @@ -198,7 +203,7 @@
front: t('doc_auth.errors.not_a_file'),
hints: true,
},
extra: { doc_auth_result: 'Failed', remaining_submit_attempts: 3 },
extra: { doc_auth_result: 'Failed', remaining_submit_attempts: 3, submit_attempts: 2 },
)
end

Expand All @@ -213,6 +218,7 @@
ocr_pii: nil,
doc_type_supported: true,
failed_image_fingerprints: { front: [], back: [], selfie: [] },
submit_attempts: 2,
}

expect(presenter.as_json).to eq expected
Expand All @@ -221,7 +227,7 @@

context 'no remaining attempts' do
let(:extra_attributes) do
{ remaining_submit_attempts: 0, flow_path: 'standard' }
{ remaining_submit_attempts: 0, flow_path: 'standard', submit_attempts: 5 }
end
let(:form_response) do
FormResponse.new(
Expand All @@ -236,7 +242,7 @@

context 'hybrid flow' do
let(:extra_attributes) do
{ remaining_submit_attempts: 0, flow_path: 'hybrid' }
{ remaining_submit_attempts: 0, flow_path: 'hybrid', submit_attempts: 5 }
end

it 'returns hash of properties' do
Expand All @@ -251,6 +257,7 @@
ocr_pii: nil,
doc_type_supported: true,
failed_image_fingerprints: { front: [], back: [], selfie: [] },
submit_attempts: 5,
}

expect(presenter.as_json).to eq expected
Expand All @@ -269,6 +276,7 @@
ocr_pii: nil,
doc_type_supported: true,
failed_image_fingerprints: { back: [], front: [], selfie: [] },
submit_attempts: 5,
}

expect(presenter.as_json).to eq expected
Expand Down Expand Up @@ -325,7 +333,7 @@
let(:form_response) do
response = DocAuth::Response.new(
success: true,
extra: { remaining_submit_attempts: 3 },
extra: { remaining_submit_attempts: 3, submit_attempts: 2 },
pii_from_doc: Idp::Constants::MOCK_IDV_APPLICANT,
)
allow(response).to receive(:attention_with_barcode?).and_return(true)
Expand All @@ -343,6 +351,7 @@
result_code_invalid: false,
doc_type_supported: true,
failed_image_fingerprints: { back: [], front: [], selfie: [] },
submit_attempts: 2,
}

expect(presenter.as_json).to eq expected
Expand All @@ -352,7 +361,7 @@
let(:form_response) do
response = DocAuth::Response.new(
success: true,
extra: { remaining_submit_attempts: 3 },
extra: { remaining_submit_attempts: 3, submit_attempts: 2 },
pii_from_doc: Idp::Constants::MOCK_IDV_APPLICANT,
)
allow(response).to receive(:attention_with_barcode?).and_return(true)
Expand All @@ -370,6 +379,7 @@
ocr_pii: Idp::Constants::MOCK_IDV_APPLICANT.slice(:first_name, :last_name, :dob),
doc_type_supported: true,
failed_image_fingerprints: { back: [], front: [], selfie: [] },
submit_attempts: 2,
}

expect(presenter.as_json).to eq expected
Expand Down

0 comments on commit 2f476bb

Please sign in to comment.