зеркало из https://github.com/mozilla/fxa.git
Merge pull request #17034 from mozilla/FXA-9529
feat(glean): Add front-end glean events for reset pwd with code
This commit is contained in:
Коммит
1e25c59475
|
@ -3,14 +3,24 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { screen } from '@testing-library/react';
|
import { screen, waitFor } from '@testing-library/react';
|
||||||
import { renderWithLocalizationProvider } from 'fxa-react/lib/test-utils/localizationProvider';
|
import { renderWithLocalizationProvider } from 'fxa-react/lib/test-utils/localizationProvider';
|
||||||
import LinkRememberPassword from '.';
|
import LinkRememberPassword, { LinkRememberPasswordProps } from '.';
|
||||||
import { MOCK_ACCOUNT } from '../../models/mocks';
|
import { MOCK_ACCOUNT } from '../../models/mocks';
|
||||||
import { getFtlBundle, testAllL10n } from 'fxa-react/lib/test-utils';
|
import { getFtlBundle, testAllL10n } from 'fxa-react/lib/test-utils';
|
||||||
import { FluentBundle } from '@fluent/bundle';
|
import { FluentBundle } from '@fluent/bundle';
|
||||||
import { MOCK_CLIENT_ID, MOCK_EMAIL } from '../../pages/mocks';
|
import { MOCK_CLIENT_ID, MOCK_EMAIL } from '../../pages/mocks';
|
||||||
import { LocationProvider } from '@reach/router';
|
import { LocationProvider } from '@reach/router';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
|
jest.mock('../../lib/glean', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: {
|
||||||
|
passwordReset: {
|
||||||
|
emailConfirmationSignin: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const mockLocation = () => {
|
const mockLocation = () => {
|
||||||
return {
|
return {
|
||||||
|
@ -23,9 +33,12 @@ jest.mock('@reach/router', () => ({
|
||||||
useLocation: () => mockLocation(),
|
useLocation: () => mockLocation(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const Subject = ({ email = MOCK_ACCOUNT.primaryEmail.email }) => (
|
const Subject = ({
|
||||||
|
email = MOCK_ACCOUNT.primaryEmail.email,
|
||||||
|
clickHandler,
|
||||||
|
}: Partial<LinkRememberPasswordProps>) => (
|
||||||
<LocationProvider>
|
<LocationProvider>
|
||||||
<LinkRememberPassword {...{ email }} />
|
<LinkRememberPassword {...{ email, clickHandler }} />
|
||||||
</LocationProvider>
|
</LocationProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -55,4 +68,17 @@ describe('LinkRememberPassword', () => {
|
||||||
`/?client_id=123&prefillEmail=${encodeURIComponent(MOCK_EMAIL)}`
|
`/?client_id=123&prefillEmail=${encodeURIComponent(MOCK_EMAIL)}`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('executes a clickHandler if passed in as prop', async () => {
|
||||||
|
const mockClickHandler = jest.fn();
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
renderWithLocalizationProvider(<Subject clickHandler={mockClickHandler} />);
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
user.click(screen.getByRole('link', { name: /^Sign in/ }))
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockClickHandler).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,28 +3,46 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FtlMsg } from 'fxa-react/lib/utils';
|
import { FtlMsg, hardNavigate } from 'fxa-react/lib/utils';
|
||||||
import { useLocation } from '@reach/router';
|
import { useLocation } from '@reach/router';
|
||||||
import { isEmailValid } from 'fxa-shared/email/helpers';
|
import { isEmailValid } from 'fxa-shared/email/helpers';
|
||||||
|
|
||||||
export type LinkRememberPasswordProps = {
|
export type LinkRememberPasswordProps = {
|
||||||
email?: string;
|
email?: string;
|
||||||
forceAuth?: boolean;
|
clickHandler?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const LinkRememberPassword = ({ email }: LinkRememberPasswordProps) => {
|
const LinkRememberPassword = ({
|
||||||
|
email,
|
||||||
|
clickHandler,
|
||||||
|
}: LinkRememberPasswordProps) => {
|
||||||
|
let linkHref: string;
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
let linkHref: string;
|
params.delete('email');
|
||||||
|
params.delete('hasLinkedAccount');
|
||||||
|
params.delete('hasPassword');
|
||||||
|
params.delete('showReactApp');
|
||||||
|
|
||||||
if (email && isEmailValid(email)) {
|
if (email && isEmailValid(email)) {
|
||||||
params.set('prefillEmail', email);
|
params.set('prefillEmail', email);
|
||||||
// react and backbone signin handle email/prefill params differently so
|
linkHref = `/?${params.toString()}`;
|
||||||
// go back to index - any errors (like throttling) will be shown there on submit
|
|
||||||
linkHref = `/?${params}`;
|
|
||||||
} else {
|
} else {
|
||||||
linkHref = params.size > 0 ? `/?${params}` : '/';
|
linkHref = params.size > 0 ? `/?${params.toString()}` : '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleClick = (
|
||||||
|
event: React.MouseEvent<HTMLAnchorElement, MouseEvent>
|
||||||
|
) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (clickHandler) {
|
||||||
|
// additional optional click handlong behavior
|
||||||
|
clickHandler();
|
||||||
|
}
|
||||||
|
hardNavigate(linkHref);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap gap-2 justify-center text-sm mt-6">
|
<div className="flex flex-wrap gap-2 justify-center text-sm mt-6">
|
||||||
<FtlMsg id="remember-password-text">
|
<FtlMsg id="remember-password-text">
|
||||||
|
@ -32,7 +50,12 @@ const LinkRememberPassword = ({ email }: LinkRememberPasswordProps) => {
|
||||||
</FtlMsg>
|
</FtlMsg>
|
||||||
<FtlMsg id="remember-password-signin-link">
|
<FtlMsg id="remember-password-signin-link">
|
||||||
{/* TODO in FXA-8636 replace with Link component */}
|
{/* TODO in FXA-8636 replace with Link component */}
|
||||||
<a href={linkHref} className="link-blue" id="remember-password">
|
<a
|
||||||
|
href={linkHref}
|
||||||
|
className="link-blue"
|
||||||
|
id="remember-password"
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
Sign in
|
Sign in
|
||||||
</a>
|
</a>
|
||||||
</FtlMsg>
|
</FtlMsg>
|
||||||
|
|
|
@ -21,7 +21,7 @@ jest.mock('../../lib/metrics', () => ({
|
||||||
jest.mock('../../lib/glean', () => ({
|
jest.mock('../../lib/glean', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default: {
|
default: {
|
||||||
resetPassword: { createNewSuccess: jest.fn() },
|
passwordReset: { createNewSuccess: jest.fn() },
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ describe('Ready', () => {
|
||||||
// });
|
// });
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(GleanMetrics.resetPassword.createNewSuccess as jest.Mock).mockClear();
|
(GleanMetrics.passwordReset.createNewSuccess as jest.Mock).mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders as expected with default values', () => {
|
it('renders as expected with default values', () => {
|
||||||
|
@ -139,7 +139,7 @@ describe('Ready', () => {
|
||||||
it('emits a metrics event on render', () => {
|
it('emits a metrics event on render', () => {
|
||||||
renderWithLocalizationProvider(<Ready {...{ viewName, isSignedIn }} />);
|
renderWithLocalizationProvider(<Ready {...{ viewName, isSignedIn }} />);
|
||||||
expect(usePageViewEvent).toHaveBeenCalledWith(viewName, REACT_ENTRYPOINT);
|
expect(usePageViewEvent).toHaveBeenCalledWith(viewName, REACT_ENTRYPOINT);
|
||||||
expect(GleanMetrics.resetPassword.createNewSuccess).toHaveBeenCalledTimes(
|
expect(GleanMetrics.passwordReset.createNewSuccess).toHaveBeenCalledTimes(
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -96,7 +96,7 @@ const Ready = ({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (viewName === 'reset-password-confirmed') {
|
if (viewName === 'reset-password-confirmed') {
|
||||||
GleanMetrics.resetPassword.createNewSuccess();
|
GleanMetrics.passwordReset.createNewSuccess();
|
||||||
}
|
}
|
||||||
}, [viewName]);
|
}, [viewName]);
|
||||||
|
|
||||||
|
|
|
@ -234,6 +234,9 @@ const recordEventMetric = (
|
||||||
case 'login_totp_code_success_view':
|
case 'login_totp_code_success_view':
|
||||||
login.totpCodeSuccessView.record();
|
login.totpCodeSuccessView.record();
|
||||||
break;
|
break;
|
||||||
|
case 'password_reset_create_new_recovery_key_message_click':
|
||||||
|
passwordReset.createNewRecoveryKeyMessageClick.record();
|
||||||
|
break;
|
||||||
case 'password_reset_create_new_submit':
|
case 'password_reset_create_new_submit':
|
||||||
passwordReset.createNewSubmit.record();
|
passwordReset.createNewSubmit.record();
|
||||||
break;
|
break;
|
||||||
|
@ -243,6 +246,24 @@ const recordEventMetric = (
|
||||||
case 'password_reset_create_new_view':
|
case 'password_reset_create_new_view':
|
||||||
passwordReset.createNewView.record();
|
passwordReset.createNewView.record();
|
||||||
break;
|
break;
|
||||||
|
case 'password_reset_email_confirmation_different_account':
|
||||||
|
passwordReset.emailConfirmationDifferentAccount.record();
|
||||||
|
break;
|
||||||
|
case 'password_reset_email_confirmation_signin':
|
||||||
|
passwordReset.emailConfirmationSignin.record();
|
||||||
|
break;
|
||||||
|
case 'password_reset_email_confirmation_submit':
|
||||||
|
passwordReset.emailConfirmationSubmit.record();
|
||||||
|
break;
|
||||||
|
case 'password_reset_email_confirmation_view':
|
||||||
|
passwordReset.emailConfirmationView.record();
|
||||||
|
break;
|
||||||
|
case 'password_reset_email_confirmation_resend_code':
|
||||||
|
passwordReset.emailConfirmationResendCode.record();
|
||||||
|
break;
|
||||||
|
case 'password_reset_recovery_key_cannot_find':
|
||||||
|
passwordReset.recoveryKeyCannotFind.record();
|
||||||
|
break;
|
||||||
case 'password_reset_recovery_key_create_new_submit':
|
case 'password_reset_recovery_key_create_new_submit':
|
||||||
passwordReset.recoveryKeyCreateNewSubmit.record();
|
passwordReset.recoveryKeyCreateNewSubmit.record();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -35,7 +35,7 @@ jest.mock('../../../lib/metrics', () => ({
|
||||||
logViewEvent: jest.fn(),
|
logViewEvent: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('../../../lib/glean', () => ({
|
jest.mock('../../../lib/glean', () => ({
|
||||||
resetPassword: {
|
passwordReset: {
|
||||||
recoveryKeyView: jest.fn(),
|
recoveryKeyView: jest.fn(),
|
||||||
recoveryKeySubmit: jest.fn(),
|
recoveryKeySubmit: jest.fn(),
|
||||||
},
|
},
|
||||||
|
@ -154,7 +154,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
|
||||||
.spyOn(console, 'warn')
|
.spyOn(console, 'warn')
|
||||||
.mockImplementation(() => {});
|
.mockImplementation(() => {});
|
||||||
|
|
||||||
(GleanMetrics.resetPassword.recoveryKeyView as jest.Mock).mockReset();
|
(GleanMetrics.passwordReset.recoveryKeyView as jest.Mock).mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -171,7 +171,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
|
||||||
'The link you clicked was missing characters, and may have been broken by your email client. Copy the address carefully, and try again.'
|
'The link you clicked was missing characters, and may have been broken by your email client. Copy the address carefully, and try again.'
|
||||||
);
|
);
|
||||||
expect(mockConsoleWarn).toBeCalled();
|
expect(mockConsoleWarn).toBeCalled();
|
||||||
expect(GleanMetrics.resetPassword.recoveryKeyView).not.toHaveBeenCalled();
|
expect(GleanMetrics.passwordReset.recoveryKeyView).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it('with missing code', async () => {
|
it('with missing code', async () => {
|
||||||
renderSubject({ params: paramsWithMissingCode });
|
renderSubject({ params: paramsWithMissingCode });
|
||||||
|
@ -180,7 +180,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
|
||||||
name: 'Reset password link damaged',
|
name: 'Reset password link damaged',
|
||||||
});
|
});
|
||||||
expect(mockConsoleWarn).toBeCalled();
|
expect(mockConsoleWarn).toBeCalled();
|
||||||
expect(GleanMetrics.resetPassword.recoveryKeyView).not.toHaveBeenCalled();
|
expect(GleanMetrics.passwordReset.recoveryKeyView).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it('with missing email', async () => {
|
it('with missing email', async () => {
|
||||||
renderSubject({ params: paramsWithMissingEmail });
|
renderSubject({ params: paramsWithMissingEmail });
|
||||||
|
@ -189,7 +189,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
|
||||||
name: 'Reset password link damaged',
|
name: 'Reset password link damaged',
|
||||||
});
|
});
|
||||||
expect(mockConsoleWarn).toBeCalled();
|
expect(mockConsoleWarn).toBeCalled();
|
||||||
expect(GleanMetrics.resetPassword.recoveryKeyView).not.toHaveBeenCalled();
|
expect(GleanMetrics.passwordReset.recoveryKeyView).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -349,8 +349,8 @@ describe('PageAccountRecoveryConfirmKey', () => {
|
||||||
|
|
||||||
describe('emits metrics events', () => {
|
describe('emits metrics events', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(GleanMetrics.resetPassword.recoveryKeyView as jest.Mock).mockReset();
|
(GleanMetrics.passwordReset.recoveryKeyView as jest.Mock).mockReset();
|
||||||
(GleanMetrics.resetPassword.recoveryKeySubmit as jest.Mock).mockReset();
|
(GleanMetrics.passwordReset.recoveryKeySubmit as jest.Mock).mockReset();
|
||||||
});
|
});
|
||||||
afterEach(() => jest.clearAllMocks());
|
afterEach(() => jest.clearAllMocks());
|
||||||
it('on engage, submit, success', async () => {
|
it('on engage, submit, success', async () => {
|
||||||
|
@ -361,7 +361,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
|
||||||
|
|
||||||
expect(logPageViewEvent).toHaveBeenCalledWith(viewName, REACT_ENTRYPOINT);
|
expect(logPageViewEvent).toHaveBeenCalledWith(viewName, REACT_ENTRYPOINT);
|
||||||
expect(
|
expect(
|
||||||
GleanMetrics.resetPassword.recoveryKeyView as jest.Mock
|
GleanMetrics.passwordReset.recoveryKeyView as jest.Mock
|
||||||
).toHaveBeenCalledTimes(1);
|
).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
await typeByLabelText('Enter account recovery key')(MOCK_RECOVERY_KEY);
|
await typeByLabelText('Enter account recovery key')(MOCK_RECOVERY_KEY);
|
||||||
|
@ -388,7 +388,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
GleanMetrics.resetPassword.recoveryKeySubmit as jest.Mock
|
GleanMetrics.passwordReset.recoveryKeySubmit as jest.Mock
|
||||||
).toHaveBeenCalledTimes(1);
|
).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -63,7 +63,7 @@ const AccountRecoveryConfirmKey = ({
|
||||||
|
|
||||||
setShowLoadingSpinner(false);
|
setShowLoadingSpinner(false);
|
||||||
logPageViewEvent(viewName, REACT_ENTRYPOINT);
|
logPageViewEvent(viewName, REACT_ENTRYPOINT);
|
||||||
GleanMetrics.resetPassword.recoveryKeyView();
|
GleanMetrics.passwordReset.recoveryKeyView();
|
||||||
} else {
|
} else {
|
||||||
setLinkStatus(LinkStatus.expired);
|
setLinkStatus(LinkStatus.expired);
|
||||||
}
|
}
|
||||||
|
@ -193,7 +193,7 @@ const AccountRecoveryConfirmKey = ({
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setBannerMessage(undefined);
|
setBannerMessage(undefined);
|
||||||
logViewEvent('flow', `${viewName}.submit`, REACT_ENTRYPOINT);
|
logViewEvent('flow', `${viewName}.submit`, REACT_ENTRYPOINT);
|
||||||
GleanMetrics.resetPassword.recoveryKeySubmit();
|
GleanMetrics.passwordReset.recoveryKeySubmit();
|
||||||
|
|
||||||
// if the submitted key does not match the expected format,
|
// if the submitted key does not match the expected format,
|
||||||
// abort before submitting to the auth server
|
// abort before submitting to the auth server
|
||||||
|
|
|
@ -34,7 +34,7 @@ import GleanMetrics from '../../../lib/glean';
|
||||||
jest.mock('../../../lib/glean', () => ({
|
jest.mock('../../../lib/glean', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default: {
|
default: {
|
||||||
resetPassword: {
|
passwordReset: {
|
||||||
recoveryKeyCreatePasswordView: jest.fn(),
|
recoveryKeyCreatePasswordView: jest.fn(),
|
||||||
recoveryKeyCreatePasswordSubmit: jest.fn(),
|
recoveryKeyCreatePasswordSubmit: jest.fn(),
|
||||||
},
|
},
|
||||||
|
@ -219,7 +219,7 @@ describe('AccountRecoveryResetPassword page', () => {
|
||||||
REACT_ENTRYPOINT
|
REACT_ENTRYPOINT
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
GleanMetrics.resetPassword.recoveryKeyCreatePasswordView
|
GleanMetrics.passwordReset.recoveryKeyCreatePasswordView
|
||||||
).toHaveBeenCalled();
|
).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -256,7 +256,7 @@ describe('AccountRecoveryResetPassword page', () => {
|
||||||
'verification.success'
|
'verification.success'
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
GleanMetrics.resetPassword.recoveryKeyCreatePasswordSubmit
|
GleanMetrics.passwordReset.recoveryKeyCreatePasswordSubmit
|
||||||
).toHaveBeenCalled();
|
).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ const AccountRecoveryResetPassword = ({
|
||||||
integration,
|
integration,
|
||||||
}: AccountRecoveryResetPasswordProps) => {
|
}: AccountRecoveryResetPasswordProps) => {
|
||||||
usePageViewEvent(viewName, REACT_ENTRYPOINT);
|
usePageViewEvent(viewName, REACT_ENTRYPOINT);
|
||||||
GleanMetrics.resetPassword.recoveryKeyCreatePasswordView();
|
GleanMetrics.passwordReset.recoveryKeyCreatePasswordView();
|
||||||
|
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -199,7 +199,7 @@ const AccountRecoveryResetPassword = ({
|
||||||
async function onSubmit(data: AccountRecoveryResetPasswordFormData) {
|
async function onSubmit(data: AccountRecoveryResetPasswordFormData) {
|
||||||
const password = data.newPassword;
|
const password = data.newPassword;
|
||||||
const email = verificationInfo.email;
|
const email = verificationInfo.email;
|
||||||
GleanMetrics.resetPassword.recoveryKeyCreatePasswordSubmit();
|
GleanMetrics.passwordReset.recoveryKeyCreatePasswordSubmit();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const options = {
|
const options = {
|
||||||
|
|
|
@ -48,7 +48,7 @@ jest.mock('../../../lib/metrics', () => ({
|
||||||
jest.mock('../../../lib/glean', () => ({
|
jest.mock('../../../lib/glean', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default: {
|
default: {
|
||||||
resetPassword: { createNewView: jest.fn(), createNewSubmit: jest.fn() },
|
passwordReset: { createNewView: jest.fn(), createNewSubmit: jest.fn() },
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ describe('CompleteResetPassword page', () => {
|
||||||
|
|
||||||
session = mockSession(true, false);
|
session = mockSession(true, false);
|
||||||
|
|
||||||
(GleanMetrics.resetPassword.createNewView as jest.Mock).mockReset();
|
(GleanMetrics.passwordReset.createNewView as jest.Mock).mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -202,7 +202,7 @@ describe('CompleteResetPassword page', () => {
|
||||||
'The link you clicked was missing characters, and may have been broken by your email client. Copy the address carefully, and try again.'
|
'The link you clicked was missing characters, and may have been broken by your email client. Copy the address carefully, and try again.'
|
||||||
);
|
);
|
||||||
expect(mockConsoleWarn).toBeCalled();
|
expect(mockConsoleWarn).toBeCalled();
|
||||||
expect(GleanMetrics.resetPassword.createNewView).not.toBeCalled();
|
expect(GleanMetrics.passwordReset.createNewView).not.toBeCalled();
|
||||||
});
|
});
|
||||||
it('with missing code', async () => {
|
it('with missing code', async () => {
|
||||||
render(<Subject params={paramsWithMissingCode} />, account);
|
render(<Subject params={paramsWithMissingCode} />, account);
|
||||||
|
@ -211,7 +211,7 @@ describe('CompleteResetPassword page', () => {
|
||||||
name: 'Reset password link damaged',
|
name: 'Reset password link damaged',
|
||||||
});
|
});
|
||||||
expect(mockConsoleWarn).toBeCalled();
|
expect(mockConsoleWarn).toBeCalled();
|
||||||
expect(GleanMetrics.resetPassword.createNewView).not.toBeCalled();
|
expect(GleanMetrics.passwordReset.createNewView).not.toBeCalled();
|
||||||
});
|
});
|
||||||
it('with missing email', async () => {
|
it('with missing email', async () => {
|
||||||
render(<Subject params={paramsWithMissingEmail} />, account);
|
render(<Subject params={paramsWithMissingEmail} />, account);
|
||||||
|
@ -220,7 +220,7 @@ describe('CompleteResetPassword page', () => {
|
||||||
name: 'Reset password link damaged',
|
name: 'Reset password link damaged',
|
||||||
});
|
});
|
||||||
expect(mockConsoleWarn).toBeCalled();
|
expect(mockConsoleWarn).toBeCalled();
|
||||||
expect(GleanMetrics.resetPassword.createNewView).not.toBeCalled();
|
expect(GleanMetrics.passwordReset.createNewView).not.toBeCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -236,7 +236,7 @@ describe('CompleteResetPassword page', () => {
|
||||||
'complete-reset-password',
|
'complete-reset-password',
|
||||||
REACT_ENTRYPOINT
|
REACT_ENTRYPOINT
|
||||||
);
|
);
|
||||||
expect(GleanMetrics.resetPassword.createNewView).toBeCalledTimes(1);
|
expect(GleanMetrics.passwordReset.createNewView).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('errors', () => {
|
describe('errors', () => {
|
||||||
|
@ -357,7 +357,7 @@ describe('CompleteResetPassword page', () => {
|
||||||
expect(
|
expect(
|
||||||
(account.completeResetPassword as jest.Mock).mock.calls[0]
|
(account.completeResetPassword as jest.Mock).mock.calls[0]
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(GleanMetrics.resetPassword.createNewSubmit).toBeCalledTimes(1);
|
expect(GleanMetrics.passwordReset.createNewSubmit).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('submits with emailToHashWith if present', async () => {
|
it('submits with emailToHashWith if present', async () => {
|
||||||
|
|
|
@ -154,7 +154,7 @@ const CompleteResetPassword = ({
|
||||||
const renderCompleteResetPassword = () => {
|
const renderCompleteResetPassword = () => {
|
||||||
setShowLoadingSpinner(false);
|
setShowLoadingSpinner(false);
|
||||||
logPageViewEvent(viewName, REACT_ENTRYPOINT);
|
logPageViewEvent(viewName, REACT_ENTRYPOINT);
|
||||||
GleanMetrics.resetPassword.createNewView();
|
GleanMetrics.passwordReset.createNewView();
|
||||||
};
|
};
|
||||||
|
|
||||||
/* When the user clicks the confirm password reset link from their email, we check
|
/* When the user clicks the confirm password reset link from their email, we check
|
||||||
|
@ -214,7 +214,7 @@ const CompleteResetPassword = ({
|
||||||
// how account password hashing works previously.
|
// how account password hashing works previously.
|
||||||
const emailToUse = emailToHashWith || email;
|
const emailToUse = emailToHashWith || email;
|
||||||
|
|
||||||
GleanMetrics.resetPassword.createNewSubmit();
|
GleanMetrics.passwordReset.createNewSubmit();
|
||||||
|
|
||||||
const accountResetData = await account.completeResetPassword(
|
const accountResetData = await account.completeResetPassword(
|
||||||
keyStretchExperiment.queryParamModel.isV2(config),
|
keyStretchExperiment.queryParamModel.isV2(config),
|
||||||
|
|
|
@ -27,7 +27,7 @@ jest.mock('../../../lib/metrics', () => ({
|
||||||
jest.mock('../../../lib/glean', () => ({
|
jest.mock('../../../lib/glean', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default: {
|
default: {
|
||||||
resetPassword: {
|
passwordReset: {
|
||||||
recoveryKeyResetSuccessView: jest.fn(),
|
recoveryKeyResetSuccessView: jest.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -135,7 +135,7 @@ describe('ResetPasswordWithRecoveryKeyVerified', () => {
|
||||||
REACT_ENTRYPOINT
|
REACT_ENTRYPOINT
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
GleanMetrics.resetPassword.recoveryKeyResetSuccessView
|
GleanMetrics.passwordReset.recoveryKeyResetSuccessView
|
||||||
).toHaveBeenCalled();
|
).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,7 +26,7 @@ const ResetPasswordWithRecoveryKeyVerified = ({
|
||||||
integration,
|
integration,
|
||||||
}: ResetPasswordWithRecoveryKeyVerifiedProps & RouteComponentProps) => {
|
}: ResetPasswordWithRecoveryKeyVerifiedProps & RouteComponentProps) => {
|
||||||
usePageViewEvent(viewName, REACT_ENTRYPOINT);
|
usePageViewEvent(viewName, REACT_ENTRYPOINT);
|
||||||
GleanMetrics.resetPassword.recoveryKeyResetSuccessView();
|
GleanMetrics.passwordReset.recoveryKeyResetSuccessView();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ jest.mock('@reach/router', () => ({
|
||||||
|
|
||||||
jest.mock('../../lib/glean', () => ({
|
jest.mock('../../lib/glean', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default: { resetPassword: { view: jest.fn(), submit: jest.fn() } },
|
default: { passwordReset: { view: jest.fn(), submit: jest.fn() } },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const route = '/reset_password';
|
const route = '/reset_password';
|
||||||
|
@ -85,8 +85,8 @@ describe('PageResetPassword', () => {
|
||||||
// });
|
// });
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(GleanMetrics.resetPassword.view as jest.Mock).mockClear();
|
(GleanMetrics.passwordReset.view as jest.Mock).mockClear();
|
||||||
(GleanMetrics.resetPassword.submit as jest.Mock).mockClear();
|
(GleanMetrics.passwordReset.submit as jest.Mock).mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders as expected', async () => {
|
it('renders as expected', async () => {
|
||||||
|
@ -127,7 +127,7 @@ describe('PageResetPassword', () => {
|
||||||
render(<ResetPasswordWithWebIntegration />);
|
render(<ResetPasswordWithWebIntegration />);
|
||||||
await screen.findByText('Reset password');
|
await screen.findByText('Reset password');
|
||||||
expect(usePageViewEvent).toHaveBeenCalledWith(viewName, REACT_ENTRYPOINT);
|
expect(usePageViewEvent).toHaveBeenCalledWith(viewName, REACT_ENTRYPOINT);
|
||||||
expect(GleanMetrics.resetPassword.view).toHaveBeenCalledTimes(1);
|
expect(GleanMetrics.passwordReset.view).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('submit success with OAuth integration', async () => {
|
it('submit success with OAuth integration', async () => {
|
||||||
|
@ -155,7 +155,7 @@ describe('PageResetPassword', () => {
|
||||||
fireEvent.click(await screen.findByText('Begin reset'));
|
fireEvent.click(await screen.findByText('Begin reset'));
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(GleanMetrics.resetPassword.submit).toHaveBeenCalledTimes(1);
|
expect(GleanMetrics.passwordReset.submit).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
expect(account.resetPassword).toHaveBeenCalled();
|
expect(account.resetPassword).toHaveBeenCalled();
|
||||||
|
|
||||||
|
@ -295,7 +295,7 @@ describe('PageResetPassword', () => {
|
||||||
fireEvent.click(screen.getByRole('button', { name: 'Begin reset' }));
|
fireEvent.click(screen.getByRole('button', { name: 'Begin reset' }));
|
||||||
await screen.findByText('Unknown account');
|
await screen.findByText('Unknown account');
|
||||||
|
|
||||||
expect(GleanMetrics.resetPassword.view).toHaveBeenCalledTimes(1);
|
expect(GleanMetrics.passwordReset.view).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays an error when rate limiting kicks in', async () => {
|
it('displays an error when rate limiting kicks in', async () => {
|
||||||
|
@ -333,7 +333,7 @@ describe('PageResetPassword', () => {
|
||||||
'You’ve tried too many times. Please try again in 15 minutes.'
|
'You’ve tried too many times. Please try again in 15 minutes.'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(GleanMetrics.resetPassword.view).toHaveBeenCalledTimes(1);
|
expect(GleanMetrics.passwordReset.view).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles unexpected errors on submit', async () => {
|
it('handles unexpected errors on submit', async () => {
|
||||||
|
|
|
@ -58,7 +58,7 @@ const ResetPassword = ({
|
||||||
const serviceName = integration.getServiceName();
|
const serviceName = integration.getServiceName();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
GleanMetrics.resetPassword.view();
|
GleanMetrics.passwordReset.view();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { control, getValues, handleSubmit, register } =
|
const { control, getValues, handleSubmit, register } =
|
||||||
|
@ -154,7 +154,7 @@ const ResetPassword = ({
|
||||||
ftlMsgResolver.getMsg('auth-error-1011', 'Valid email required')
|
ftlMsgResolver.getMsg('auth-error-1011', 'Valid email required')
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
GleanMetrics.resetPassword.submit();
|
GleanMetrics.passwordReset.submit();
|
||||||
submitEmail(sanitizedEmail, {
|
submitEmail(sanitizedEmail, {
|
||||||
metricsContext: queryParamsToMetricsContext(
|
metricsContext: queryParamsToMetricsContext(
|
||||||
flowQueryParams as unknown as Record<string, string>
|
flowQueryParams as unknown as Record<string, string>
|
||||||
|
|
|
@ -16,7 +16,7 @@ const mockVerifyRecoveryKey = jest.fn((_recoveryKey: string) =>
|
||||||
);
|
);
|
||||||
|
|
||||||
jest.mock('../../../lib/glean', () => ({
|
jest.mock('../../../lib/glean', () => ({
|
||||||
resetPassword: {
|
passwordReset: {
|
||||||
recoveryKeyView: jest.fn(),
|
recoveryKeyView: jest.fn(),
|
||||||
recoveryKeySubmit: jest.fn(),
|
recoveryKeySubmit: jest.fn(),
|
||||||
},
|
},
|
||||||
|
@ -24,8 +24,8 @@ jest.mock('../../../lib/glean', () => ({
|
||||||
|
|
||||||
describe('AccountRecoveryConfirmKey', () => {
|
describe('AccountRecoveryConfirmKey', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(GleanMetrics.resetPassword.recoveryKeyView as jest.Mock).mockReset();
|
(GleanMetrics.passwordReset.recoveryKeyView as jest.Mock).mockReset();
|
||||||
(GleanMetrics.resetPassword.recoveryKeySubmit as jest.Mock).mockReset();
|
(GleanMetrics.passwordReset.recoveryKeySubmit as jest.Mock).mockReset();
|
||||||
mockVerifyRecoveryKey.mockClear();
|
mockVerifyRecoveryKey.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ describe('AccountRecoveryConfirmKey', () => {
|
||||||
|
|
||||||
expect(mockVerifyRecoveryKey).toHaveBeenCalled();
|
expect(mockVerifyRecoveryKey).toHaveBeenCalled();
|
||||||
expect(
|
expect(
|
||||||
GleanMetrics.resetPassword.recoveryKeySubmit
|
GleanMetrics.passwordReset.recoveryKeySubmit
|
||||||
).toHaveBeenCalledTimes(1);
|
).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ const AccountRecoveryConfirmKey = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
GleanMetrics.resetPassword.recoveryKeyView();
|
GleanMetrics.passwordReset.recoveryKeyView();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
|
@ -64,7 +64,7 @@ const AccountRecoveryConfirmKey = ({
|
||||||
|
|
||||||
if (recoveryKey.length === 32 && isBase32Crockford(recoveryKey)) {
|
if (recoveryKey.length === 32 && isBase32Crockford(recoveryKey)) {
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
GleanMetrics.resetPassword.recoveryKeySubmit();
|
GleanMetrics.passwordReset.recoveryKeySubmit();
|
||||||
verifyRecoveryKey(recoveryKey);
|
verifyRecoveryKey(recoveryKey);
|
||||||
} else {
|
} else {
|
||||||
// if the submitted key does not match the expected format,
|
// if the submitted key does not match the expected format,
|
||||||
|
@ -157,6 +157,7 @@ const AccountRecoveryConfirmKey = ({
|
||||||
token,
|
token,
|
||||||
uid,
|
uid,
|
||||||
}}
|
}}
|
||||||
|
onClick={() => GleanMetrics.passwordReset.recoveryKeyCannotFind()}
|
||||||
>
|
>
|
||||||
Don’t have an account recovery key?
|
Don’t have an account recovery key?
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -141,7 +141,7 @@ const CompleteResetPasswordContainer = ({
|
||||||
const emailToUse = emailToHashWith || email;
|
const emailToUse = emailToHashWith || email;
|
||||||
|
|
||||||
if (hasConfirmedRecoveryKey) {
|
if (hasConfirmedRecoveryKey) {
|
||||||
GleanMetrics.resetPassword.recoveryKeyCreatePasswordSubmit();
|
GleanMetrics.passwordReset.recoveryKeyCreatePasswordSubmit();
|
||||||
const accountResetData = await resetPasswordWithRecoveryKey(
|
const accountResetData = await resetPasswordWithRecoveryKey(
|
||||||
accountResetToken,
|
accountResetToken,
|
||||||
emailToUse,
|
emailToUse,
|
||||||
|
@ -153,7 +153,7 @@ const CompleteResetPasswordContainer = ({
|
||||||
notifyBrowserOfSignin(accountResetData);
|
notifyBrowserOfSignin(accountResetData);
|
||||||
handleNavigationWithRecoveryKey();
|
handleNavigationWithRecoveryKey();
|
||||||
} else if (isResetWithoutRecoveryKey) {
|
} else if (isResetWithoutRecoveryKey) {
|
||||||
GleanMetrics.resetPassword.createNewSubmit();
|
GleanMetrics.passwordReset.createNewSubmit();
|
||||||
const accountResetData = await resetPasswordWithoutRecoveryKey(
|
const accountResetData = await resetPasswordWithoutRecoveryKey(
|
||||||
code,
|
code,
|
||||||
emailToUse,
|
emailToUse,
|
||||||
|
|
|
@ -16,7 +16,7 @@ const mockSubmitNewPassword = jest.fn((newPassword: string) =>
|
||||||
jest.mock('../../../lib/glean', () => ({
|
jest.mock('../../../lib/glean', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default: {
|
default: {
|
||||||
resetPassword: {
|
passwordReset: {
|
||||||
createNewView: jest.fn(),
|
createNewView: jest.fn(),
|
||||||
recoveryKeyCreatePasswordView: jest.fn(),
|
recoveryKeyCreatePasswordView: jest.fn(),
|
||||||
},
|
},
|
||||||
|
@ -25,9 +25,9 @@ jest.mock('../../../lib/glean', () => ({
|
||||||
|
|
||||||
describe('CompleteResetPassword page', () => {
|
describe('CompleteResetPassword page', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(GleanMetrics.resetPassword.createNewView as jest.Mock).mockClear();
|
(GleanMetrics.passwordReset.createNewView as jest.Mock).mockClear();
|
||||||
(
|
(
|
||||||
GleanMetrics.resetPassword.recoveryKeyCreatePasswordView as jest.Mock
|
GleanMetrics.passwordReset.recoveryKeyCreatePasswordView as jest.Mock
|
||||||
).mockClear();
|
).mockClear();
|
||||||
mockSubmitNewPassword.mockClear();
|
mockSubmitNewPassword.mockClear();
|
||||||
});
|
});
|
||||||
|
@ -72,7 +72,7 @@ describe('CompleteResetPassword page', () => {
|
||||||
|
|
||||||
it('sends the expected metrics on render', () => {
|
it('sends the expected metrics on render', () => {
|
||||||
renderWithLocalizationProvider(<Subject />);
|
renderWithLocalizationProvider(<Subject />);
|
||||||
expect(GleanMetrics.resetPassword.createNewView).toHaveBeenCalledTimes(1);
|
expect(GleanMetrics.passwordReset.createNewView).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ describe('CompleteResetPassword page', () => {
|
||||||
it('sends the expected metrics on render', () => {
|
it('sends the expected metrics on render', () => {
|
||||||
renderWithLocalizationProvider(<Subject hasConfirmedRecoveryKey />);
|
renderWithLocalizationProvider(<Subject hasConfirmedRecoveryKey />);
|
||||||
expect(
|
expect(
|
||||||
GleanMetrics.resetPassword.recoveryKeyCreatePasswordView
|
GleanMetrics.passwordReset.recoveryKeyCreatePasswordView
|
||||||
).toHaveBeenCalledTimes(1);
|
).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,8 +31,8 @@ const CompleteResetPassword = ({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
hasConfirmedRecoveryKey
|
hasConfirmedRecoveryKey
|
||||||
? GleanMetrics.resetPassword.recoveryKeyCreatePasswordView()
|
? GleanMetrics.passwordReset.recoveryKeyCreatePasswordView()
|
||||||
: GleanMetrics.resetPassword.createNewView();
|
: GleanMetrics.passwordReset.createNewView();
|
||||||
}, [hasConfirmedRecoveryKey]);
|
}, [hasConfirmedRecoveryKey]);
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
@ -76,6 +76,9 @@ const CompleteResetPassword = ({
|
||||||
to={`/account_recovery_confirm_key${location.search}`}
|
to={`/account_recovery_confirm_key${location.search}`}
|
||||||
state={locationState}
|
state={locationState}
|
||||||
className="link-white underline-offset-4"
|
className="link-white underline-offset-4"
|
||||||
|
onClick={() =>
|
||||||
|
GleanMetrics.passwordReset.createNewClickRecoveryKeyMessage()
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Reset your password with your account recovery key.
|
Reset your password with your account recovery key.
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
import { ResendStatus } from '../../../lib/types';
|
import { ResendStatus } from '../../../lib/types';
|
||||||
import { useNavigateWithQuery } from '../../../lib/hooks/useNavigateWithQuery';
|
import { useNavigateWithQuery } from '../../../lib/hooks/useNavigateWithQuery';
|
||||||
import { getLocalizedErrorMessage } from '../../../lib/error-utils';
|
import { getLocalizedErrorMessage } from '../../../lib/error-utils';
|
||||||
|
import GleanMetrics from '../../../lib/glean';
|
||||||
|
|
||||||
const ConfirmResetPasswordContainer = (_: RouteComponentProps) => {
|
const ConfirmResetPasswordContainer = (_: RouteComponentProps) => {
|
||||||
const [resendStatus, setResendStatus] = useState<ResendStatus>(
|
const [resendStatus, setResendStatus] = useState<ResendStatus>(
|
||||||
|
@ -91,6 +92,7 @@ const ConfirmResetPasswordContainer = (_: RouteComponentProps) => {
|
||||||
setResendStatus(ResendStatus['not sent']);
|
setResendStatus(ResendStatus['not sent']);
|
||||||
const options = { metricsContext };
|
const options = { metricsContext };
|
||||||
try {
|
try {
|
||||||
|
GleanMetrics.passwordReset.emailConfirmationSubmit();
|
||||||
const { code, emailToHashWith, token, uid } =
|
const { code, emailToHashWith, token, uid } =
|
||||||
await authClient.passwordForgotVerifyOtp(email, otpCode, options);
|
await authClient.passwordForgotVerifyOtp(email, otpCode, options);
|
||||||
const { exists: recoveryKeyExists, estimatedSyncDeviceCount } =
|
const { exists: recoveryKeyExists, estimatedSyncDeviceCount } =
|
||||||
|
|
|
@ -7,19 +7,52 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Subject } from './mocks';
|
import { Subject } from './mocks';
|
||||||
import { renderWithLocalizationProvider } from 'fxa-react/lib/test-utils/localizationProvider';
|
import { renderWithLocalizationProvider } from 'fxa-react/lib/test-utils/localizationProvider';
|
||||||
|
import * as utils from 'fxa-react/lib/utils';
|
||||||
import { screen, waitFor } from '@testing-library/react';
|
import { screen, waitFor } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { MOCK_EMAIL } from '../../mocks';
|
import { MOCK_EMAIL } from '../../mocks';
|
||||||
|
import GleanMetrics from '../../../lib/glean';
|
||||||
|
|
||||||
// add Glean mocks
|
jest.mock('../../../lib/glean', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: {
|
||||||
|
passwordReset: {
|
||||||
|
emailConfirmationView: jest.fn(),
|
||||||
|
emailConfirmationResendCode: jest.fn(),
|
||||||
|
emailConfirmationDifferentAccount: jest.fn(),
|
||||||
|
emailConfirmationSignin: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('fxa-react/lib/utils', () => ({
|
||||||
|
...jest.requireActual('fxa-react/lib/utils'),
|
||||||
|
hardNavigate: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
const mockResendCode = jest.fn(() => Promise.resolve(true));
|
const mockResendCode = jest.fn(() => Promise.resolve(true));
|
||||||
const mockVerifyCode = jest.fn((code: string) => Promise.resolve());
|
const mockVerifyCode = jest.fn((code: string) => Promise.resolve());
|
||||||
|
|
||||||
describe('ConfirmResetPassword', () => {
|
describe('ConfirmResetPassword', () => {
|
||||||
|
let locationAssignSpy: jest.Mock;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockResendCode.mockClear();
|
jest.clearAllMocks();
|
||||||
mockVerifyCode.mockClear();
|
|
||||||
|
locationAssignSpy = jest.fn();
|
||||||
|
|
||||||
|
Object.defineProperty(window, 'location', {
|
||||||
|
value: {
|
||||||
|
// mock content server url for URL constructor
|
||||||
|
origin: 'http://localhost:3030',
|
||||||
|
assign: locationAssignSpy,
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
locationAssignSpy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders as expected', async () => {
|
it('renders as expected', async () => {
|
||||||
|
@ -45,6 +78,18 @@ describe('ConfirmResetPassword', () => {
|
||||||
expect(links[2]).toHaveTextContent('Use a different account');
|
expect(links[2]).toHaveTextContent('Use a different account');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('emits the expected metrics event on render', async () => {
|
||||||
|
renderWithLocalizationProvider(<Subject />);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
screen.getByRole('heading', { name: 'Check your email' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
GleanMetrics.passwordReset.emailConfirmationView
|
||||||
|
).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('submits with valid code', async () => {
|
it('submits with valid code', async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
renderWithLocalizationProvider(<Subject verifyCode={mockVerifyCode} />);
|
renderWithLocalizationProvider(<Subject verifyCode={mockVerifyCode} />);
|
||||||
|
@ -66,6 +111,20 @@ describe('ConfirmResetPassword', () => {
|
||||||
expect(mockVerifyCode).toHaveBeenCalledWith('12345678');
|
expect(mockVerifyCode).toHaveBeenCalledWith('12345678');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles click on signin', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
renderWithLocalizationProvider(<Subject />);
|
||||||
|
|
||||||
|
const signinLink = screen.getByRole('link', {
|
||||||
|
name: 'Sign in',
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => user.click(signinLink));
|
||||||
|
expect(
|
||||||
|
GleanMetrics.passwordReset.emailConfirmationSignin
|
||||||
|
).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('handles resend code', async () => {
|
it('handles resend code', async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
renderWithLocalizationProvider(<Subject resendCode={mockResendCode} />);
|
renderWithLocalizationProvider(<Subject resendCode={mockResendCode} />);
|
||||||
|
@ -76,6 +135,9 @@ describe('ConfirmResetPassword', () => {
|
||||||
|
|
||||||
await waitFor(() => user.click(resendButton));
|
await waitFor(() => user.click(resendButton));
|
||||||
expect(mockResendCode).toHaveBeenCalledTimes(1);
|
expect(mockResendCode).toHaveBeenCalledTimes(1);
|
||||||
|
expect(
|
||||||
|
GleanMetrics.passwordReset.emailConfirmationResendCode
|
||||||
|
).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
screen.getByText(
|
screen.getByText(
|
||||||
|
@ -83,4 +145,22 @@ describe('ConfirmResetPassword', () => {
|
||||||
)
|
)
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles Use different account link', async () => {
|
||||||
|
let hardNavigateSpy: jest.SpyInstance;
|
||||||
|
hardNavigateSpy = jest
|
||||||
|
.spyOn(utils, 'hardNavigate')
|
||||||
|
.mockImplementation(() => {});
|
||||||
|
|
||||||
|
const user = userEvent.setup();
|
||||||
|
renderWithLocalizationProvider(<Subject />);
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
user.click(screen.getByRole('link', { name: /^Use a different account/ }))
|
||||||
|
);
|
||||||
|
expect(hardNavigateSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(
|
||||||
|
GleanMetrics.passwordReset.emailConfirmationDifferentAccount
|
||||||
|
).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import AppLayout from '../../../components/AppLayout';
|
import AppLayout from '../../../components/AppLayout';
|
||||||
import FormVerifyTotp from '../../../components/FormVerifyTotp';
|
import FormVerifyTotp from '../../../components/FormVerifyTotp';
|
||||||
import { ConfirmResetPasswordProps } from './interfaces';
|
import { ConfirmResetPasswordProps } from './interfaces';
|
||||||
|
@ -13,6 +13,7 @@ import { FtlMsg, hardNavigate } from 'fxa-react/lib/utils';
|
||||||
import { ResendEmailSuccessBanner } from '../../../components/Banner';
|
import { ResendEmailSuccessBanner } from '../../../components/Banner';
|
||||||
import { ResendStatus } from '../../../lib/types';
|
import { ResendStatus } from '../../../lib/types';
|
||||||
import { EmailCodeImage } from '../../../components/images';
|
import { EmailCodeImage } from '../../../components/images';
|
||||||
|
import GleanMetrics from '../../../lib/glean';
|
||||||
|
|
||||||
const ConfirmResetPassword = ({
|
const ConfirmResetPassword = ({
|
||||||
email,
|
email,
|
||||||
|
@ -23,6 +24,10 @@ const ConfirmResetPassword = ({
|
||||||
setResendStatus,
|
setResendStatus,
|
||||||
verifyCode,
|
verifyCode,
|
||||||
}: ConfirmResetPasswordProps & RouteComponentProps) => {
|
}: ConfirmResetPasswordProps & RouteComponentProps) => {
|
||||||
|
useEffect(() => {
|
||||||
|
GleanMetrics.passwordReset.emailConfirmationView();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const ftlMsgResolver = useFtlMsgResolver();
|
const ftlMsgResolver = useFtlMsgResolver();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
|
@ -39,12 +44,17 @@ const ConfirmResetPassword = ({
|
||||||
|
|
||||||
const handleResend = async () => {
|
const handleResend = async () => {
|
||||||
setResendStatus(ResendStatus['not sent']);
|
setResendStatus(ResendStatus['not sent']);
|
||||||
|
GleanMetrics.passwordReset.emailConfirmationResendCode();
|
||||||
const result = await resendCode();
|
const result = await resendCode();
|
||||||
if (result === true) {
|
if (result === true) {
|
||||||
setResendStatus(ResendStatus.sent);
|
setResendStatus(ResendStatus.sent);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const signinClickHandler = () => {
|
||||||
|
GleanMetrics.passwordReset.emailConfirmationSignin();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout>
|
<AppLayout>
|
||||||
<FtlMsg id="password-reset-flow-heading">
|
<FtlMsg id="password-reset-flow-heading">
|
||||||
|
@ -74,7 +84,7 @@ const ConfirmResetPassword = ({
|
||||||
verifyCode,
|
verifyCode,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LinkRememberPassword {...{ email }} />
|
<LinkRememberPassword {...{ email }} clickHandler={signinClickHandler} />
|
||||||
<div className="flex justify-between mt-8 text-sm">
|
<div className="flex justify-between mt-8 text-sm">
|
||||||
<FtlMsg id="confirm-reset-password-otp-resend-code-button">
|
<FtlMsg id="confirm-reset-password-otp-resend-code-button">
|
||||||
<button type="button" className="link-blue" onClick={handleResend}>
|
<button type="button" className="link-blue" onClick={handleResend}>
|
||||||
|
@ -87,6 +97,7 @@ const ConfirmResetPassword = ({
|
||||||
className="text-sm link-blue"
|
className="text-sm link-blue"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
GleanMetrics.passwordReset.emailConfirmationDifferentAccount();
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
// Tell content-server to stay on index and prefill the email
|
// Tell content-server to stay on index and prefill the email
|
||||||
params.set('prefillEmail', email);
|
params.set('prefillEmail', email);
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { MOCK_EMAIL } from '../../mocks';
|
||||||
|
|
||||||
jest.mock('../../../lib/glean', () => ({
|
jest.mock('../../../lib/glean', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default: { resetPassword: { view: jest.fn(), submit: jest.fn() } },
|
default: { passwordReset: { view: jest.fn(), submit: jest.fn() } },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const mockRequestResetPasswordCode = jest.fn((email: string) =>
|
const mockRequestResetPasswordCode = jest.fn((email: string) =>
|
||||||
|
@ -22,9 +22,7 @@ const mockRequestResetPasswordCode = jest.fn((email: string) =>
|
||||||
|
|
||||||
describe('ResetPassword', () => {
|
describe('ResetPassword', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(GleanMetrics.resetPassword.view as jest.Mock).mockClear();
|
jest.clearAllMocks();
|
||||||
(GleanMetrics.resetPassword.submit as jest.Mock).mockClear();
|
|
||||||
mockRequestResetPasswordCode.mockClear();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('renders', () => {
|
describe('renders', () => {
|
||||||
|
@ -47,7 +45,7 @@ describe('ResetPassword', () => {
|
||||||
it('emits a Glean event on render', async () => {
|
it('emits a Glean event on render', async () => {
|
||||||
renderWithLocalizationProvider(<Subject />);
|
renderWithLocalizationProvider(<Subject />);
|
||||||
await expect(screen.getByRole('heading', { level: 1 })).toBeVisible();
|
await expect(screen.getByRole('heading', { level: 1 })).toBeVisible();
|
||||||
expect(GleanMetrics.resetPassword.view).toHaveBeenCalledTimes(1);
|
expect(GleanMetrics.passwordReset.view).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -68,8 +66,8 @@ describe('ResetPassword', () => {
|
||||||
|
|
||||||
expect(mockRequestResetPasswordCode).toBeCalledWith(MOCK_EMAIL);
|
expect(mockRequestResetPasswordCode).toBeCalledWith(MOCK_EMAIL);
|
||||||
|
|
||||||
expect(GleanMetrics.resetPassword.view).toHaveBeenCalledTimes(1);
|
expect(GleanMetrics.passwordReset.view).toHaveBeenCalledTimes(1);
|
||||||
expect(GleanMetrics.resetPassword.submit).toHaveBeenCalledTimes(1);
|
expect(GleanMetrics.passwordReset.submit).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('trims leading space in email', async () => {
|
it('trims leading space in email', async () => {
|
||||||
|
@ -87,8 +85,8 @@ describe('ResetPassword', () => {
|
||||||
await waitFor(() => user.click(screen.getByRole('button')));
|
await waitFor(() => user.click(screen.getByRole('button')));
|
||||||
|
|
||||||
expect(mockRequestResetPasswordCode).toBeCalledWith(MOCK_EMAIL);
|
expect(mockRequestResetPasswordCode).toBeCalledWith(MOCK_EMAIL);
|
||||||
expect(GleanMetrics.resetPassword.view).toHaveBeenCalledTimes(1);
|
expect(GleanMetrics.passwordReset.view).toHaveBeenCalledTimes(1);
|
||||||
expect(GleanMetrics.resetPassword.submit).toHaveBeenCalledTimes(1);
|
expect(GleanMetrics.passwordReset.submit).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('handles errors', () => {
|
describe('handles errors', () => {
|
||||||
|
@ -103,8 +101,8 @@ describe('ResetPassword', () => {
|
||||||
|
|
||||||
expect(screen.getByText('Valid email required')).toBeVisible();
|
expect(screen.getByText('Valid email required')).toBeVisible();
|
||||||
expect(mockRequestResetPasswordCode).not.toBeCalled();
|
expect(mockRequestResetPasswordCode).not.toBeCalled();
|
||||||
expect(GleanMetrics.resetPassword.view).toHaveBeenCalledTimes(1);
|
expect(GleanMetrics.passwordReset.view).toHaveBeenCalledTimes(1);
|
||||||
expect(GleanMetrics.resetPassword.submit).not.toHaveBeenCalled();
|
expect(GleanMetrics.passwordReset.submit).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('with an invalid email', async () => {
|
it('with an invalid email', async () => {
|
||||||
|
@ -120,8 +118,8 @@ describe('ResetPassword', () => {
|
||||||
|
|
||||||
expect(screen.getByText('Valid email required')).toBeVisible();
|
expect(screen.getByText('Valid email required')).toBeVisible();
|
||||||
expect(mockRequestResetPasswordCode).not.toBeCalled();
|
expect(mockRequestResetPasswordCode).not.toBeCalled();
|
||||||
expect(GleanMetrics.resetPassword.view).toHaveBeenCalledTimes(1);
|
expect(GleanMetrics.passwordReset.view).toHaveBeenCalledTimes(1);
|
||||||
expect(GleanMetrics.resetPassword.submit).not.toHaveBeenCalled();
|
expect(GleanMetrics.passwordReset.submit).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,7 +32,7 @@ const ResetPassword = ({
|
||||||
const ftlMsgResolver = useFtlMsgResolver();
|
const ftlMsgResolver = useFtlMsgResolver();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
GleanMetrics.resetPassword.view();
|
GleanMetrics.passwordReset.view();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { control, getValues, handleSubmit, register } =
|
const { control, getValues, handleSubmit, register } =
|
||||||
|
@ -55,7 +55,7 @@ const ResetPassword = ({
|
||||||
ftlMsgResolver.getMsg('auth-error-1011', 'Valid email required')
|
ftlMsgResolver.getMsg('auth-error-1011', 'Valid email required')
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
GleanMetrics.resetPassword.submit();
|
GleanMetrics.passwordReset.submit();
|
||||||
await requestResetPasswordCode(email);
|
await requestResetPasswordCode(email);
|
||||||
}
|
}
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
|
|
|
@ -621,6 +621,24 @@ login:
|
||||||
data_sensitivity:
|
data_sensitivity:
|
||||||
- interaction
|
- interaction
|
||||||
password_reset:
|
password_reset:
|
||||||
|
create_new_recovery_key_message_click:
|
||||||
|
type: event
|
||||||
|
description: |
|
||||||
|
Reset Password Create New Password See Recovery Key Question Click
|
||||||
|
User clicks the button for "reset your password with your recovery key"'
|
||||||
|
send_in_pings:
|
||||||
|
- events
|
||||||
|
notification_emails:
|
||||||
|
- vzare@mozilla.com
|
||||||
|
- fxa-staff@mozilla.com
|
||||||
|
bugs:
|
||||||
|
- https://mozilla-hub.atlassian.net/browse/FXA-9529
|
||||||
|
data_reviews:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1830504
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1844121
|
||||||
|
expires: never
|
||||||
|
data_sensitivity:
|
||||||
|
- interaction
|
||||||
create_new_submit:
|
create_new_submit:
|
||||||
type: event
|
type: event
|
||||||
description: |
|
description: |
|
||||||
|
@ -675,6 +693,114 @@ password_reset:
|
||||||
expires: never
|
expires: never
|
||||||
data_sensitivity:
|
data_sensitivity:
|
||||||
- interaction
|
- interaction
|
||||||
|
email_confirmation_different_account:
|
||||||
|
type: event
|
||||||
|
description: |
|
||||||
|
Forgot Password Confirmation Code Use a different account
|
||||||
|
User clicks the "use a different account" button on the "Enter Confirmation Code" screen'
|
||||||
|
send_in_pings:
|
||||||
|
- events
|
||||||
|
notification_emails:
|
||||||
|
- vzare@mozilla.com
|
||||||
|
- fxa-staff@mozilla.com
|
||||||
|
bugs:
|
||||||
|
- https://mozilla-hub.atlassian.net/browse/FXA-9529
|
||||||
|
data_reviews:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1830504
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1844121
|
||||||
|
expires: never
|
||||||
|
data_sensitivity:
|
||||||
|
- interaction
|
||||||
|
email_confirmation_signin:
|
||||||
|
type: event
|
||||||
|
description: |
|
||||||
|
Forgot Password Confirmation Code Sign In
|
||||||
|
User clicks the "sign in" button on the "Enter Confirmation Code" screen'
|
||||||
|
send_in_pings:
|
||||||
|
- events
|
||||||
|
notification_emails:
|
||||||
|
- vzare@mozilla.com
|
||||||
|
- fxa-staff@mozilla.com
|
||||||
|
bugs:
|
||||||
|
- https://mozilla-hub.atlassian.net/browse/FXA-9529
|
||||||
|
data_reviews:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1830504
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1844121
|
||||||
|
expires: never
|
||||||
|
data_sensitivity:
|
||||||
|
- interaction
|
||||||
|
email_confirmation_submit:
|
||||||
|
type: event
|
||||||
|
description: |
|
||||||
|
Forgot Password Confirmation Code Submit
|
||||||
|
User clicks the "Continue" button on the "Enter Confirmation Code" screen'
|
||||||
|
send_in_pings:
|
||||||
|
- events
|
||||||
|
notification_emails:
|
||||||
|
- vzare@mozilla.com
|
||||||
|
- fxa-staff@mozilla.com
|
||||||
|
bugs:
|
||||||
|
- https://mozilla-hub.atlassian.net/browse/FXA-9529
|
||||||
|
data_reviews:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1830504
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1844121
|
||||||
|
expires: never
|
||||||
|
data_sensitivity:
|
||||||
|
- interaction
|
||||||
|
email_confirmation_view:
|
||||||
|
type: event
|
||||||
|
description: |
|
||||||
|
Forgot Password Confirmation Code View
|
||||||
|
User views the "Enter Confirmation Code" screen'
|
||||||
|
send_in_pings:
|
||||||
|
- events
|
||||||
|
notification_emails:
|
||||||
|
- vzare@mozilla.com
|
||||||
|
- fxa-staff@mozilla.com
|
||||||
|
bugs:
|
||||||
|
- https://mozilla-hub.atlassian.net/browse/FXA-9529
|
||||||
|
data_reviews:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1830504
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1844121
|
||||||
|
expires: never
|
||||||
|
data_sensitivity:
|
||||||
|
- interaction
|
||||||
|
email_confirmation_resend_code:
|
||||||
|
type: event
|
||||||
|
description: |
|
||||||
|
Forgot Password Confirmation Code Resend
|
||||||
|
User clicks the "resend code" button on the "Enter Confirmation Code" screen'
|
||||||
|
send_in_pings:
|
||||||
|
- events
|
||||||
|
notification_emails:
|
||||||
|
- vzare@mozilla.com
|
||||||
|
- fxa-staff@mozilla.com
|
||||||
|
bugs:
|
||||||
|
- https://mozilla-hub.atlassian.net/browse/FXA-9529
|
||||||
|
data_reviews:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1830504
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1844121
|
||||||
|
expires: never
|
||||||
|
data_sensitivity:
|
||||||
|
- interaction
|
||||||
|
recovery_key_cannot_find:
|
||||||
|
type: event
|
||||||
|
description: |
|
||||||
|
Reset Password Can't Find Key
|
||||||
|
User clicks the "Can't find your account recovery key?" button on the confirm reccovery key page'
|
||||||
|
send_in_pings:
|
||||||
|
- events
|
||||||
|
notification_emails:
|
||||||
|
- vzare@mozilla.com
|
||||||
|
- fxa-staff@mozilla.com
|
||||||
|
bugs:
|
||||||
|
- https://mozilla-hub.atlassian.net/browse/FXA-9529
|
||||||
|
data_reviews:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1830504
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1844121
|
||||||
|
expires: never
|
||||||
|
data_sensitivity:
|
||||||
|
- interaction
|
||||||
recovery_key_create_new_submit:
|
recovery_key_create_new_submit:
|
||||||
type: event
|
type: event
|
||||||
description: |
|
description: |
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
// AUTOGENERATED BY glean_parser v14.0.1. DO NOT EDIT. DO NOT COMMIT.
|
||||||
|
|
||||||
|
import EventMetricType from '@mozilla/glean/private/metrics/event';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User engaged on the "Connect another device" screen with choice options,
|
||||||
|
* selecting either of "I already have FF for mobile" or "I don't have FF for
|
||||||
|
* mobile", which is provided in the 'reason' for this event
|
||||||
|
*
|
||||||
|
* Generated from `cad_firefox.choice_engage`.
|
||||||
|
*/
|
||||||
|
export const choiceEngage = new EventMetricType<{
|
||||||
|
reason?: string;
|
||||||
|
}>(
|
||||||
|
{
|
||||||
|
category: 'cad_firefox',
|
||||||
|
name: 'choice_engage',
|
||||||
|
sendInPings: ['events'],
|
||||||
|
lifetime: 'ping',
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
['reason']
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User submitted on the "Connect another device" screen with choice options,
|
||||||
|
* submitting either of "I already have FF for mobile" or "I don't have FF for
|
||||||
|
* mobile", which is provided in the 'reason' for this event
|
||||||
|
*
|
||||||
|
* Generated from `cad_firefox.choice_submit`.
|
||||||
|
*/
|
||||||
|
export const choiceSubmit = new EventMetricType<{
|
||||||
|
reason?: string;
|
||||||
|
}>(
|
||||||
|
{
|
||||||
|
category: 'cad_firefox',
|
||||||
|
name: 'choice_submit',
|
||||||
|
sendInPings: ['events'],
|
||||||
|
lifetime: 'ping',
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
['reason']
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User viewed the "Connect another device" screen with choice options to download
|
||||||
|
* FF for mobile or not
|
||||||
|
*
|
||||||
|
* Generated from `cad_firefox.choice_view`.
|
||||||
|
*/
|
||||||
|
export const choiceView = new EventMetricType(
|
||||||
|
{
|
||||||
|
category: 'cad_firefox',
|
||||||
|
name: 'choice_view',
|
||||||
|
sendInPings: ['events'],
|
||||||
|
lifetime: 'ping',
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User clicked the "Continue to sync" button on the "Download Firefox for mobile"
|
||||||
|
* screen
|
||||||
|
*
|
||||||
|
* Generated from `cad_firefox.sync_device_submit`.
|
||||||
|
*/
|
||||||
|
export const syncDeviceSubmit = new EventMetricType(
|
||||||
|
{
|
||||||
|
category: 'cad_firefox',
|
||||||
|
name: 'sync_device_submit',
|
||||||
|
sendInPings: ['events'],
|
||||||
|
lifetime: 'ping',
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User viewed the "Download Firefox for mobile" screen after choosing and
|
||||||
|
* submitting the "I don't have Firefox for mobile" option
|
||||||
|
*
|
||||||
|
* Generated from `cad_firefox.view`.
|
||||||
|
*/
|
||||||
|
export const view = new EventMetricType(
|
||||||
|
{
|
||||||
|
category: 'cad_firefox',
|
||||||
|
name: 'view',
|
||||||
|
sendInPings: ['events'],
|
||||||
|
lifetime: 'ping',
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
|
@ -80,14 +80,27 @@ export const eventsMap = {
|
||||||
success: 'login_totp_code_success_view',
|
success: 'login_totp_code_success_view',
|
||||||
},
|
},
|
||||||
|
|
||||||
resetPassword: {
|
passwordReset: {
|
||||||
view: 'password_reset_view',
|
view: 'password_reset_view',
|
||||||
submit: 'password_reset_submit',
|
submit: 'password_reset_submit',
|
||||||
|
|
||||||
createNewView: 'password_reset_create_new_view',
|
createNewView: 'password_reset_create_new_view',
|
||||||
createNewSubmit: 'password_reset_create_new_submit',
|
createNewSubmit: 'password_reset_create_new_submit',
|
||||||
createNewSuccess: 'password_reset_create_new_success_view',
|
createNewSuccess: 'password_reset_create_new_success_view',
|
||||||
|
createNewClickRecoveryKeyMessage:
|
||||||
|
'password_reset_create_new_recovery_key_message_click',
|
||||||
|
|
||||||
|
emailConfirmationView: 'password_reset_email_confirmation_view',
|
||||||
|
emailConfirmationSubmit: 'password_reset_email_confirmation_submit',
|
||||||
|
emailConfirmationDifferentAccount:
|
||||||
|
'password_reset_email_confirmation_different_account',
|
||||||
|
emailConfirmationSignin: 'password_reset_email_confirmation_signin',
|
||||||
|
emailConfirmationResendCode:
|
||||||
|
'password_reset_email_confirmation_resend_code',
|
||||||
|
|
||||||
recoveryKeyView: 'password_reset_recovery_key_view',
|
recoveryKeyView: 'password_reset_recovery_key_view',
|
||||||
recoveryKeySubmit: 'password_reset_recovery_key_submit',
|
recoveryKeySubmit: 'password_reset_recovery_key_submit',
|
||||||
|
recoveryKeyCannotFind: 'password_reset_recovery_key_cannot_find',
|
||||||
|
|
||||||
recoveryKeyCreatePasswordView:
|
recoveryKeyCreatePasswordView:
|
||||||
'password_reset_recovery_key_create_new_view',
|
'password_reset_recovery_key_create_new_view',
|
||||||
|
|
|
@ -6,6 +6,23 @@
|
||||||
|
|
||||||
import EventMetricType from '@mozilla/glean/private/metrics/event';
|
import EventMetricType from '@mozilla/glean/private/metrics/event';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset Password Create New Password See Recovery Key Question Click
|
||||||
|
* User clicks the button for "reset your password with your recovery key"'
|
||||||
|
*
|
||||||
|
* Generated from `password_reset.create_new_recovery_key_message_click`.
|
||||||
|
*/
|
||||||
|
export const createNewRecoveryKeyMessageClick = new EventMetricType(
|
||||||
|
{
|
||||||
|
category: 'password_reset',
|
||||||
|
name: 'create_new_recovery_key_message_click',
|
||||||
|
sendInPings: ['events'],
|
||||||
|
lifetime: 'ping',
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create New Password Submit
|
* Create New Password Submit
|
||||||
* User attemps to submit the create new password form'
|
* User attemps to submit the create new password form'
|
||||||
|
@ -57,6 +74,110 @@ export const createNewView = new EventMetricType(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forgot Password Confirmation Code Use a different account
|
||||||
|
* User clicks the "use a different account" button on the "Enter Confirmation
|
||||||
|
* Code" screen'
|
||||||
|
*
|
||||||
|
* Generated from `password_reset.email_confirmation_different_account`.
|
||||||
|
*/
|
||||||
|
export const emailConfirmationDifferentAccount = new EventMetricType(
|
||||||
|
{
|
||||||
|
category: 'password_reset',
|
||||||
|
name: 'email_confirmation_different_account',
|
||||||
|
sendInPings: ['events'],
|
||||||
|
lifetime: 'ping',
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forgot Password Confirmation Code Resend
|
||||||
|
* User clicks the "resend code" button on the "Enter Confirmation Code" screen'
|
||||||
|
*
|
||||||
|
* Generated from `password_reset.email_confirmation_resend_code`.
|
||||||
|
*/
|
||||||
|
export const emailConfirmationResendCode = new EventMetricType(
|
||||||
|
{
|
||||||
|
category: 'password_reset',
|
||||||
|
name: 'email_confirmation_resend_code',
|
||||||
|
sendInPings: ['events'],
|
||||||
|
lifetime: 'ping',
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forgot Password Confirmation Code Sign In
|
||||||
|
* User clicks the "sign in" button on the "Enter Confirmation Code" screen'
|
||||||
|
*
|
||||||
|
* Generated from `password_reset.email_confirmation_signin`.
|
||||||
|
*/
|
||||||
|
export const emailConfirmationSignin = new EventMetricType(
|
||||||
|
{
|
||||||
|
category: 'password_reset',
|
||||||
|
name: 'email_confirmation_signin',
|
||||||
|
sendInPings: ['events'],
|
||||||
|
lifetime: 'ping',
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forgot Password Confirmation Code Submit
|
||||||
|
* User clicks the "Continue" button on the "Enter Confirmation Code" screen'
|
||||||
|
*
|
||||||
|
* Generated from `password_reset.email_confirmation_submit`.
|
||||||
|
*/
|
||||||
|
export const emailConfirmationSubmit = new EventMetricType(
|
||||||
|
{
|
||||||
|
category: 'password_reset',
|
||||||
|
name: 'email_confirmation_submit',
|
||||||
|
sendInPings: ['events'],
|
||||||
|
lifetime: 'ping',
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forgot Password Confirmation Code View
|
||||||
|
* User views the "Enter Confirmation Code" screen'
|
||||||
|
*
|
||||||
|
* Generated from `password_reset.email_confirmation_view`.
|
||||||
|
*/
|
||||||
|
export const emailConfirmationView = new EventMetricType(
|
||||||
|
{
|
||||||
|
category: 'password_reset',
|
||||||
|
name: 'email_confirmation_view',
|
||||||
|
sendInPings: ['events'],
|
||||||
|
lifetime: 'ping',
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset Password Can't Find Key
|
||||||
|
* User clicks the "Can't find your account recovery key?" button on the confirm
|
||||||
|
* reccovery key page'
|
||||||
|
*
|
||||||
|
* Generated from `password_reset.recovery_key_cannot_find`.
|
||||||
|
*/
|
||||||
|
export const recoveryKeyCannotFind = new EventMetricType(
|
||||||
|
{
|
||||||
|
category: 'password_reset',
|
||||||
|
name: 'recovery_key_cannot_find',
|
||||||
|
sendInPings: ['events'],
|
||||||
|
lifetime: 'ping',
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forgot Password w/ Recovery Key Create New Password Submit
|
* Forgot Password w/ Recovery Key Create New Password Submit
|
||||||
* User attempts to submit the create new password form'
|
* User attempts to submit the create new password form'
|
||||||
|
|
Загрузка…
Ссылка в новой задаче