Skip to content

Commit

Permalink
[CRO] NikitK/CRO-381/Questionary adding by ab testing approach for ne…
Browse files Browse the repository at this point in the history
…w users right after email verification (binary-com#12553)

* feat: quiestionary adding by ab testing approach

* fix: ab feature fix
  • Loading branch information
NikitK-deriv committed Dec 28, 2023
1 parent c6b5bb1 commit 11f27bf
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 33 deletions.
14 changes: 11 additions & 3 deletions .github/actions/build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ inputs:
DATADOG_SESSION_REPLAY_SAMPLE_RATE:
description: 'Datadog session replay sample rate'
required: false
DATADOG_SESSION_SAMPLE_RATE:
DATADOG_SESSION_SAMPLE_RATE:
description: 'Datadog session sample rate'
required: false
GD_API_KEY:
Expand All @@ -33,12 +33,18 @@ inputs:
GD_APP_ID:
description: 'Google drive app id'
required: false
GD_CLIENT_ID:
GD_CLIENT_ID:
description: 'Google drive client id'
required: false
RUDDERSTACK_KEY:
description: 'Rudderstack key'
required: false
GROWTHBOOK_CLIENT_KEY:
description: 'Growthbook key'
required: false
GROWTHBOOK_DECRYPTION_KEY:
description: 'Growthbook decryption key'
required: false
DATADOG_SESSION_SAMPLE_RATE_LOGS:
description: 'Datadog session sample rate for logs'
required: false
Expand All @@ -50,7 +56,7 @@ runs:
env:
NODE_ENV: ${{ inputs.NODE_ENV }}
CROWDIN_WALLETS_API_KEY: ${{ inputs.CROWDIN_WALLETS_API_KEY }}
DATADOG_APPLICATION_ID: ${{ inputs.DATADOG_APPLICATION_ID }}
DATADOG_APPLICATION_ID: ${{ inputs.DATADOG_APPLICATION_ID }}
DATADOG_CLIENT_TOKEN: ${{ inputs.DATADOG_CLIENT_TOKEN }}
DATADOG_CLIENT_TOKEN_LOGS: ${{ inputs.DATADOG_CLIENT_TOKEN_LOGS }}
DATADOG_SESSION_REPLAY_SAMPLE_RATE: ${{ inputs.DATADOG_SESSION_REPLAY_SAMPLE_RATE }}
Expand All @@ -60,6 +66,8 @@ runs:
GD_APP_ID: ${{ inputs.GD_APP_ID }}
GD_CLIENT_ID: ${{ inputs.GD_CLIENT_ID }}
RUDDERSTACK_KEY: ${{ inputs.RUDDERSTACK_KEY }}
GROWTHBOOK_CLIENT_KEY: ${{ inputs.GROWTHBOOK_CLIENT_KEY }}
GROWTHBOOK_DECRYPTION_KEY: ${{ inputs.GROWTHBOOK_DECRYPTION_KEY }}
REF_NAME: ${{ inputs.REF_NAME }}
run: npm run build:all
shell: bash
6 changes: 4 additions & 2 deletions .github/workflows/release_production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
uses: "./.github/actions/build"
with:
NODE_ENV: production
DATADOG_APPLICATION_ID: ${{ vars.DATADOG_APPLICATION_ID }}
DATADOG_APPLICATION_ID: ${{ vars.DATADOG_APPLICATION_ID }}
DATADOG_CLIENT_TOKEN: ${{ vars.DATADOG_CLIENT_TOKEN }}
DATADOG_CLIENT_TOKEN_LOGS: ${{ vars.DATADOG_CLIENT_TOKEN_LOGS }}
DATADOG_SESSION_REPLAY_SAMPLE_RATE: ${{ vars.DATADOG_SESSION_REPLAY_SAMPLE_RATE }}
Expand All @@ -31,6 +31,8 @@ jobs:
GD_APP_ID: ${{ secrets.GD_APP_ID }}
GD_CLIENT_ID: ${{ secrets.GD_CLIENT_ID }}
RUDDERSTACK_KEY: ${{ vars.RUDDERSTACK_KEY }}
GROWTHBOOK_CLIENT_KEY: ${{ vars.GROWTHBOOK_CLIENT_KEY }}
GROWTHBOOK_DECRYPTION_KEY: ${{ vars.GROWTHBOOK_DECRYPTION_KEY }}
REF_NAME: ${{ github.ref_name }}
- name: Run tests
run: npm test
Expand Down Expand Up @@ -74,5 +76,5 @@ jobs:
with:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
status: ${{ env.WORKFLOW_CONCLUSION }}
release_type: Production
release_type: Production
version: ${{ needs.build_test_and_publish.outputs.RELEASE_VERSION}}
10 changes: 6 additions & 4 deletions .github/workflows/release_staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:

jobs:
build_test_and_publish:
name: Build, Test and Publish to Cloudflare Staging
name: Build, Test and Publish to Cloudflare Staging
runs-on: Runner_16cores_Deriv-app
environment: Staging
steps:
Expand All @@ -21,7 +21,7 @@ jobs:
with:
NODE_ENV: staging
CROWDIN_WALLETS_API_KEY: ${{ secrets.CROWDIN_WALLETS_API_KEY }}
DATADOG_APPLICATION_ID: ${{ vars.DATADOG_APPLICATION_ID }}
DATADOG_APPLICATION_ID: ${{ vars.DATADOG_APPLICATION_ID }}
DATADOG_CLIENT_TOKEN: ${{ vars.DATADOG_CLIENT_TOKEN }}
DATADOG_CLIENT_TOKEN_LOGS: ${{ vars.DATADOG_CLIENT_TOKEN_LOGS }}
DATADOG_SESSION_REPLAY_SAMPLE_RATE: ${{ vars.DATADOG_SESSION_REPLAY_SAMPLE_RATE }}
Expand All @@ -31,16 +31,18 @@ jobs:
GD_APP_ID: ${{ secrets.GD_APP_ID }}
GD_CLIENT_ID: ${{ secrets.GD_CLIENT_ID }}
RUDDERSTACK_KEY: ${{ vars.RUDDERSTACK_KEY }}
GROWTHBOOK_CLIENT_KEY: ${{ vars.GROWTHBOOK_CLIENT_KEY }}
GROWTHBOOK_DECRYPTION_KEY: ${{ vars.GROWTHBOOK_DECRYPTION_KEY }}
REF_NAME: ${{ github.ref_name }}
- name: Run tests
run: npm test
- name: Versioning
uses: "./.github/actions/versioning"
with:
release_type: staging
- name: Publish to Cloudflare Pages Staging
- name: Publish to Cloudflare Pages Staging
uses: "./.github/actions/publish_to_pages_staging"
with:
with:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

Expand Down
10 changes: 6 additions & 4 deletions .github/workflows/release_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:

jobs:
build_test_and_publish:
name: Build, Test and Publish to Cloudflare Test
name: Build, Test and Publish to Cloudflare Test
runs-on: Runner_8cores_Deriv-app
environment: Staging
steps:
Expand All @@ -21,7 +21,7 @@ jobs:
with:
NODE_ENV: staging
CROWDIN_WALLETS_API_KEY: ${{ secrets.CROWDIN_WALLETS_API_KEY }}
DATADOG_APPLICATION_ID: ${{ vars.DATADOG_APPLICATION_ID }}
DATADOG_APPLICATION_ID: ${{ vars.DATADOG_APPLICATION_ID }}
DATADOG_CLIENT_TOKEN: ${{ vars.DATADOG_CLIENT_TOKEN }}
DATADOG_CLIENT_TOKEN_LOGS: ${{ vars.DATADOG_CLIENT_TOKEN_LOGS }}
DATADOG_SESSION_REPLAY_SAMPLE_RATE: ${{ vars.DATADOG_SESSION_REPLAY_SAMPLE_RATE }}
Expand All @@ -31,11 +31,13 @@ jobs:
GD_APP_ID: ${{ secrets.GD_APP_ID }}
GD_CLIENT_ID: ${{ secrets.GD_CLIENT_ID }}
RUDDERSTACK_KEY: ${{ vars.RUDDERSTACK_KEY }}
GROWTHBOOK_CLIENT_KEY: ${{ vars.GROWTHBOOK_CLIENT_KEY }}
GROWTHBOOK_DECRYPTION_KEY: ${{ vars.GROWTHBOOK_DECRYPTION_KEY }}
REF_NAME: ${{ github.ref_name }}
- name: Run tests
run: npm test
- name: Publish to Cloudflare Pages Test
- name: Publish to Cloudflare Pages Test
uses: "./.github/actions/publish_to_pages_test"
with:
with:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
10 changes: 6 additions & 4 deletions .github/workflows/release_uat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:

jobs:
build_test_and_publish:
name: Build, Test and Publish to Cloudflare UAT
name: Build, Test and Publish to Cloudflare UAT
runs-on: Runner_16cores_Deriv-app
environment: Staging
steps:
Expand All @@ -21,7 +21,7 @@ jobs:
uses: "./.github/actions/build"
with:
NODE_ENV: staging
DATADOG_APPLICATION_ID: ${{ vars.DATADOG_APPLICATION_ID }}
DATADOG_APPLICATION_ID: ${{ vars.DATADOG_APPLICATION_ID }}
DATADOG_CLIENT_TOKEN: ${{ vars.DATADOG_CLIENT_TOKEN }}
DATADOG_CLIENT_TOKEN_LOGS: ${{ vars.DATADOG_CLIENT_TOKEN_LOGS }}
DATADOG_SESSION_REPLAY_SAMPLE_RATE: ${{ vars.DATADOG_SESSION_REPLAY_SAMPLE_RATE }}
Expand All @@ -31,14 +31,16 @@ jobs:
GD_APP_ID: ${{ secrets.GD_APP_ID }}
GD_CLIENT_ID: ${{ secrets.GD_CLIENT_ID }}
RUDDERSTACK_KEY: ${{ vars.RUDDERSTACK_KEY }}
GROWTHBOOK_CLIENT_KEY: ${{ vars.GROWTHBOOK_CLIENT_KEY }}
GROWTHBOOK_DECRYPTION_KEY: ${{ vars.GROWTHBOOK_DECRYPTION_KEY }}
- name: Versioning
uses: "./.github/actions/versioning"
with:
release_type: uat
- name: Run tests
run: npm test
- name: Publish to Cloudflare Pages UAT
- name: Publish to Cloudflare Pages UAT
uses: "./.github/actions/publish_to_pages_uat"
with:
with:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ import { Form, Formik } from 'formik';
import PropTypes from 'prop-types';

import { Button, Checkbox, Dialog, Loading, Text } from '@deriv/components';
import { getLocation, SessionStore } from '@deriv/shared';
import { localize } from '@deriv/translations';
import { getLocation, SessionStore, shuffleArray } from '@deriv/shared';
import { getLanguage, localize } from '@deriv/translations';
import { Analytics } from '@deriv/analytics';

import { WS } from 'Services';
import { observer, useStore } from '@deriv/stores';

import CitizenshipForm from '../CitizenshipModal/set-citizenship-form.jsx';
import PasswordSelectionModal from '../PasswordSelectionModal/password-selection-modal.jsx';
import QuestionnaireModal from '../QuestionnaireModal';
import ResidenceForm from '../SetResidenceModal/set-residence-form.jsx';

import validateSignupFields from './validate-signup-fields.jsx';
Expand All @@ -35,6 +36,10 @@ const AccountSignup = ({
const [pw_input, setPWInput] = React.useState('');
const [is_password_modal, setIsPasswordModal] = React.useState(false);
const [is_disclaimer_accepted, setIsDisclaimerAccepted] = React.useState(false);
const [is_questionnaire, setIsQuestionnaire] = React.useState(false);
const [ab_questionnaire, setABQuestionnaire] = React.useState();
const [modded_state, setModdedState] = React.useState({});
const language = getLanguage();

const checkResidenceIsBrazil = selected_country =>
selected_country && residence_list[indexOfSelection(selected_country)]?.value?.toLowerCase() === 'br';
Expand All @@ -58,6 +63,23 @@ const AccountSignup = ({
}
setIsLoading(false);
});
// need to modify data from ab testing platform to reach translation and tracking needs
const fetchQuestionnarieData = () => {
let ab_value = Analytics.getFeatureValue('questionnaire-config', 'inactive');
const default_ab_value = ab_value;
ab_value = ab_value?.[language] ?? ab_value?.EN ?? ab_value;
if (ab_value?.show_answers_in_random_order) {
ab_value = [
{ ...default_ab_value.default },
{
...ab_value,
answers: shuffleArray(ab_value.answers),
},
];
} else if (ab_value !== 'inactive') ab_value = [{ ...default_ab_value.default }, { ...ab_value }];
return ab_value;
};
setABQuestionnaire(fetchQuestionnarieData());

Analytics.trackEvent('ce_virtual_signup_form', {
action: 'signup_confirmed',
Expand All @@ -75,6 +97,8 @@ const AccountSignup = ({
const indexOfSelection = selected_country =>
residence_list.findIndex(item => item.text.toLowerCase() === selected_country?.toLowerCase());

const handleSignup = () => onSignup(modded_state, onSignupComplete);

const onSignupPassthrough = values => {
const index_of_selected_residence = indexOfSelection(values.residence);
const index_of_selected_citizenship = indexOfSelection(values.citizenship);
Expand All @@ -84,8 +108,12 @@ const AccountSignup = ({
residence: residence_list[index_of_selected_residence].value,
citizenship: residence_list[index_of_selected_citizenship].value,
};
setModdedState(modded_values);

onSignup(modded_values, onSignupComplete);
// a/b test
ab_questionnaire === 'inactive'
? onSignup(modded_values, onSignupComplete)
: setIsQuestionnaire(!!ab_questionnaire);
};

const onSignupComplete = error => {
Expand Down Expand Up @@ -188,19 +216,28 @@ const AccountSignup = ({
</div>
</div>
) : (
<PasswordSelectionModal
api_error={api_error}
errors={errors}
handleBlur={handleBlur}
handleChange={handleChange}
isModalVisible={isModalVisible}
isSubmitting={isSubmitting}
touched={touched}
pw_input={pw_input}
setFieldTouched={setFieldTouched}
updatePassword={updatePassword}
values={values}
/>
<React.Fragment>
{is_questionnaire ? (
<QuestionnaireModal
ab_questionnaire={ab_questionnaire}
handleSignup={handleSignup}
/>
) : (
<PasswordSelectionModal
api_error={api_error}
errors={errors}
handleBlur={handleBlur}
handleChange={handleChange}
isModalVisible={isModalVisible}
isSubmitting={isSubmitting}
touched={touched}
pw_input={pw_input}
setFieldTouched={setFieldTouched}
updatePassword={updatePassword}
values={values}
/>
)}
</React.Fragment>
)}
</Form>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from 'react';
import { Formik } from 'formik';
import { render, screen } from '@testing-library/react';
import QuestionnaireModal from '../questionnaire-modal';
import userEvent from '@testing-library/user-event';
import crypto from 'crypto';

describe('QuestionnaireModal', () => {
const mock_props = {
ab_questionnaire: [
{
id: '1',
question: 'Default question',
show_answers_in_random_order: true,
answers: [
{ code: 'a', text: 'Default A' },
{ code: 'b', text: 'Default B', header: 'Default Header B' },
],
},
{
id: '1',
question: 'Sample question',
show_answers_in_random_order: false,
answers: [
{ code: 'a', text: 'Option A' },
{ code: 'b', text: 'Option B', header: 'Header B' },
],
},
],
handleSignup: jest.fn(),
};

let modal_root_el: HTMLDivElement;

beforeAll(() => {
modal_root_el = document.createElement('div');
modal_root_el.setAttribute('id', 'modal_root');
document.body.appendChild(modal_root_el);

Object.defineProperty(global, 'crypto', {
value: {
getRandomValues: (arr: Uint32Array) => crypto.randomBytes(arr.length),
},
});
});

afterAll(() => {
document.body.removeChild(modal_root_el);
});

it('renders QuestionnaireModal component correctly', () => {
render(
<Formik initialValues={{}} onSubmit={jest.fn()}>
<QuestionnaireModal {...mock_props} />
</Formik>
);
expect(screen.getByText('Sample question')).toBeInTheDocument();
});

it('calls handleSignup when an answer is clicked', () => {
render(
<Formik initialValues={{}} onSubmit={jest.fn()}>
<QuestionnaireModal {...mock_props} />
</Formik>
);
const option_a = screen.getByTestId('a_questionnaire');
userEvent.click(option_a);
expect(mock_props.handleSignup).toHaveBeenCalledTimes(1);
});
});
3 changes: 3 additions & 0 deletions packages/core/src/App/Containers/QuestionnaireModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import QuestionnaireModal from './questionnaire-modal';

export default QuestionnaireModal;
Loading

0 comments on commit 11f27bf

Please sign in to comment.