[NEW] Log events from Onboarding, NewServer, Login and Register screens (#2169)

* Create method to track user event to isolate the logic to improve future refactoring

* Track Onboarding view

* Track NewServer view

* Refactor track method due to firebase already send the current screen

* Track default login and all the oAuth options

* Track default sign up in RegisterView

* Change trackUserEvent signature and update all the files

* Track the remaining login services

* Resolve requests to improve the importing logs and events

* Leave a bugsnag breadcrumb when logging an event

* Move all logEvent to the top of code block and log remaining fail events

Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
Youssef Muhamad 2020-07-22 16:31:38 -03:00 committed by GitHub
parent df6c2c5424
commit fc324edafe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 60 additions and 7 deletions

View File

@ -15,6 +15,7 @@ import OrSeparator from './OrSeparator';
import Touch from '../utils/touch'; import Touch from '../utils/touch';
import I18n from '../i18n'; import I18n from '../i18n';
import random from '../utils/random'; import random from '../utils/random';
import { logEvent, events } from '../utils/log';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
const BUTTON_HEIGHT = 48; const BUTTON_HEIGHT = 48;
@ -77,6 +78,7 @@ class LoginServices extends React.PureComponent {
} }
onPressFacebook = () => { onPressFacebook = () => {
logEvent(events.LOGIN_WITH_FACEBOOK);
const { services, server } = this.props; const { services, server } = this.props;
const { clientId } = services.facebook; const { clientId } = services.facebook;
const endpoint = 'https://m.facebook.com/v2.9/dialog/oauth'; const endpoint = 'https://m.facebook.com/v2.9/dialog/oauth';
@ -88,6 +90,7 @@ class LoginServices extends React.PureComponent {
} }
onPressGithub = () => { onPressGithub = () => {
logEvent(events.LOGIN_WITH_GITHUB);
const { services, server } = this.props; const { services, server } = this.props;
const { clientId } = services.github; const { clientId } = services.github;
const endpoint = `https://github.com/login?client_id=${ clientId }&return_to=${ encodeURIComponent('/login/oauth/authorize') }`; const endpoint = `https://github.com/login?client_id=${ clientId }&return_to=${ encodeURIComponent('/login/oauth/authorize') }`;
@ -99,6 +102,7 @@ class LoginServices extends React.PureComponent {
} }
onPressGitlab = () => { onPressGitlab = () => {
logEvent(events.LOGIN_WITH_GITLAB);
const { services, server, Gitlab_URL } = this.props; const { services, server, Gitlab_URL } = this.props;
const { clientId } = services.gitlab; const { clientId } = services.gitlab;
const baseURL = Gitlab_URL ? Gitlab_URL.trim().replace(/\/*$/, '') : 'https://gitlab.com'; const baseURL = Gitlab_URL ? Gitlab_URL.trim().replace(/\/*$/, '') : 'https://gitlab.com';
@ -111,6 +115,7 @@ class LoginServices extends React.PureComponent {
} }
onPressGoogle = () => { onPressGoogle = () => {
logEvent(events.LOGIN_WITH_GOOGLE);
const { services, server } = this.props; const { services, server } = this.props;
const { clientId } = services.google; const { clientId } = services.google;
const endpoint = 'https://accounts.google.com/o/oauth2/auth'; const endpoint = 'https://accounts.google.com/o/oauth2/auth';
@ -122,6 +127,7 @@ class LoginServices extends React.PureComponent {
} }
onPressLinkedin = () => { onPressLinkedin = () => {
logEvent(events.LOGIN_WITH_LINKEDIN);
const { services, server } = this.props; const { services, server } = this.props;
const { clientId } = services.linkedin; const { clientId } = services.linkedin;
const endpoint = 'https://www.linkedin.com/oauth/v2/authorization'; const endpoint = 'https://www.linkedin.com/oauth/v2/authorization';
@ -133,6 +139,7 @@ class LoginServices extends React.PureComponent {
} }
onPressMeteor = () => { onPressMeteor = () => {
logEvent(events.LOGIN_WITH_METEOR);
const { services, server } = this.props; const { services, server } = this.props;
const { clientId } = services['meteor-developer']; const { clientId } = services['meteor-developer'];
const endpoint = 'https://www.meteor.com/oauth2/authorize'; const endpoint = 'https://www.meteor.com/oauth2/authorize';
@ -143,6 +150,7 @@ class LoginServices extends React.PureComponent {
} }
onPressTwitter = () => { onPressTwitter = () => {
logEvent(events.LOGIN_WITH_TWITTER);
const { server } = this.props; const { server } = this.props;
const state = this.getOAuthState(); const state = this.getOAuthState();
const url = `${ server }/_oauth/twitter/?requestTokenAndRedirect=true&state=${ state }`; const url = `${ server }/_oauth/twitter/?requestTokenAndRedirect=true&state=${ state }`;
@ -150,6 +158,7 @@ class LoginServices extends React.PureComponent {
} }
onPressWordpress = () => { onPressWordpress = () => {
logEvent(events.LOGIN_WITH_WORDPRESS);
const { services, server } = this.props; const { services, server } = this.props;
const { clientId, serverURL } = services.wordpress; const { clientId, serverURL } = services.wordpress;
const endpoint = `${ serverURL }/oauth/authorize`; const endpoint = `${ serverURL }/oauth/authorize`;
@ -161,6 +170,7 @@ class LoginServices extends React.PureComponent {
} }
onPressCustomOAuth = (loginService) => { onPressCustomOAuth = (loginService) => {
logEvent(events.LOGIN_WITH_CUSTOM_OAUTH);
const { server } = this.props; const { server } = this.props;
const { const {
serverURL, authorizePath, clientId, scope, service serverURL, authorizePath, clientId, scope, service
@ -175,6 +185,7 @@ class LoginServices extends React.PureComponent {
} }
onPressSaml = (loginService) => { onPressSaml = (loginService) => {
logEvent(events.LOGIN_WITH_SAML);
const { server } = this.props; const { server } = this.props;
const { clientConfig } = loginService; const { clientConfig } = loginService;
const { provider } = clientConfig; const { provider } = clientConfig;
@ -184,6 +195,7 @@ class LoginServices extends React.PureComponent {
} }
onPressCas = () => { onPressCas = () => {
logEvent(events.LOGIN_WITH_CAS);
const { server, CAS_login_url } = this.props; const { server, CAS_login_url } = this.props;
const ssoToken = random(17); const ssoToken = random(17);
const url = `${ CAS_login_url }?service=${ server }/_cas/${ ssoToken }`; const url = `${ CAS_login_url }?service=${ server }/_cas/${ ssoToken }`;

View File

@ -17,7 +17,7 @@ import {
import { roomsRequest } from '../actions/rooms'; import { roomsRequest } from '../actions/rooms';
import { toMomentLocale } from '../utils/moment'; import { toMomentLocale } from '../utils/moment';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import log from '../utils/log'; import log, { logEvent, events } from '../utils/log';
import I18n from '../i18n'; import I18n from '../i18n';
import database from '../lib/database'; import database from '../lib/database';
import EventEmitter from '../utils/events'; import EventEmitter from '../utils/events';
@ -32,6 +32,7 @@ const loginCall = args => RocketChat.login(args);
const logoutCall = args => RocketChat.logout(args); const logoutCall = args => RocketChat.logout(args);
const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnError = false }) { const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnError = false }) {
logEvent(events.DEFAULT_LOGIN);
try { try {
let result; let result;
if (credentials.resume) { if (credentials.resume) {
@ -52,6 +53,7 @@ const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnE
if (logoutOnError && (e.data && e.data.message && /you've been logged out by the server/i.test(e.data.message))) { if (logoutOnError && (e.data && e.data.message && /you've been logged out by the server/i.test(e.data.message))) {
yield put(logout(true)); yield put(logout(true));
} else { } else {
logEvent(events.DEFAULT_LOGIN_FAIL);
yield put(loginFailure(e)); yield put(loginFailure(e));
} }
} }

24
app/utils/log/events.js Normal file
View File

@ -0,0 +1,24 @@
export default {
JOIN_A_WORKSPACE: 'join_a_workspace',
CREATE_NEW_WORKSPACE: 'create_new_workspace',
CREATE_NEW_WORKSPACE_FAIL: 'create_new_workspace_fail',
CONNECT_TO_WORKSPACE: 'connect_to_workspace',
CONNECT_TO_WORKSPACE_FAIL: 'connect_to_workspace_fail',
JOIN_OPEN_WORKSPACE: 'join_open_workspace',
DEFAULT_LOGIN: 'default_login',
DEFAULT_LOGIN_FAIL: 'default_login_fail',
DEFAULT_SIGN_UP: 'default_sign_up',
DEFAULT_SIGN_UP_FAIL: 'default_sign_up_fail',
FORGOT_PASSWORD: 'forgot_password',
LOGIN_WITH_FACEBOOK: 'login_with_facebook',
LOGIN_WITH_GITHUB: 'login_with_github',
LOGIN_WITH_GITLAB: 'login_with_gitlab',
LOGIN_WITH_LINKEDIN: 'login_with_linkedin',
LOGIN_WITH_GOOGLE: 'login_with_google',
LOGIN_WITH_METEOR: 'login_with_meteor',
LOGIN_WITH_TWITTER: 'login_with_twitter',
LOGIN_WITH_WORDPRESS: 'login_with_wordpress',
LOGIN_WITH_CUSTOM_OAUTH: 'login_with_custom_oauth',
LOGIN_WITH_SAML: 'login_with_saml',
LOGIN_WITH_CAS: 'login_with_cas'
};

View File

@ -1,12 +1,14 @@
import { Client } from 'bugsnag-react-native'; import { Client } from 'bugsnag-react-native';
import firebase from 'react-native-firebase'; import firebase from 'react-native-firebase';
import config from '../../config'; import config from '../../../config';
import events from './events';
const bugsnag = new Client(config.BUGSNAG_API_KEY); const bugsnag = new Client(config.BUGSNAG_API_KEY);
export const { analytics } = firebase; export const { analytics } = firebase;
export const loggerConfig = bugsnag.config; export const loggerConfig = bugsnag.config;
export const { leaveBreadcrumb } = bugsnag; export const { leaveBreadcrumb } = bugsnag;
export { events };
let metadata = {}; let metadata = {};
@ -16,6 +18,11 @@ export const logServerVersion = (serverVersion) => {
}; };
}; };
export const logEvent = (eventName, payload) => {
analytics().logEvent(eventName, payload);
leaveBreadcrumb(eventName, payload);
};
export const setCurrentScreen = (currentScreen) => { export const setCurrentScreen = (currentScreen) => {
analytics().setCurrentScreen(currentScreen); analytics().setCurrentScreen(currentScreen);
leaveBreadcrumb(currentScreen, { type: 'navigation' }); leaveBreadcrumb(currentScreen, { type: 'navigation' });

View File

@ -6,7 +6,7 @@ import {
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import equal from 'deep-equal'; import equal from 'deep-equal';
import { analytics } from '../utils/log'; import { logEvent, events } from '../utils/log';
import sharedStyles from './Styles'; import sharedStyles from './Styles';
import Button from '../containers/Button'; import Button from '../containers/Button';
import I18n from '../i18n'; import I18n from '../i18n';
@ -103,6 +103,7 @@ class LoginView extends React.Component {
} }
forgotPassword = () => { forgotPassword = () => {
logEvent(events.FORGOT_PASSWORD);
const { navigation, Site_Name } = this.props; const { navigation, Site_Name } = this.props;
navigation.navigate('ForgotPasswordView', { title: Site_Name }); navigation.navigate('ForgotPasswordView', { title: Site_Name });
} }
@ -121,7 +122,6 @@ class LoginView extends React.Component {
const { loginRequest } = this.props; const { loginRequest } = this.props;
Keyboard.dismiss(); Keyboard.dismiss();
loginRequest({ user, password }); loginRequest({ user, password });
analytics().logEvent('login');
} }
renderUserForm = () => { renderUserForm = () => {

View File

@ -20,7 +20,7 @@ import FormContainer, { FormContainerInner } from '../containers/FormContainer';
import I18n from '../i18n'; import I18n from '../i18n';
import { isIOS } from '../utils/deviceInfo'; import { isIOS } from '../utils/deviceInfo';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import log from '../utils/log'; import log, { logEvent, events } from '../utils/log';
import { animateNextTransition } from '../utils/layoutAnimation'; import { animateNextTransition } from '../utils/layoutAnimation';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
import { setBasicAuth, BASIC_AUTH_KEY } from '../utils/fetch'; import { setBasicAuth, BASIC_AUTH_KEY } from '../utils/fetch';
@ -124,6 +124,7 @@ class NewServerView extends React.Component {
} }
submit = async() => { submit = async() => {
logEvent(events.CONNECT_TO_WORKSPACE);
const { text, certificate } = this.state; const { text, certificate } = this.state;
const { connectServer } = this.props; const { connectServer } = this.props;
let cert = null; let cert = null;
@ -135,6 +136,7 @@ class NewServerView extends React.Component {
try { try {
await FileSystem.copyAsync({ from: certificate.path, to: certificatePath }); await FileSystem.copyAsync({ from: certificate.path, to: certificatePath });
} catch (e) { } catch (e) {
logEvent(events.CONNECT_TO_WORKSPACE_FAIL);
log(e); log(e);
} }
cert = { cert = {
@ -152,6 +154,7 @@ class NewServerView extends React.Component {
} }
connectOpen = () => { connectOpen = () => {
logEvent(events.JOIN_OPEN_WORKSPACE);
this.setState({ connectingOpen: true }); this.setState({ connectingOpen: true });
const { connectServer } = this.props; const { connectServer } = this.props;
connectServer('https://open.rocket.chat'); connectServer('https://open.rocket.chat');

View File

@ -14,6 +14,7 @@ import { isTablet } from '../../utils/deviceInfo';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import FormContainer, { FormContainerInner } from '../../containers/FormContainer'; import FormContainer, { FormContainerInner } from '../../containers/FormContainer';
import { logEvent, events } from '../../utils/log';
class OnboardingView extends React.Component { class OnboardingView extends React.Component {
static navigationOptions = { static navigationOptions = {
@ -69,15 +70,17 @@ class OnboardingView extends React.Component {
} }
connectServer = () => { connectServer = () => {
logEvent(events.JOIN_A_WORKSPACE);
const { navigation } = this.props; const { navigation } = this.props;
navigation.navigate('NewServerView'); navigation.navigate('NewServerView');
} }
createWorkspace = async() => { createWorkspace = async() => {
logEvent(events.CREATE_NEW_WORKSPACE);
try { try {
await Linking.openURL('https://cloud.rocket.chat/trial'); await Linking.openURL('https://cloud.rocket.chat/trial');
} catch { } catch {
// do nothing logEvent(events.CREATE_NEW_WORKSPACE_FAIL);
} }
} }

View File

@ -6,7 +6,7 @@ import {
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import RNPickerSelect from 'react-native-picker-select'; import RNPickerSelect from 'react-native-picker-select';
import log from '../utils/log'; import log, { logEvent, events } from '../utils/log';
import sharedStyles from './Styles'; import sharedStyles from './Styles';
import Button from '../containers/Button'; import Button from '../containers/Button';
import I18n from '../i18n'; import I18n from '../i18n';
@ -114,6 +114,7 @@ class RegisterView extends React.Component {
} }
submit = async() => { submit = async() => {
logEvent(events.DEFAULT_SIGN_UP);
if (!this.valid()) { if (!this.valid()) {
return; return;
} }
@ -149,6 +150,7 @@ class RegisterView extends React.Component {
return loginRequest({ user: email, password }); return loginRequest({ user: email, password });
} }
if (e.data?.error) { if (e.data?.error) {
logEvent(events.DEFAULT_SIGN_UP_FAIL);
showErrorAlert(e.data.error, I18n.t('Oops')); showErrorAlert(e.data.error, I18n.t('Oops'));
} }
} }