Merge pull request #12722 from strapi/chore/events

Add events to monitor first administrator account creation
This commit is contained in:
Alexandre BODIN 2022-03-09 18:35:21 +01:00 committed by GitHub
commit a79cafc670
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 234 additions and 217 deletions

View File

@ -15,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 } 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';
@ -41,12 +47,14 @@ 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();
const query = useQuery();
const registrationToken = query.get('registrationToken');
@ -97,225 +105,218 @@ 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 }) => (
<Form noValidate>
<Main>
<Column>
<Logo />
<Box paddingTop={6} paddingBottom={1}>
<Typography as="h1" variant="alpha">
{formatMessage({
id: 'Auth.form.welcome.title',
defaultMessage: 'Welcome!',
})}
</Typography>
</Box>
<CenteredBox paddingBottom={7}>
<Typography variant="epsilon" textColor="neutral600">
{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.',
})}
</Typography>
</CenteredBox>
</Column>
<Stack size={7}>
<Grid gap={4}>
<GridItem col={6}>
<TextInput
name="firstname"
required
value={values.firstname}
error={
errors.firstname
? formatMessage({
id: errors.firstname,
defaultMessage: 'This value is required.',
})
: undefined
}
onChange={handleChange}
label={formatMessage({
id: 'Auth.form.firstname.label',
defaultMessage: 'Firstname',
{({ values, errors, handleChange }) => {
return (
<Form noValidate>
<Main>
<Column>
<Logo />
<Box paddingTop={6} paddingBottom={1}>
<Typography as="h1" variant="alpha">
{formatMessage({
id: 'Auth.form.welcome.title',
defaultMessage: 'Welcome!',
})}
/>
</GridItem>
<GridItem col={6}>
<TextInput
name="lastname"
value={values.lastname}
onChange={handleChange}
label={formatMessage({
id: 'Auth.form.lastname.label',
defaultMessage: 'Lastname',
</Typography>
</Box>
<CenteredBox paddingBottom={7}>
<Typography variant="epsilon" textColor="neutral600">
{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.',
})}
/>
</GridItem>
</Grid>
<TextInput
name="email"
disabled={fieldsToDisable.includes('email')}
value={values.email}
onChange={handleChange}
error={
errors.email
? formatMessage({
id: errors.email,
defaultMessage: 'This value is required.',
})
: undefined
}
required
label={formatMessage({
id: 'Auth.form.email.label',
defaultMessage: 'Email',
})}
type="email"
/>
<PasswordInput
name="password"
onChange={handleChange}
value={values.password}
error={
errors.password
? formatMessage({
id: errors.password,
defaultMessage: 'This value is required',
})
: undefined
}
endAction={
// eslint-disable-next-line react/jsx-wrap-multilines
<FieldActionWrapper
onClick={e => {
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 ? <Eye /> : <EyeStriked />}
</FieldActionWrapper>
}
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'}
/>
<PasswordInput
name="confirmPassword"
onChange={handleChange}
value={values.confirmPassword}
error={
errors.confirmPassword
? formatMessage({
id: errors.confirmPassword,
defaultMessage: 'This value is required.',
})
: undefined
}
endAction={
// eslint-disable-next-line react/jsx-wrap-multilines
<FieldActionWrapper
onClick={e => {
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 ? <Eye /> : <EyeStriked />}
</FieldActionWrapper>
}
required
label={formatMessage({
id: 'Auth.form.confirmPassword.label',
defaultMessage: 'Confirmation Password',
})}
type={confirmPasswordShown ? 'text' : 'password'}
/>
<Checkbox
onValueChange={checked => {
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: (
<A target="_blank" href="https://strapi.io/terms" rel="noreferrer">
{formatMessage({
id: 'Auth.privacy-policy-agreement.terms',
defaultMessage: 'terms',
})}
</A>
),
policy: (
<A target="_blank" href="https://strapi.io/privacy" rel="noreferrer">
{formatMessage({
id: 'Auth.privacy-policy-agreement.policy',
defaultMessage: 'policy',
})}
</A>
),
</Typography>
</CenteredBox>
</Column>
<Stack size={7}>
<Grid gap={4}>
<GridItem col={6}>
<TextInput
name="firstname"
required
value={values.firstname}
error={errors.firstname ? formatMessage(errors.firstname) : undefined}
onChange={handleChange}
label={formatMessage({
id: 'Auth.form.firstname.label',
defaultMessage: 'Firstname',
})}
/>
</GridItem>
<GridItem col={6}>
<TextInput
name="lastname"
value={values.lastname}
onChange={handleChange}
label={formatMessage({
id: 'Auth.form.lastname.label',
defaultMessage: 'Lastname',
})}
/>
</GridItem>
</Grid>
<TextInput
name="email"
disabled={fieldsToDisable.includes('email')}
value={values.email}
onChange={handleChange}
error={errors.email ? formatMessage(errors.email) : undefined}
required
label={formatMessage({
id: 'Auth.form.email.label',
defaultMessage: 'Email',
})}
type="email"
/>
<PasswordInput
name="password"
onChange={handleChange}
value={values.password}
error={errors.password ? formatMessage(errors.password) : undefined}
endAction={
// eslint-disable-next-line react/jsx-wrap-multilines
<FieldActionWrapper
onClick={e => {
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 ? <Eye /> : <EyeStriked />}
</FieldActionWrapper>
}
)}
</Checkbox>
<Button fullWidth size="L" type="submit">
{formatMessage({
id: 'Auth.form.button.register',
defaultMessage: "Let's start",
})}
</Button>
</Stack>
</Main>
</Form>
)}
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'}
/>
<PasswordInput
name="confirmPassword"
onChange={handleChange}
value={values.confirmPassword}
error={
errors.confirmPassword ? formatMessage(errors.confirmPassword) : undefined
}
endAction={
// eslint-disable-next-line react/jsx-wrap-multilines
<FieldActionWrapper
onClick={e => {
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 ? <Eye /> : <EyeStriked />}
</FieldActionWrapper>
}
required
label={formatMessage({
id: 'Auth.form.confirmPassword.label',
defaultMessage: 'Confirmation Password',
})}
type={confirmPasswordShown ? 'text' : 'password'}
/>
<Checkbox
onValueChange={checked => {
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: (
<A target="_blank" href="https://strapi.io/terms" rel="noreferrer">
{formatMessage({
id: 'Auth.privacy-policy-agreement.terms',
defaultMessage: 'terms',
})}
</A>
),
policy: (
<A target="_blank" href="https://strapi.io/privacy" rel="noreferrer">
{formatMessage({
id: 'Auth.privacy-policy-agreement.policy',
defaultMessage: 'policy',
})}
</A>
),
}
)}
</Checkbox>
<Button fullWidth size="L" type="submit">
{formatMessage({
id: 'Auth.form.button.register',
defaultMessage: "Let's start",
})}
</Button>
</Stack>
</Main>
</Form>
);
}}
</Formik>
{!noSignin && (
<Box paddingTop={4}>
@ -341,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;

View File

@ -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);
@ -249,6 +254,7 @@ const AuthPage = ({ hasAdmin, setHasAdmin }) => {
return (
<Component
{...rest}
authType={authType}
fieldsToDisable={fieldsToDisable}
formErrors={formErrors}
inputsPrefix={inputsPrefix}

View File

@ -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');
}
;
}
}