feat(client): AB test displaying the progress donation modal multiple times (#50408)

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Ahmad Abdolsaheb
2023-06-07 19:56:01 +05:30
committed by GitHub
co-authored by Oliver Eyton-Williams
parent 65239ee68b
commit 76673d544b
8 changed files with 116 additions and 19 deletions
@@ -0,0 +1,62 @@
/* eslint-disable @typescript-eslint/naming-convention */
import React, { ReactNode, useEffect } from 'react';
import { useFeature } from '@growthbook/growthbook-react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import {
isSignedInSelector,
showMultipleProgressModalsSelector
} from '../../redux/selectors';
import { setShowMultipleProgressModals } from '../../redux/actions';
const mapStateToProps = createSelector(
isSignedInSelector,
showMultipleProgressModalsSelector,
(isSignedIn, showMultipleProgressModals: boolean) => ({
isSignedIn,
showMultipleProgressModals
})
);
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = { setShowMultipleProgressModals: (arg: boolean) => void };
interface GrowthBookReduxConnector extends StateProps, DispatchProps {
children: ReactNode;
}
const mapDispatchToProps = {
setShowMultipleProgressModals
};
const GrowthBookReduxConnector = ({
children,
isSignedIn,
showMultipleProgressModals,
setShowMultipleProgressModals
}: GrowthBookReduxConnector) => {
const displayProgressModalMultipleTimes = useFeature(
'display_progress_modal_multiple_times'
).on;
useEffect(() => {
if (
isSignedIn &&
displayProgressModalMultipleTimes &&
!showMultipleProgressModals
) {
setShowMultipleProgressModals(true);
}
}, [
isSignedIn,
showMultipleProgressModals,
displayProgressModalMultipleTimes,
setShowMultipleProgressModals
]);
return <>{children}</>;
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(GrowthBookReduxConnector);
@@ -11,6 +11,7 @@ import { createSelector } from 'reselect';
import { isSignedInSelector, userSelector } from '../../redux/selectors';
import envData from '../../../../config/env.json';
import { User } from '../../redux/prop-types';
import GrowthBookReduxConnector from './growth-book-redux-connector';
const { clientLocale, growthbookUri } = envData as {
clientLocale: string;
@@ -85,7 +86,9 @@ const GrowthBookWrapper = ({
}, [isSignedIn, user.email, user.joinDate, user.completedChallenges]);
return (
<GrowthBookProvider growthbook={growthbook}>{children}</GrowthBookProvider>
<GrowthBookProvider growthbook={growthbook}>
<GrowthBookReduxConnector>{children}</GrowthBookReduxConnector>
</GrowthBookProvider>
);
};
+2 -1
View File
@@ -10,7 +10,8 @@ export const actionTypes = createTypes(
'setRenderStartTime',
'hideCodeAlly',
'preventBlockDonationRequests',
'preventProgressDonationRequests',
'setCompletionCountWhenShownProgressModal',
'setShowMultipleProgressModals',
'openDonationModal',
'closeDonationModal',
'openSignoutModal',
+5 -2
View File
@@ -19,8 +19,11 @@ export const openDonationModal = createAction(actionTypes.openDonationModal);
export const preventBlockDonationRequests = createAction(
actionTypes.preventBlockDonationRequests
);
export const preventProgressDonationRequests = createAction(
actionTypes.preventProgressDonationRequests
export const setCompletionCountWhenShownProgressModal = createAction(
actionTypes.setCompletionCountWhenShownProgressModal
);
export const setShowMultipleProgressModals = createAction(
actionTypes.setShowMultipleProgressModals
);
export const updateDonationFormState = createAction(
actionTypes.updateDonationFormState
+2 -2
View File
@@ -23,7 +23,7 @@ import {
postChargeProcessing,
postChargeError,
preventBlockDonationRequests,
preventProgressDonationRequests,
setCompletionCountWhenShownProgressModal,
executeGA
} from './actions';
import {
@@ -45,7 +45,7 @@ function* showDonateModalSaga() {
if (recentlyClaimedBlock) {
yield put(preventBlockDonationRequests());
} else {
yield put(preventProgressDonationRequests());
yield put(setCompletionCountWhenShownProgressModal());
}
}
}
+13 -4
View File
@@ -17,7 +17,7 @@ import { createGaSaga } from './ga-saga';
import hardGoToEpic from './hard-go-to-epic';
import { createReportUserSaga } from './report-user-saga';
import { createSaveChallengeSaga } from './save-challenge-saga';
import { savedChallengesSelector } from './selectors';
import { completionCountSelector, savedChallengesSelector } from './selectors';
import { actionTypes as settingsTypes } from './settings/action-types';
import { createShowCertSaga } from './show-cert-saga';
import updateCompleteEpic from './update-complete-epic';
@@ -43,8 +43,10 @@ export const defaultDonationFormState = {
const initialState = {
appUsername: '',
showMultipleProgressModals: false,
recentlyClaimedBlock: null,
canRequestProgressDonation: true,
completionCountWhenShownProgressModal: 0,
progressDonationModalShown: false,
completionCount: 0,
currentChallengeId: store.get(CURRENT_CHALLENGE_KEY),
examInProgress: false,
@@ -251,9 +253,16 @@ export const reducer = handleActions(
...state,
recentlyClaimedBlock: null
}),
[actionTypes.preventProgressDonationRequests]: state => ({
[actionTypes.setCompletionCountWhenShownProgressModal]: state => ({
...state,
canRequestProgressDonation: false
progressDonationModalShown: true,
completionCountWhenShownProgressModal: completionCountSelector({
[MainApp]: state
})
}),
[actionTypes.setShowMultipleProgressModals]: (state, { payload }) => ({
...state,
showMultipleProgressModals: payload
}),
[actionTypes.resetUserData]: state => ({
...state,
+26 -8
View File
@@ -7,10 +7,15 @@ export const completedChallengesSelector = state =>
userSelector(state).completedChallenges || [];
export const partiallyCompletedChallengesSelector = state =>
userSelector(state).partiallyCompletedChallenges || [];
const completionCountSelector = state => state[MainApp].completionCount;
export const currentChallengeIdSelector = state =>
state[MainApp].currentChallengeId;
export const completionCountSelector = state => state[MainApp].completionCount;
export const showMultipleProgressModalsSelector = state =>
state[MainApp].showMultipleProgressModals;
export const completionCountWhenShownProgressModalSelector = state =>
state[MainApp].completionCountWhenShownProgressModal;
export const progressDonationModalShownSelector = state =>
state[MainApp].progressDonationModalShown;
export const isDonatingSelector = state => userSelector(state).isDonating;
export const isOnlineSelector = state => state[MainApp].isOnline;
export const isServerOnlineSelector = state => state[MainApp].isServerOnline;
@@ -29,11 +34,14 @@ export const showCertSelector = state => state[MainApp].showCert;
export const showCertFetchStateSelector = state =>
state[MainApp].showCertFetchState;
export const shouldRequestDonationSelector = state => {
const completedChallenges = completedChallengesSelector(state);
const completedChallengesLength = completedChallengesSelector(state).length;
const completionCount = completionCountSelector(state);
const canRequestProgressDonation = state[MainApp].canRequestProgressDonation;
const lastCompletionCount =
completionCountWhenShownProgressModalSelector(state);
const progressDonationModalShown = progressDonationModalShownSelector(state);
const isDonating = isDonatingSelector(state);
const recentlyClaimedBlock = recentlyClaimedBlockSelector(state);
const showMultipleProgressModals = showMultipleProgressModalsSelector(state);
// don't request donation if already donating
if (isDonating) return false;
@@ -41,14 +49,24 @@ export const shouldRequestDonationSelector = state => {
// a block has been completed
if (recentlyClaimedBlock) return true;
/*
When AB testing for showing multiple progress modals is active,
show a donation modal every 30 challenges after the first 50
*/
if (
showMultipleProgressModals &&
progressDonationModalShown &&
completedChallengesLength > 50 &&
completionCount - lastCompletionCount >= 30
)
return true;
// a donation has already been requested
if (!canRequestProgressDonation) return false;
if (progressDonationModalShown) return false;
// donations only appear after the user has completed ten challenges (i.e.
// not before the 11th challenge has mounted)
if (completedChallenges.length < 10) {
return false;
}
if (completedChallengesLength < 10) return false;
// this will mean we have completed 3 or more challenges this browser session
// and enough challenges overall to not be new
+2 -1
View File
@@ -14,7 +14,8 @@ export interface State {
[MainApp]: {
appUsername: string;
recentlyClaimedBlock: null | string;
canRequestProgressDonation: boolean;
completionCountWhenShownProgressModal: number | null;
showMultipleProgressModals: boolean;
completionCount: number;
currentChallengId: string;
showCert: Record<string, unknown>;