Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
10 changes: 9 additions & 1 deletion app/forms/idv/api_image_upload_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def update_analytics(client_response)
client_image_metrics: image_metadata,
async: false,
flow_path: params[:flow_path],
).merge(native_camera_ab_test_data),
).merge(native_camera_ab_test_data, acuant_sdk_upgrade_ab_test_data),
)
pii_from_doc = client_response.pii_from_doc || {}
store_encrypted_images_if_required
Expand Down Expand Up @@ -250,6 +250,14 @@ def native_camera_ab_test_data
}
end

def acuant_sdk_upgrade_ab_test_data
return {} unless IdentityConfig.store.idv_acuant_sdk_upgrade_a_b_testing_enabled
{
acuant_sdk_upgrade_ab_test_bucket:
AbTests::ACUANT_SDK.bucket(document_capture_session.uuid),
}
end

def acuant_sdk_capture?
image_metadata.dig(:front, :source) == Idp::Constants::Vendors::ACUANT &&
image_metadata.dig(:back, :source) == Idp::Constants::Vendors::ACUANT
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createContext } from 'react';

interface AcuantSdkUpgradeABTestContextValue {
/**
* Whether or not the A/B testing of using the newer Acuant
* SDK version vs the older version is enabled
*/
acuantSdkUpgradeABTestingEnabled: boolean;
/**
* Whether or not we should use the newer sdk version as opposed
* to the older / current version
*/
useNewerSdk: boolean;
}
export const AcuantSdkUpgradeABTestContext = createContext<AcuantSdkUpgradeABTestContextValue>({
acuantSdkUpgradeABTestingEnabled: false,
useNewerSdk: false,
});

AcuantSdkUpgradeABTestContext.displayName = 'AcuantSdkUpgradeABTestContext';

export default AcuantSdkUpgradeABTestContext;
export const AcuantSdkUpgradeABTestContextProvider = AcuantSdkUpgradeABTestContext.Provider;
31 changes: 27 additions & 4 deletions app/javascript/packages/document-capture/context/acuant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { ReactNode } from 'react';
import useObjectMemo from '@18f/identity-react-hooks/use-object-memo';
import DeviceContext from './device';
import AnalyticsContext from './analytics';
import { AcuantSdkUpgradeABTestContext } from './acuant-sdk-upgrade-a-b-test';

/**
* Global declarations
Expand Down Expand Up @@ -80,11 +81,13 @@ type AcuantWorkersInitialize = (callback: () => void) => void;

interface AcuantContextProviderProps {
/**
* SDK source URL.
* The relative url source for the
* main acuant sdk file
*/
sdkSrc: string;
/**
* Camera JavaScript source URL.
* The relative url source for the
* camera acuant sdk file
*/
cameraSrc: string;
/**
Expand Down Expand Up @@ -193,8 +196,8 @@ const getActualAcuantCamera = (): AcuantCameraInterface => {
};

function AcuantContextProvider({
sdkSrc = '/acuant/11.7.1/AcuantJavascriptWebSdk.min.js',
cameraSrc = '/acuant/11.7.1/AcuantCamera.min.js',
sdkSrc = '/acuant/11.7.0/AcuantJavascriptWebSdk.min.js',
cameraSrc = '/acuant/11.7.0/AcuantCamera.min.js',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are we planning to keep this code pattern around for a while?

If so, and if we plan to hard-code the version sources, I'd suggest getting rid of the prop altogether, since it's misleading to imply that it can be customized if the logic of the component will ignore the value depending on the A/B testing value.

Alternatively, we could adapt the component to receive the two sets of sources.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Are we planning to keep this code pattern around for a while?

My understanding is that we wanted to let this run for a week, maybe two. However I think we should have clarification. If we are going to AB test upgrades in the future (and why not?) then you are right, we should change some if these assumptions. Additionally, we'd probably want to set the old/new version numbers in the identitystore config

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@aduth After toying around with this a bit more, it seems that the sdkSrc and cameraSrc props were introduced specifically for the tests so that an error isn't thrown when these resources don't load during a frontend test. Here is an example.

I've gone ahead and pushed some changes, whereby a source prop is passed in explicitly, that's what gets used no matter what (for now only tests use it this way). Otherwise, we kick it to the ab test context to determine which version/path to specify.

I've also added some constants to make this a little cleaner.

lmk what you think

credentials = null,
endpoint = null,
glareThreshold = DEFAULT_ACCEPTABLE_GLARE_SCORE,
Expand All @@ -203,6 +206,26 @@ function AcuantContextProvider({
}: AcuantContextProviderProps) {
const { isMobile } = useContext(DeviceContext);
const { trackEvent } = useContext(AnalyticsContext);

// Set the appropriate SDK sources based on
// A/B Testing
const { acuantSdkUpgradeABTestingEnabled, useNewerSdk } = useContext(
AcuantSdkUpgradeABTestContext,
);
let sdkVersion = '11.7.0';
if (acuantSdkUpgradeABTestingEnabled && useNewerSdk) {
sdkSrc = '/acuant/11.7.1/AcuantJavascriptWebSdk.min.js';
cameraSrc = '/acuant/11.7.1/AcuantCamera.min.js';
sdkVersion = '11.7.1';
}

if (acuantSdkUpgradeABTestingEnabled) {
trackEvent('IdV: Acuant SDK Upgrade A/B Test', {
Comment thread
aduth marked this conversation as resolved.
Outdated
use_newer_sdk: useNewerSdk,
version: sdkVersion,
});
}

// Only mobile devices should load the Acuant SDK. Consider immediately ready otherwise.
const [isReady, setIsReady] = useState(!isMobile);
const [isAcuantLoaded, setIsAcuantLoaded] = useState(false);
Expand Down
4 changes: 4 additions & 0 deletions app/javascript/packages/document-capture/context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ export {
default as NativeCameraABTestContext,
Provider as NativeCameraABTestContextProvider,
} from './native-camera-a-b-test';
export {
default as AcuantSdkUpgradeABTestContext,
Provider as AcuantSdkUpgradeABTestContextProvider,
} from './native-camera-a-b-test';
12 changes: 12 additions & 0 deletions app/javascript/packs/document-capture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
AnalyticsContextProvider,
FailedCaptureAttemptsContextProvider,
NativeCameraABTestContextProvider,
AcuantSdkUpgradeABTestContextProvider,
MarketingSiteContextProvider,
} from '@18f/identity-document-capture';
import { isCameraCapableMobile } from '@18f/identity-device';
Expand All @@ -30,6 +31,8 @@ interface AppRootData {
maxAttemptsBeforeNativeCamera: string;
nativeCameraABTestingEnabled: string;
nativeCameraOnly: string;
acuantSdkUpgradeABTestingEnabled: string;
useNewerSdk: string;
flowPath: FlowPath;
cancelUrl: string;
idvInPersonUrl?: string;
Expand Down Expand Up @@ -110,6 +113,8 @@ const trackEvent: typeof baseTrackEvent = (event, payload) => {
maxSubmissionAttemptsBeforeNativeCamera,
nativeCameraABTestingEnabled,
nativeCameraOnly,
acuantSdkUpgradeABTestingEnabled,
useNewerSdk,
appName,
flowPath,
cancelUrl: cancelURL,
Expand Down Expand Up @@ -171,6 +176,13 @@ const trackEvent: typeof baseTrackEvent = (event, payload) => {
nativeCameraOnly: nativeCameraOnly === 'true',
},
],
[
AcuantSdkUpgradeABTestContextProvider,
{
acuantSdkUpgradeABTestingEnabled: acuantSdkUpgradeABTestingEnabled === 'true',
useNewerSdk: useNewerSdk === 'true',
},
],
[DocumentCapture, { isAsyncForm, onStepChange: keepAlive }],
);

Expand Down
17 changes: 17 additions & 0 deletions app/services/analytics_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,23 @@ def idv_native_camera_forced(
)
end

# @param [Boolean] use_newer_sdk Whether not to load the newer Acuant SDK version
# @param [String] version The version of the Acuant SDK that was loaded
# An A/B test for loading the old vs new version of the Acuant SDK.
# If this analytics call is not made at all, then the test isn't enabled.
def idv_acuant_sdk_upgrade_a_b_test(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

based on the wording of the JIRA, I think that it might be good to add AB test attributes to the existing idv_doc_auth_submitted_image_upload_vendor or something, so that we could easily correlate A/B test bucket with results

having a separate event like this to proactively log which bucket is good too in case users don't make it as far as submissions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah we need to know at SDK load time (ie before any submission or even attempts). Should we do both, keep the new event and amend the upload vendor event?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

yeah seems like it would be useful to have the info at both events

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The way that flow_path is tracked across events during document capture could serve as good prior art to follow:

const trackEvent: typeof baseTrackEvent = (event, payload) => {
const { flowPath } = appRoot.dataset;
return baseTrackEvent(event, { ...payload, flow_path: flowPath });
};

use_newer_sdk:,
version:,
**extra
)
track_event(
'IdV: Acuant SDK Upgrade A/B Test',
use_newer_sdk: use_newer_sdk,
version: version,
**extra,
)
end

# @param [String] step the step that the user was on when they clicked cancel
# @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components
# The user confirmed their choice to cancel going through IDV
Expand Down
11 changes: 10 additions & 1 deletion app/services/idv/steps/document_capture_step.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def extra_view_variables
image_type: 'back',
transaction_id: flow_session[:document_capture_session_uuid],
),
}.merge(native_camera_ab_testing_variables)
}.merge(native_camera_ab_testing_variables, acuant_sdk_upgrade_a_b_testing_variables)
end

private
Expand All @@ -44,6 +44,15 @@ def native_camera_ab_testing_variables
}
end

def acuant_sdk_upgrade_a_b_testing_variables
bucket = AbTests::ACUANT_SDK.bucket(flow_session[:document_capture_session_uuid])
{
acuant_sdk_upgrade_a_b_testing_enabled:
IdentityConfig.store.idv_acuant_sdk_upgrade_a_b_testing_enabled,
use_newer_sdk: (bucket == :use_newer_sdk),
}
end

def handle_stored_result
if stored_result&.success?
save_proofing_components
Expand Down
2 changes: 2 additions & 0 deletions app/views/idv/capture_doc/document_capture.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@
back_image_upload_url: back_image_upload_url,
native_camera_a_b_testing_enabled: native_camera_a_b_testing_enabled,
native_camera_only: native_camera_only,
acuant_sdk_upgrade_a_b_testing_enabled: acuant_sdk_upgrade_a_b_testing_enabled,
use_newer_sdk: use_newer_sdk,
) %>
2 changes: 2 additions & 0 deletions app/views/idv/doc_auth/document_capture.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@
back_image_upload_url: back_image_upload_url,
native_camera_a_b_testing_enabled: native_camera_a_b_testing_enabled,
native_camera_only: native_camera_only,
acuant_sdk_upgrade_a_b_testing_enabled: acuant_sdk_upgrade_a_b_testing_enabled,
use_newer_sdk: use_newer_sdk,
) %>
2 changes: 2 additions & 0 deletions app/views/idv/shared/_document_capture.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
max_submission_attempts_before_native_camera: IdentityConfig.store.doc_auth_max_submission_attempts_before_native_camera,
native_camera_a_b_testing_enabled: native_camera_a_b_testing_enabled,
native_camera_only: native_camera_only,
acuant_sdk_upgrade_a_b_testing_enabled: acuant_sdk_upgrade_a_b_testing_enabled,
use_newer_sdk: use_newer_sdk,
sp_name: sp_name,
flow_path: flow_path,
cancel_url: idv_cancel_path,
Expand Down
2 changes: 2 additions & 0 deletions config/application.yml.default
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ idv_max_attempts: 5
idv_min_age_years: 13
idv_native_camera_a_b_testing_enabled: false
idv_native_camera_a_b_testing_percent: 10
idv_acuant_sdk_upgrade_a_b_testing_enabled: true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we really want this enabled by default? I would expect a test to be disabled by default and explicitly enabled per environment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll switch back and you can walk me through the typical process

idv_acuant_sdk_upgrade_a_b_testing_percent: 50
idv_send_link_attempt_window_in_minutes: 10
idv_send_link_max_attempts: 5
ie11_support_end_date: '2022-12-31'
Expand Down
7 changes: 7 additions & 0 deletions config/initializers/ab_tests.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,11 @@ module AbTests
nil,
}.compact,
)

ACUANT_SDK = AbTestBucket.new(
experiment_name: 'Acuant SDK Upgrade',
buckets: {
use_newer_sdk: IdentityConfig.store.idv_acuant_sdk_upgrade_a_b_testing_percent,
},
)
end
2 changes: 2 additions & 0 deletions lib/identity_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ def self.build_store(config_map)
config.add(:idv_min_age_years, type: :integer)
config.add(:idv_native_camera_a_b_testing_enabled, type: :boolean)
config.add(:idv_native_camera_a_b_testing_percent, type: :integer)
config.add(:idv_acuant_sdk_upgrade_a_b_testing_enabled, type: :boolean)
config.add(:idv_acuant_sdk_upgrade_a_b_testing_percent, type: :integer)
config.add(:idv_send_link_attempt_window_in_minutes, type: :integer)
config.add(:idv_send_link_max_attempts, type: :integer)
config.add(:idv_sp_required, type: :boolean)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -958,16 +958,16 @@ describe('document-capture/components/acuant-capture', () => {
const upload = getByText('Upload');
fireEvent.click(upload);

expect(trackEvent).to.have.been.calledThrice();
expect(trackEvent.getCall(0)).to.have.been.calledWith('IdV: test image clicked', {
expect(trackEvent.callCount).to.be.at.least(3);
expect(trackEvent).to.have.been.calledWith('IdV: test image clicked', {
source: 'placeholder',
isDrop: false,
});
expect(trackEvent.getCall(1)).to.have.been.calledWith('IdV: test image clicked', {
expect(trackEvent).to.have.been.calledWith('IdV: test image clicked', {
source: 'button',
isDrop: false,
});
expect(trackEvent.getCall(2)).to.have.been.calledWith('IdV: test image clicked', {
expect(trackEvent).to.have.been.calledWith('IdV: test image clicked', {
source: 'upload',
isDrop: false,
});
Expand All @@ -986,7 +986,7 @@ describe('document-capture/components/acuant-capture', () => {
const input = getByLabelText('Image');
fireEvent.drop(input);

expect(trackEvent.getCall(0)).to.have.been.calledWith('IdV: test image clicked', {
expect(trackEvent).to.have.been.calledWith('IdV: test image clicked', {
source: 'placeholder',
isDrop: true,
});
Expand Down
Loading