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

Bugfix: onboarding analytics #976

Merged
merged 10 commits into from
Aug 12, 2019
Merged
Show file tree
Hide file tree
Changes from 6 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
21 changes: 21 additions & 0 deletions app/actions/onboarding/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Saves an onboarding analytics event in state
*
* @param {object} event - Event object
*/
export function saveOnboardingEvent(event) {
return {
type: 'SAVE_EVENT',
event
};
}

/**
* Erases any event stored in state
*/
export function eraseOnboardingEvent() {
return {
type: 'SAVE_EVENT',
event: undefined
};
}
43 changes: 37 additions & 6 deletions app/components/UI/OptinMetrics/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import React, { PureComponent } from 'react';
import { View, SafeAreaView, Text, StyleSheet, TouchableOpacity, ScrollView, BackHandler, Alert } from 'react-native';
import {
View,
SafeAreaView,
Text,
StyleSheet,
TouchableOpacity,
ScrollView,
BackHandler,
Alert,
InteractionManager
} from 'react-native';
import PropTypes from 'prop-types';
import { baseStyles, fontStyles, colors } from '../../../styles/common';
import AsyncStorage from '@react-native-community/async-storage';
Expand All @@ -12,6 +22,7 @@ import { NavigationActions, withNavigationFocus } from 'react-navigation';
import StyledButton from '../StyledButton';
import Analytics from '../../../core/Analytics';
import ANALYTICS_EVENT_OPTS from '../../../util/analytics';
import { eraseOnboardingEvent } from '../../../actions/onboarding';

const styles = StyleSheet.create({
root: {
Expand Down Expand Up @@ -97,7 +108,15 @@ class OptinMetrics extends PureComponent {
/**
* React navigation prop to know if this view is focused
*/
isFocused: PropTypes.bool
isFocused: PropTypes.bool,
/**
* Onboarding event created in previous onboarding views
*/
event: PropTypes.object,
/**
* Action to erase any event stored in onboarding state
*/
eraseOnboardingEvent: PropTypes.func
};

actionsList = [
Expand Down Expand Up @@ -182,7 +201,10 @@ class OptinMetrics extends PureComponent {
onCancel = async () => {
await AsyncStorage.setItem('@MetaMask:metricsOptIn', 'denied');
Analytics.disable();
Analytics.trackEvent(ANALYTICS_EVENT_OPTS.ONBOARDING_METRICS_OPT_OUT);
InteractionManager.runAfterInteractions(() => {
Analytics.trackEvent(ANALYTICS_EVENT_OPTS.ONBOARDING_METRICS_OPT_OUT);
this.props.eraseOnboardingEvent();
});
this.continue();
};

Expand All @@ -192,7 +214,11 @@ class OptinMetrics extends PureComponent {
onConfirm = async () => {
await AsyncStorage.setItem('@MetaMask:metricsOptIn', 'agreed');
Analytics.enable();
Analytics.trackEvent(ANALYTICS_EVENT_OPTS.ONBOARDING_METRICS_OPT_IN);
InteractionManager.runAfterInteractions(() => {
this.props.event && Analytics.trackEvent(this.props.event);
Copy link
Contributor

Choose a reason for hiding this comment

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

Assuming you have an array of events instead:

if(this.props.events && this.props.events.length){
    this.props.events.forEach(e => Analytics.trackEvent(e));
}

Analytics.trackEvent(ANALYTICS_EVENT_OPTS.ONBOARDING_METRICS_OPT_IN);
this.props.eraseOnboardingEvent();
});
this.continue();
};

Expand Down Expand Up @@ -257,11 +283,16 @@ class OptinMetrics extends PureComponent {
}
}

const mapStateToProps = state => ({
event: state.onboarding.event
});

const mapDispatchToProps = dispatch => ({
setOnboardingWizardStep: step => dispatch(setOnboardingWizardStep(step))
setOnboardingWizardStep: step => dispatch(setOnboardingWizardStep(step)),
eraseOnboardingEvent: () => dispatch(eraseOnboardingEvent())
});

export default connect(
null,
mapStateToProps,
mapDispatchToProps
)(withNavigationFocus(OptinMetrics));
6 changes: 5 additions & 1 deletion app/components/UI/OptinMetrics/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ const mockStore = configureMockStore();

describe('OptinMetrics', () => {
it('should render correctly', () => {
const initialState = {};
const initialState = {
onboarding: {
event: 'event'
}
};

const wrapper = shallow(<OptinMetrics />, {
context: { store: mockStore(initialState) }
Expand Down
67 changes: 45 additions & 22 deletions app/components/Views/ImportWallet/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { Platform, Alert, ActivityIndicator, Image, Text, View, ScrollView, StyleSheet } from 'react-native';
import {
Platform,
Alert,
ActivityIndicator,
Image,
Text,
View,
ScrollView,
StyleSheet,
InteractionManager
} from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import { connect } from 'react-redux';
import { passwordSet, seedphraseBackedUp } from '../../../actions/user';
Expand All @@ -16,6 +26,9 @@ import SecureKeychain from '../../../core/SecureKeychain';
import AppConstants from '../../../core/AppConstants';
import PubNubWrapper from '../../../util/syncWithExtension';
import AnimatedFox from 'react-native-animated-fox';
import Analytics from '../../../core/Analytics';
import ANALYTICS_EVENT_OPTS from '../../../util/analytics';
import { saveOnboardingEvent } from '../../../actions/onboarding';

const styles = StyleSheet.create({
scroll: {
Expand Down Expand Up @@ -120,7 +133,11 @@ class ImportWallet extends PureComponent {
/**
* Selected address
*/
selectedAddress: PropTypes.string
selectedAddress: PropTypes.string,
/**
* Save onboarding event to state
*/
saveOnboardingEvent: PropTypes.func
};

seedwords = null;
Expand Down Expand Up @@ -297,7 +314,19 @@ class ImportWallet extends PureComponent {

onPressImport = () => {
const { existingUser } = this.state;
const action = () => this.props.navigation.push('ImportFromSeed');
const action = () => {
this.props.navigation.push('ImportFromSeed');
InteractionManager.runAfterInteractions(async () => {
if (Analytics.getEnabled()) {
Analytics.trackEvent(ANALYTICS_EVENT_OPTS.ONBOARDING_SELECTED_IMPORT_WITH_SEEDPHRASE);
return;
}
const metricsOptIn = await AsyncStorage.getItem('@MetaMask:metricsOptIn');
if (!metricsOptIn) {
this.props.saveOnboardingEvent(ANALYTICS_EVENT_OPTS.ONBOARDING_SELECTED_IMPORT_WITH_SEEDPHRASE);
}
});
};
if (existingUser) {
this.alertExistingUser(action);
} else {
Expand All @@ -324,24 +353,17 @@ class ImportWallet extends PureComponent {
);
return false;
}

if (this.props.navigation.getParam('existingUser', false)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this deleted?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

because we were doing the same thing here https://github.com/MetaMask/metamask-mobile/pull/976/files#diff-e78d44598405f73efa09c3b995881f68R340 before calling this onPressSync method

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in fact this line is using a param that we don't provide when doing a navigation.push so this will always be false

Alert.alert(
strings('sync_with_extension.warning_title'),
strings('sync_with_extension.warning_message'),
[
{
text: strings('sync_with_extension.warning_cancel_button'),
onPress: () => false,
style: 'cancel'
},
{ text: strings('sync_with_extension.warning_ok_button'), onPress: () => this.showQrCode() }
],
{ cancelable: false }
);
} else {
this.showQrCode();
}
InteractionManager.runAfterInteractions(async () => {
if (Analytics.getEnabled()) {
Analytics.trackEvent(ANALYTICS_EVENT_OPTS.ONBOARDING_SELECTED_SYNC_WITH_EXTENSION);
return;
}
const metricsOptIn = await AsyncStorage.getItem('@MetaMask:metricsOptIn');
if (!metricsOptIn) {
this.props.saveOnboardingEvent(ANALYTICS_EVENT_OPTS.ONBOARDING_SELECTED_SYNC_WITH_EXTENSION);
}
});
this.showQrCode();
};

renderLoader() {
Expand Down Expand Up @@ -439,7 +461,8 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
passwordHasBeenSet: () => dispatch(passwordSet()),
setLockTime: time => dispatch(setLockTime(time)),
seedphraseBackedUp: () => dispatch(seedphraseBackedUp())
seedphraseBackedUp: () => dispatch(seedphraseBackedUp()),
saveOnboardingEvent: event => dispatch(saveOnboardingEvent(event))
});

export default connect(
Expand Down
44 changes: 38 additions & 6 deletions app/components/Views/Onboarding/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { Platform, Text, View, ScrollView, StyleSheet, Image, Alert } from 'react-native';
import { Platform, Text, View, ScrollView, StyleSheet, Image, Alert, InteractionManager } from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import StyledButton from '../../UI/StyledButton';
import AnimatedFox from 'react-native-animated-fox';
Expand All @@ -15,6 +15,7 @@ import FadeOutOverlay from '../../UI/FadeOutOverlay';
import TermsAndConditions from '../TermsAndConditions';
import Analytics from '../../../core/Analytics';
import ANALYTICS_EVENT_OPTS from '../../../util/analytics';
import { saveOnboardingEvent } from '../../../actions/onboarding';

const styles = StyleSheet.create({
scroll: {
Expand Down Expand Up @@ -108,7 +109,11 @@ class Onboarding extends PureComponent {
/**
* redux flag that indicates if the user set a password
*/
passwordSet: PropTypes.bool
passwordSet: PropTypes.bool,
/**
* Save onboarding event to state
*/
saveOnboardingEvent: PropTypes.func
};

state = {
Expand Down Expand Up @@ -141,8 +146,19 @@ class Onboarding extends PureComponent {

onPressCreate = () => {
const { existingUser } = this.state;
Analytics.trackEvent(ANALYTICS_EVENT_OPTS.ONBOARDING_SELECTED_CREATE_NEW_WALLET);
const action = () => this.props.navigation.navigate('CreateWallet');
const action = () => {
this.props.navigation.navigate('CreateWallet');
InteractionManager.runAfterInteractions(async () => {
if (Analytics.getEnabled()) {
Analytics.trackEvent(ANALYTICS_EVENT_OPTS.ONBOARDING_SELECTED_CREATE_NEW_WALLET);
return;
}
const metricsOptIn = await AsyncStorage.getItem('@MetaMask:metricsOptIn');
if (!metricsOptIn) {
this.props.saveOnboardingEvent(ANALYTICS_EVENT_OPTS.ONBOARDING_SELECTED_CREATE_NEW_WALLET);
}
});
};
if (existingUser) {
this.alertExistingUser(action);
} else {
Expand All @@ -152,7 +168,16 @@ class Onboarding extends PureComponent {

onPressImport = () => {
this.props.navigation.push('ImportWallet');
Analytics.trackEvent(ANALYTICS_EVENT_OPTS.ONBOARDING_SELECTED_IMPORT_WALLET);
InteractionManager.runAfterInteractions(async () => {
if (Analytics.getEnabled()) {
Analytics.trackEvent(ANALYTICS_EVENT_OPTS.ONBOARDING_SELECTED_IMPORT_WALLET);
return;
}
const metricsOptIn = await AsyncStorage.getItem('@MetaMask:metricsOptIn');
if (!metricsOptIn) {
this.props.saveOnboardingEvent(ANALYTICS_EVENT_OPTS.ONBOARDING_SELECTED_IMPORT_WALLET);
}
});
};

alertExistingUser = callback => {
Expand Down Expand Up @@ -246,4 +271,11 @@ const mapStateToProps = state => ({
passwordSet: state.user.passwordSet
});

export default connect(mapStateToProps)(Onboarding);
const mapDispatchToProps = dispatch => ({
saveOnboardingEvent: event => dispatch(saveOnboardingEvent(event))
});

export default connect(
mapStateToProps,
mapDispatchToProps
)(Onboarding);
4 changes: 3 additions & 1 deletion app/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import transactionReducer from './transaction';
import userReducer from './user';
import wizardReducer from './wizard';
import analyticsReducer from './analytics';
import onboardingReducer from './onboarding';
import { combineReducers } from 'redux';

const rootReducer = combineReducers({
Expand All @@ -22,7 +23,8 @@ const rootReducer = combineReducers({
alert: alertReducer,
transaction: transactionReducer,
user: userReducer,
wizard: wizardReducer
wizard: wizardReducer,
onboarding: onboardingReducer
});

export default rootReducer;
28 changes: 28 additions & 0 deletions app/reducers/onboarding/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { REHYDRATE } from 'redux-persist';

const initialState = {
event: undefined
};

/**
* Reducer to keep track of user oboarding actions to send it to analytics if the user
* decides to optin after finishing onboarding flow
*/
const onboardingReducer = (state = initialState, action) => {
switch (action.type) {
case REHYDRATE:
if (action.payload && action.payload.onboarding) {
return { ...state, ...action.payload.onboarding };
}
return state;
case 'SAVE_EVENT':
return {
...state,
event: action.event
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer if this was an array instead and you push events to the stack, so you can track the scenario of the user going to import wallet and then coming back to the first screen and then create a new wallet.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

totally agree, added a comment on this on analytics sheet. As we didn't have this array analytics behavior there yet I chose to wait for MetaMask/Design#119

};
default:
return state;
}
};

export default onboardingReducer;
13 changes: 13 additions & 0 deletions app/util/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const NAMES = {
ONBOARDING_METRICS_OPT_OUT: 'Metrics Opt Out',
ONBOARDING_SELECTED_CREATE_NEW_WALLET: 'Selected Create New Wallet',
ONBOARDING_SELECTED_IMPORT_WALLET: 'Selected Import Wallet',
ONBOARDING_SELECTED_SYNC_WITH_EXTENSION: 'Selected Sync with Extension',
ONBOARDING_SELECTED_WITH_SEEDPHRASE: 'Selected Import with Seedphrase',
// Navigation Drawer
NAVIGATION_TAPS_ACCOUNT_NAME: 'Tapped Account Name / Profile',
NAVIGATION_TAPS_SEND: "Taps on 'Send'",
Expand Down Expand Up @@ -67,6 +69,7 @@ const ACTIONS = {
//Onboarding
METRICS_OPTS: 'Metrics Option',
IMPORT_OR_CREATE: 'Import or Create',
IMPORT_OR_SYNC: 'Import or Sync',
// Navigation Drawer
NAVIGATION_DRAWER: 'Navigation Drawer',
// Common Navigation
Expand Down Expand Up @@ -126,6 +129,16 @@ const ANALYTICS_EVENT_OPTS = {
ACTIONS.IMPORT_OR_CREATE,
NAMES.ONBOARDING_SELECTED_IMPORT_WALLET
),
ONBOARDING_SELECTED_IMPORT_WITH_SEEDPHRASE: generateOpt(
CATEGORIES.ONBOARDING,
ACTIONS.IMPORT_OR_SYNC,
NAMES.ONBOARDING_SELECTED_WITH_SEEDPHRASE
),
ONBOARDING_SELECTED_SYNC_WITH_EXTENSION: generateOpt(
CATEGORIES.ONBOARDING,
ACTIONS.IMPORT_OR_SYNC,
NAMES.ONBOARDING_SELECTED_SYNC_WITH_EXTENSION
),
// Navigation Drawer
NAVIGATION_TAPS_ACCOUNT_NAME: generateOpt(
CATEGORIES.NAVIGATION_DRAWER,
Expand Down