From 0c15900e5b62228c23aaae3a5f2cab997a80bad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Georget?= Date: Wed, 2 Mar 2022 19:02:31 +0100 Subject: [PATCH 1/4] Add events to monitor automatic tab opening --- packages/core/strapi/lib/Strapi.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/strapi/lib/Strapi.js b/packages/core/strapi/lib/Strapi.js index 31a4434a84..64f1d6118c 100644 --- a/packages/core/strapi/lib/Strapi.js +++ b/packages/core/strapi/lib/Strapi.js @@ -205,7 +205,13 @@ class Strapi { this.config.get('admin.autoOpen', true) !== false; if (shouldOpenAdmin && !isInitialized) { - await utils.openBrowser(this.config); + try { + await utils.openBrowser(this.config); + this.telemetry.send('didOpenTab'); + } catch (e) { + this.telemetry.send('didNotOpenTab'); + } + ; } } From a6b6991f065d0be58f0bba7da67a96e97a21c309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Georget?= Date: Wed, 2 Mar 2022 19:14:23 +0100 Subject: [PATCH 2/4] Add events to monitor first admin creation --- packages/core/admin/admin/src/pages/AuthPage/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/admin/admin/src/pages/AuthPage/index.js b/packages/core/admin/admin/src/pages/AuthPage/index.js index 5ddcaf9fdf..015c817958 100644 --- a/packages/core/admin/admin/src/pages/AuthPage/index.js +++ b/packages/core/admin/admin/src/pages/AuthPage/index.js @@ -4,7 +4,7 @@ import camelCase from 'lodash/camelCase'; import get from 'lodash/get'; import omit from 'lodash/omit'; import { Redirect, useRouteMatch, useHistory } from 'react-router-dom'; -import { auth, useQuery, useGuidedTour } from '@strapi/helper-plugin'; +import { auth, useQuery, useGuidedTour, useTracking } from '@strapi/helper-plugin'; import PropTypes from 'prop-types'; import forms from 'ee_else_ce/pages/AuthPage/utils/forms'; import persistStateToLocaleStorage from '../../components/GuidedTour/utils/persistStateToLocaleStorage'; @@ -17,6 +17,7 @@ const AuthPage = ({ hasAdmin, setHasAdmin }) => { const { push } = useHistory(); const { changeLocale } = useLocalesProvider(); const { setSkipped } = useGuidedTour(); + const { trackUsage } = useTracking(); const { params: { authType }, } = useRouteMatch('/auth/:authType'); @@ -146,6 +147,8 @@ const AuthPage = ({ hasAdmin, setHasAdmin }) => { const registerRequest = async (body, requestURL, { setSubmitting, setErrors }) => { try { + trackUsage('willCreateFirstAdmin'); + const { data: { data: { token, user }, @@ -189,6 +192,8 @@ const AuthPage = ({ hasAdmin, setHasAdmin }) => { // Redirect to the homePage push('/'); } catch (err) { + trackUsage('didNotCreateFirstAdmin'); + if (err.response) { const { data } = err.response; const apiErrors = formatAPIErrors(data); From df8cc6a063760bf503a8347f62c988d378d7c761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Georget?= Date: Wed, 2 Mar 2022 21:12:00 +0100 Subject: [PATCH 3/4] Monitor when errors happen in registration form --- .../AuthPage/components/Register/index.js | 420 +++++++++--------- 1 file changed, 214 insertions(+), 206 deletions(-) diff --git a/packages/core/admin/admin/src/pages/AuthPage/components/Register/index.js b/packages/core/admin/admin/src/pages/AuthPage/components/Register/index.js index bc4de66769..223c1fedc3 100644 --- a/packages/core/admin/admin/src/pages/AuthPage/components/Register/index.js +++ b/packages/core/admin/admin/src/pages/AuthPage/components/Register/index.js @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; import { useIntl } from 'react-intl'; import styled from 'styled-components'; +import isEmpty from 'lodash/isEmpty'; import get from 'lodash/get'; import omit from 'lodash/omit'; import { Box } from '@strapi/design-system/Box'; @@ -15,7 +16,7 @@ import { Grid, GridItem } from '@strapi/design-system/Grid'; import { Typography } from '@strapi/design-system/Typography'; import EyeStriked from '@strapi/icons/EyeStriked'; import Eye from '@strapi/icons/Eye'; -import { Form, useQuery, useNotification } from '@strapi/helper-plugin'; +import { Form, useQuery, useNotification, useTracking } from '@strapi/helper-plugin'; import { useHistory } from 'react-router-dom'; import PropTypes from 'prop-types'; import { Formik } from 'formik'; @@ -47,6 +48,7 @@ const Register = ({ fieldsToDisable, noSignin, onSubmit, schema }) => { const [passwordShown, setPasswordShown] = useState(false); const [confirmPasswordShown, setConfirmPasswordShown] = useState(false); const [userInfo, setUserInfo] = useState({}); + const { trackUsage } = useTracking(); const { formatMessage } = useIntl(); const query = useQuery(); const registrationToken = query.get('registrationToken'); @@ -108,214 +110,220 @@ const Register = ({ fieldsToDisable, noSignin, onSubmit, schema }) => { validationSchema={schema} validateOnChange={false} > - {({ values, errors, handleChange }) => ( -
-
- - - - - {formatMessage({ - id: 'Auth.form.welcome.title', - defaultMessage: 'Welcome!', - })} - - - - - {formatMessage({ - id: 'Auth.form.register.subtitle', - defaultMessage: - 'Your credentials are only used to authenticate yourself on the admin panel. All saved data will be stored in your own database.', - })} - - - - - - - { + if (submitCount > 1 && isEmpty(errors)) { + trackUsage('didSubmitWithErrorsFirstAdmin', { count: submitCount }); + } + + return ( + +
+ + + + + {formatMessage({ + id: 'Auth.form.welcome.title', + defaultMessage: 'Welcome!', })} - /> - - - + + + + {formatMessage({ + id: 'Auth.form.register.subtitle', + defaultMessage: + 'Your credentials are only used to authenticate yourself on the admin panel. All saved data will be stored in your own database.', })} - /> - - - - { - e.preventDefault(); - setPasswordShown(prev => !prev); - }} - label={formatMessage( - passwordShown - ? { - id: 'Auth.form.password.show-password', - defaultMessage: 'Show password', - } - : { - id: 'Auth.form.password.hide-password', - defaultMessage: 'Hide password', - } - )} - > - {passwordShown ? : } - - } - hint={formatMessage({ - id: 'Auth.form.password.hint', - defaultMessage: - 'Password must contain at least 8 characters, 1 uppercase, 1 lowercase and 1 number', - })} - required - label={formatMessage({ - id: 'Auth.form.password.label', - defaultMessage: 'Password', - })} - type={passwordShown ? 'text' : 'password'} - /> - { - e.preventDefault(); - setConfirmPasswordShown(prev => !prev); - }} - label={formatMessage( - confirmPasswordShown - ? { - id: 'Auth.form.password.show-password', - defaultMessage: 'Show password', - } - : { - id: 'Auth.form.password.hide-password', - defaultMessage: 'Hide password', - } - )} - > - {confirmPasswordShown ? : } - - } - required - label={formatMessage({ - id: 'Auth.form.confirmPassword.label', - defaultMessage: 'Confirmation Password', - })} - type={confirmPasswordShown ? 'text' : 'password'} - /> - { - handleChange({ target: { value: checked, name: 'news' } }); - }} - value={values.news} - name="news" - aria-label="news" - > - {formatMessage( - { - id: 'Auth.form.register.news.label', - defaultMessage: - 'Keep me updated about the new features and upcoming improvements (by doing this you accept the {terms} and the {policy}).', - }, - { - terms: ( - - {formatMessage({ - id: 'Auth.privacy-policy-agreement.terms', - defaultMessage: 'terms', - })} - - ), - policy: ( - - {formatMessage({ - id: 'Auth.privacy-policy-agreement.policy', - defaultMessage: 'policy', - })} - - ), + + + + + + + + + + + + + - - -
- - )} + required + label={formatMessage({ + id: 'Auth.form.email.label', + defaultMessage: 'Email', + })} + type="email" + /> + { + e.preventDefault(); + setPasswordShown(prev => !prev); + }} + label={formatMessage( + passwordShown + ? { + id: 'Auth.form.password.show-password', + defaultMessage: 'Show password', + } + : { + id: 'Auth.form.password.hide-password', + defaultMessage: 'Hide password', + } + )} + > + {passwordShown ? : } + + } + hint={formatMessage({ + id: 'Auth.form.password.hint', + defaultMessage: + 'Password must contain at least 8 characters, 1 uppercase, 1 lowercase and 1 number', + })} + required + label={formatMessage({ + id: 'Auth.form.password.label', + defaultMessage: 'Password', + })} + type={passwordShown ? 'text' : 'password'} + /> + { + e.preventDefault(); + setConfirmPasswordShown(prev => !prev); + }} + label={formatMessage( + confirmPasswordShown + ? { + id: 'Auth.form.password.show-password', + defaultMessage: 'Show password', + } + : { + id: 'Auth.form.password.hide-password', + defaultMessage: 'Hide password', + } + )} + > + {confirmPasswordShown ? : } + + } + required + label={formatMessage({ + id: 'Auth.form.confirmPassword.label', + defaultMessage: 'Confirmation Password', + })} + type={confirmPasswordShown ? 'text' : 'password'} + /> + { + handleChange({ target: { value: checked, name: 'news' } }); + }} + value={values.news} + name="news" + aria-label="news" + > + {formatMessage( + { + id: 'Auth.form.register.news.label', + defaultMessage: + 'Keep me updated about the new features and upcoming improvements (by doing this you accept the {terms} and the {policy}).', + }, + { + terms: ( + + {formatMessage({ + id: 'Auth.privacy-policy-agreement.terms', + defaultMessage: 'terms', + })} + + ), + policy: ( + + {formatMessage({ + id: 'Auth.privacy-policy-agreement.policy', + defaultMessage: 'policy', + })} + + ), + } + )} + + +
+
+ + ); + }} {!noSignin && ( From d88a879fc1f1c705cd4b1a25df6a0ef4b780ca17 Mon Sep 17 00:00:00 2001 From: soupette Date: Thu, 3 Mar 2022 15:12:31 +0100 Subject: [PATCH 4/4] Use imperative API to track register submission Signed-off-by: soupette --- .../AuthPage/components/Register/index.js | 89 +++++++++---------- .../admin/admin/src/pages/AuthPage/index.js | 3 +- 2 files changed, 45 insertions(+), 47 deletions(-) diff --git a/packages/core/admin/admin/src/pages/AuthPage/components/Register/index.js b/packages/core/admin/admin/src/pages/AuthPage/components/Register/index.js index 223c1fedc3..bd01dd88fd 100644 --- a/packages/core/admin/admin/src/pages/AuthPage/components/Register/index.js +++ b/packages/core/admin/admin/src/pages/AuthPage/components/Register/index.js @@ -1,7 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useIntl } from 'react-intl'; import styled from 'styled-components'; -import isEmpty from 'lodash/isEmpty'; import get from 'lodash/get'; import omit from 'lodash/omit'; import { Box } from '@strapi/design-system/Box'; @@ -16,7 +15,13 @@ import { Grid, GridItem } from '@strapi/design-system/Grid'; import { Typography } from '@strapi/design-system/Typography'; import EyeStriked from '@strapi/icons/EyeStriked'; import Eye from '@strapi/icons/Eye'; -import { Form, useQuery, useNotification, useTracking } from '@strapi/helper-plugin'; +import { + Form, + useQuery, + useNotification, + useTracking, + getYupInnerErrors, +} from '@strapi/helper-plugin'; import { useHistory } from 'react-router-dom'; import PropTypes from 'prop-types'; import { Formik } from 'formik'; @@ -42,11 +47,12 @@ const PasswordInput = styled(TextInput)` } `; -const Register = ({ fieldsToDisable, noSignin, onSubmit, schema }) => { +const Register = ({ authType, fieldsToDisable, noSignin, onSubmit, schema }) => { const toggleNotification = useNotification(); const { push } = useHistory(); const [passwordShown, setPasswordShown] = useState(false); const [confirmPasswordShown, setConfirmPasswordShown] = useState(false); + const [submitCount, setSubmitCount] = useState(0); const [userInfo, setUserInfo] = useState({}); const { trackUsage } = useTracking(); const { formatMessage } = useIntl(); @@ -99,22 +105,35 @@ const Register = ({ fieldsToDisable, noSignin, onSubmit, schema }) => { registrationToken: registrationToken || undefined, news: false, }} - onSubmit={(data, formik) => { - if (registrationToken) { - // We need to pass the registration token in the url param to the api in order to submit another admin user - onSubmit({ userInfo: omit(data, ['registrationToken']), registrationToken }, formik); - } else { - onSubmit(data, formik); + onSubmit={async (data, formik) => { + try { + await schema.validate(data, { abortEarly: false }); + + if (submitCount > 0 && authType === 'register-admin') { + trackUsage('didSubmitWithErrorsFirstAdmin', { count: submitCount.toString() }); + } + + if (registrationToken) { + // We need to pass the registration token in the url param to the api in order to submit another admin user + onSubmit( + { userInfo: omit(data, ['registrationToken']), registrationToken }, + formik + ); + } else { + onSubmit(data, formik); + } + } catch (err) { + const errors = getYupInnerErrors(err); + setSubmitCount(submitCount + 1); + + formik.setErrors(errors); } }} - validationSchema={schema} + // Leaving this part commented when we remove the tracking for the submitCount + // validationSchema={schema} validateOnChange={false} > - {({ values, errors, handleChange, submitCount }) => { - if (submitCount > 1 && isEmpty(errors)) { - trackUsage('didSubmitWithErrorsFirstAdmin', { count: submitCount }); - } - + {({ values, errors, handleChange }) => { return (
@@ -145,14 +164,7 @@ const Register = ({ fieldsToDisable, noSignin, onSubmit, schema }) => { name="firstname" required value={values.firstname} - error={ - errors.firstname - ? formatMessage({ - id: errors.firstname, - defaultMessage: 'This value is required.', - }) - : undefined - } + error={errors.firstname ? formatMessage(errors.firstname) : undefined} onChange={handleChange} label={formatMessage({ id: 'Auth.form.firstname.label', @@ -177,14 +189,7 @@ const Register = ({ fieldsToDisable, noSignin, onSubmit, schema }) => { disabled={fieldsToDisable.includes('email')} value={values.email} onChange={handleChange} - error={ - errors.email - ? formatMessage({ - id: errors.email, - defaultMessage: 'This value is required.', - }) - : undefined - } + error={errors.email ? formatMessage(errors.email) : undefined} required label={formatMessage({ id: 'Auth.form.email.label', @@ -196,14 +201,7 @@ const Register = ({ fieldsToDisable, noSignin, onSubmit, schema }) => { name="password" onChange={handleChange} value={values.password} - error={ - errors.password - ? formatMessage({ - id: errors.password, - defaultMessage: 'This value is required', - }) - : undefined - } + error={errors.password ? formatMessage(errors.password) : undefined} endAction={ // eslint-disable-next-line react/jsx-wrap-multilines { onChange={handleChange} value={values.confirmPassword} error={ - errors.confirmPassword - ? formatMessage({ - id: errors.confirmPassword, - defaultMessage: 'This value is required.', - }) - : undefined + errors.confirmPassword ? formatMessage(errors.confirmPassword) : undefined } endAction={ // eslint-disable-next-line react/jsx-wrap-multilines @@ -349,10 +342,14 @@ Register.defaultProps = { }; Register.propTypes = { + authType: PropTypes.string.isRequired, fieldsToDisable: PropTypes.array, noSignin: PropTypes.bool, onSubmit: PropTypes.func, - schema: PropTypes.shape({ type: PropTypes.string.isRequired }).isRequired, + schema: PropTypes.shape({ + validate: PropTypes.func.isRequired, + type: PropTypes.string.isRequired, + }).isRequired, }; export default Register; diff --git a/packages/core/admin/admin/src/pages/AuthPage/index.js b/packages/core/admin/admin/src/pages/AuthPage/index.js index 015c817958..063d4c0ce0 100644 --- a/packages/core/admin/admin/src/pages/AuthPage/index.js +++ b/packages/core/admin/admin/src/pages/AuthPage/index.js @@ -193,7 +193,7 @@ const AuthPage = ({ hasAdmin, setHasAdmin }) => { push('/'); } catch (err) { trackUsage('didNotCreateFirstAdmin'); - + if (err.response) { const { data } = err.response; const apiErrors = formatAPIErrors(data); @@ -254,6 +254,7 @@ const AuthPage = ({ hasAdmin, setHasAdmin }) => { return (