diff --git a/webapp/channels/src/components/signup/__snapshots__/signup.test.tsx.snap b/webapp/channels/src/components/signup/__snapshots__/signup.test.tsx.snap index 78d02de456..691b7341e9 100644 --- a/webapp/channels/src/components/signup/__snapshots__/signup.test.tsx.snap +++ b/webapp/channels/src/components/signup/__snapshots__/signup.test.tsx.snap @@ -58,6 +58,7 @@ exports[`components/signup/Signup should match snapshot for all signup options e disabled={false} inputSize="large" name="email" + onBlur={[Function]} onChange={[Function]} placeholder="Email address" type="text" @@ -75,6 +76,7 @@ exports[`components/signup/Signup should match snapshot for all signup options e disabled={false} inputSize="large" name="name" + onBlur={[Function]} onChange={[Function]} placeholder="Choose a Username" type="text" @@ -87,6 +89,7 @@ exports[`components/signup/Signup should match snapshot for all signup options e error="" info="Must be 5-64 characters long." inputSize="large" + onBlur={[Function]} onChange={[Function]} value="" /> @@ -206,6 +209,7 @@ exports[`components/signup/Signup should match snapshot for all signup options e disabled={false} inputSize="large" name="email" + onBlur={[Function]} onChange={[Function]} placeholder="Email address" type="text" @@ -223,6 +227,7 @@ exports[`components/signup/Signup should match snapshot for all signup options e disabled={false} inputSize="large" name="name" + onBlur={[Function]} onChange={[Function]} placeholder="Choose a Username" type="text" @@ -235,6 +240,7 @@ exports[`components/signup/Signup should match snapshot for all signup options e error="" info="Must be 5-64 characters long." inputSize="large" + onBlur={[Function]} onChange={[Function]} value="" /> diff --git a/webapp/channels/src/components/signup/signup.tsx b/webapp/channels/src/components/signup/signup.tsx index ca6d6e29fc..dde147597f 100644 --- a/webapp/channels/src/components/signup/signup.tsx +++ b/webapp/channels/src/components/signup/signup.tsx @@ -1,7 +1,8 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useState, useEffect, useRef, useCallback} from 'react'; +import React, {useState, useEffect, useRef, useCallback, FocusEvent} from 'react'; + import {useIntl} from 'react-intl'; import {useLocation, useHistory} from 'react-router-dom'; import {useSelector, useDispatch} from 'react-redux'; @@ -455,16 +456,25 @@ const Signup = ({onCustomizeHeader}: SignupProps) => { } }; + function sendSignUpTelemetryEvents(telemetryId: string, props?: any) { + trackEvent('signup', telemetryId, props); + } + + type TelemetryErrorList = {errors: Array<{field: string; rule: string}>; success: boolean}; + const isUserValid = () => { let isValid = true; const providedEmail = emailInput.current?.value.trim(); + const telemetryEvents: TelemetryErrorList = {errors: [], success: true}; if (!providedEmail) { setEmailError(formatMessage({id: 'signup_user_completed.required', defaultMessage: 'This field is required'})); + telemetryEvents.errors.push({field: 'email', rule: 'not_provided'}); isValid = false; } else if (!isEmail(providedEmail)) { setEmailError(formatMessage({id: 'signup_user_completed.validEmail', defaultMessage: 'Please enter a valid email address'})); + telemetryEvents.errors.push({field: 'email', rule: 'invalid_email'}); isValid = false; } @@ -474,10 +484,11 @@ const Signup = ({onCustomizeHeader}: SignupProps) => { const usernameError = isValidUsername(providedUsername); if (usernameError) { - setNameError(usernameError.id === ValidationErrors.RESERVED_NAME ? ( - formatMessage({id: 'signup_user_completed.reserved', defaultMessage: 'This username is reserved, please choose a new one.'}) - ) : ( - formatMessage( + let nameError = ''; + if (usernameError.id === ValidationErrors.RESERVED_NAME) { + nameError = formatMessage({id: 'signup_user_completed.reserved', defaultMessage: 'This username is reserved, please choose a new one.'}); + } else { + nameError = formatMessage( { id: 'signup_user_completed.usernameLength', defaultMessage: 'Usernames have to begin with a lowercase letter and be {min}-{max} characters long. You can use lowercase letters, numbers, periods, dashes, and underscores.', @@ -486,23 +497,33 @@ const Signup = ({onCustomizeHeader}: SignupProps) => { min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH, }, - ) - )); + ); + } + telemetryEvents.errors.push({field: 'username', rule: usernameError.id.toLowerCase()}); + setNameError(nameError); isValid = false; } } else { setNameError(formatMessage({id: 'signup_user_completed.required', defaultMessage: 'This field is required'})); + telemetryEvents.errors.push({field: 'username', rule: 'not_provided'}); isValid = false; } const providedPassword = passwordInput.current?.value ?? ''; - const {error} = isValidPassword(providedPassword, getPasswordConfig(config), intl); + const {error, telemetryErrorIds} = isValidPassword(providedPassword, getPasswordConfig(config), intl); if (error) { setPasswordError(error as string); + telemetryEvents.errors = [...telemetryEvents.errors, ...telemetryErrorIds]; isValid = false; } + if (telemetryEvents.errors.length) { + telemetryEvents.success = false; + } + + sendSignUpTelemetryEvents('validate_user', telemetryEvents); + return isValid; }; @@ -512,7 +533,7 @@ const Signup = ({onCustomizeHeader}: SignupProps) => { const handleSubmit = async (e: React.MouseEvent | React.KeyboardEvent) => { e.preventDefault(); - trackEvent('signup_email', 'click_create_account', getRoleFromTrackFlow()); + sendSignUpTelemetryEvents('click_create_account', getRoleFromTrackFlow()); setIsWaiting(true); if (isUserValid()) { @@ -550,6 +571,14 @@ const Signup = ({onCustomizeHeader}: SignupProps) => { const handleReturnButtonOnClick = () => history.replace('/'); + const handleOnBlur = (e: FocusEvent, inputId: string) => { + const text = e.target.value; + if (!text) { + return; + } + sendSignUpTelemetryEvents(`typed_input_${inputId}`); + }; + const getContent = () => { if (!enableSignUpWithEmail && !enableExternalSignup) { return ( @@ -671,6 +700,7 @@ const Signup = ({onCustomizeHeader}: SignupProps) => { disabled={isWaiting || Boolean(parsedEmail)} autoFocus={true} customMessage={emailCustomLabelForInput} + onBlur={(e) => handleOnBlur(e, 'email')} /> { value: formatMessage({id: 'signup_user_completed.userHelp', defaultMessage: 'You can use lowercase letters, numbers, periods, dashes, and underscores.'}), } } + onBlur={(e) => handleOnBlur(e, 'username')} /> { createMode={true} info={passwordInfo as string} error={passwordError} + onBlur={(e) => handleOnBlur(e, 'password')} /> ) { export function isValidPassword(password: string, passwordConfig: ReturnType, intl?: IntlShape) { let errorId = t('user.settings.security.passwordError'); + const telemetryErrorIds = []; let valid = true; const minimumLength = passwordConfig.minimumLength || Constants.MIN_PASSWORD_LENGTH; if (password.length < minimumLength || password.length > Constants.MAX_PASSWORD_LENGTH) { valid = false; + telemetryErrorIds.push({field: 'password', rule: 'error_length'}); } if (passwordConfig.requireLowercase) { @@ -1379,6 +1381,7 @@ export function isValidPassword(password: string, passwordConfig: ReturnType