Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LG-15217 Add submit_attempts property to doc auth troubleshooting option actions #11682

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 submit attempts for user.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Number of doc auth submit 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
Copy link
Contributor

@gina-yamada gina-yamada Dec 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe try... number of doc auth submit_attempts for user

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 @@ -5268,16 +5271,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
# The user clicked the troubleshooting option to start in-person proofing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you update the description above, maybe do so here too

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