зеркало из https://github.com/mozilla/fxa.git
feat(glean): Enable automatic click events, add 3x GetDataTrio events
Because: * We want to add automatic Glean click events so we don't have to edit the YAML and generate files for every click interaction * We want events for copy, download, and print actions for 2FA recovery codes This commit: * Adds the click event from the Glean API to be callable from our custom GleanMetrics object, sets up the global event listener once application state has settled * Adds click events with data-glean-id and data-glean-type for all states (setup, change, inline setup) of 2FA GetDataTrio closes FXA-10264, closes FXA-9580, closes FXA-9581, closes FXA-9582
This commit is contained in:
Родитель
a1b2161d18
Коммит
c2e3a067f2
|
@ -353,6 +353,7 @@ export const GleanMetrics = {
|
|||
// this ensures that events are uploaded.
|
||||
maxEvents: 1,
|
||||
enableAutoPageLoadEvents: true,
|
||||
enableAutoElementClickEvents: true,
|
||||
});
|
||||
Glean.setLogPings(config.logPings);
|
||||
if (config.debugViewTag) {
|
||||
|
|
|
@ -174,6 +174,7 @@ describe('lib/glean', () => {
|
|||
serverEndpoint: mockConfig.serverEndpoint,
|
||||
maxEvents: 1,
|
||||
enableAutoPageLoadEvents: true,
|
||||
enableAutoElementClickEvents: true,
|
||||
}
|
||||
);
|
||||
sinon.assert.calledWith(logPingsStub, mockConfig.logPings);
|
||||
|
|
|
@ -7,6 +7,7 @@ import GetDataTrio, {
|
|||
DownloadContentType,
|
||||
GetDataCopySingleton,
|
||||
GetDataCopySingletonInline,
|
||||
GetDataTrioGleanData,
|
||||
} from '../GetDataTrio';
|
||||
import { Tooltip } from '../Tooltip';
|
||||
import { FtlMsg } from 'fxa-react/lib/utils';
|
||||
|
@ -30,6 +31,11 @@ export type DataBlockProps = {
|
|||
isIOS?: boolean;
|
||||
isInline?: boolean;
|
||||
email: string;
|
||||
gleanDataAttrs: {
|
||||
copy?: GetDataTrioGleanData;
|
||||
download?: GetDataTrioGleanData;
|
||||
print?: GetDataTrioGleanData;
|
||||
};
|
||||
};
|
||||
|
||||
export const DataBlock = ({
|
||||
|
@ -42,6 +48,7 @@ export const DataBlock = ({
|
|||
isIOS = false,
|
||||
isInline = false,
|
||||
email,
|
||||
gleanDataAttrs,
|
||||
}: DataBlockProps) => {
|
||||
const valueIsArray = Array.isArray(value);
|
||||
const [performedAction, setPerformedAction] = useState<actions>();
|
||||
|
@ -97,13 +104,25 @@ export const DataBlock = ({
|
|||
)}
|
||||
{isInline && (
|
||||
<GetDataCopySingletonInline
|
||||
{...{ value, onAction: actionCb, setTooltipVisible, email }}
|
||||
{...{
|
||||
value,
|
||||
onAction: actionCb,
|
||||
setTooltipVisible,
|
||||
email,
|
||||
gleanDataAttrs,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{isIOS && !isInline && (
|
||||
<GetDataCopySingleton
|
||||
{...{ value, onAction: actionCb, setTooltipVisible, email }}
|
||||
{...{
|
||||
value,
|
||||
onAction: actionCb,
|
||||
setTooltipVisible,
|
||||
email,
|
||||
gleanDataAttrs,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!isIOS && !isInline && (
|
||||
|
@ -114,6 +133,7 @@ export const DataBlock = ({
|
|||
onAction: actionCb,
|
||||
setTooltipVisible,
|
||||
email,
|
||||
gleanDataAttrs,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -10,5 +10,5 @@ export const Subject = ({
|
|||
value = 'ANMD 1S09 7Y2Y 4EES 02CW BJ6Z PYKP H69F',
|
||||
...props // overrides
|
||||
}: Partial<DataBlockProps> = {}) => (
|
||||
<DataBlock email={MOCK_EMAIL} {...{ value, ...props }} />
|
||||
<DataBlock email={MOCK_EMAIL} {...{ value, ...props }} gleanDataAttrs={{}} />
|
||||
);
|
||||
|
|
|
@ -25,6 +25,7 @@ export const Default = () => {
|
|||
value="Copy that"
|
||||
email={MOCK_EMAIL}
|
||||
{...{ setTooltipVisible }}
|
||||
gleanDataAttrs={{}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -38,6 +39,7 @@ export const SingleCopyButton = () => {
|
|||
value="Copy that"
|
||||
email={MOCK_EMAIL}
|
||||
{...{ setTooltipVisible }}
|
||||
gleanDataAttrs={{}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -51,6 +53,7 @@ export const SingleCopyButtonInline = () => {
|
|||
value="Copy that"
|
||||
email={MOCK_EMAIL}
|
||||
{...{ setTooltipVisible }}
|
||||
gleanDataAttrs={{}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -20,6 +20,7 @@ it('renders Trio as expected', () => {
|
|||
<GetDataTrio
|
||||
{...{ value, contentType, url, setTooltipVisible }}
|
||||
email={MOCK_EMAIL}
|
||||
gleanDataAttrs={{}}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByTestId('databutton-download')).toBeInTheDocument();
|
||||
|
@ -37,6 +38,7 @@ it('renders single Copy button as expected', () => {
|
|||
<GetDataTrio
|
||||
{...{ value, contentType, url, setTooltipVisible }}
|
||||
email={MOCK_EMAIL}
|
||||
gleanDataAttrs={{}}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByTestId('databutton-copy')).toBeInTheDocument();
|
||||
|
|
|
@ -23,18 +23,33 @@ const DownloadContentTypeL10nMapping: Record<DownloadContentType, string> = {
|
|||
'Firefox account recovery key': 'get-data-trio-title-firefox-recovery-key',
|
||||
};
|
||||
|
||||
export interface GetDataTrioGleanData {
|
||||
id:
|
||||
| 'two_step_auth_codes_download'
|
||||
| 'two_step_auth_codes_copy'
|
||||
| 'two_step_auth_codes_print'
|
||||
| 'account_pref_recovery_key_copy';
|
||||
type?: 'setup' | 'inline setup' | 'replace';
|
||||
}
|
||||
|
||||
export type GetDataTrioProps = {
|
||||
value: string | string[];
|
||||
contentType?: DownloadContentType;
|
||||
onAction?: (type: 'download' | 'copy' | 'print') => void;
|
||||
setTooltipVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
email: string;
|
||||
gleanDataAttrs: {
|
||||
copy?: GetDataTrioGleanData;
|
||||
download?: GetDataTrioGleanData;
|
||||
print?: GetDataTrioGleanData;
|
||||
};
|
||||
};
|
||||
|
||||
export const GetDataCopySingleton = ({
|
||||
value,
|
||||
onAction,
|
||||
setTooltipVisible,
|
||||
gleanDataAttrs,
|
||||
}: GetDataTrioProps) => {
|
||||
return (
|
||||
<FtlMsg id="get-data-trio-copy-2" attrs={{ title: true, ariaLabel: true }}>
|
||||
|
@ -50,6 +65,12 @@ export const GetDataCopySingleton = ({
|
|||
onBlur={() => setTooltipVisible(false)}
|
||||
data-testid="databutton-copy"
|
||||
className="w-12 h-12 relative inline-block text-grey-500 rounded active:text-blue-600 focus:outline focus:outline-2 focus:outline-offset-2 focus:outline-blue-500 hover:bg-grey-50"
|
||||
{...(gleanDataAttrs?.copy && {
|
||||
'data-glean-id': gleanDataAttrs.copy.id,
|
||||
})}
|
||||
{...(gleanDataAttrs?.copy?.type && {
|
||||
'data-glean-type': gleanDataAttrs.copy.type,
|
||||
})}
|
||||
>
|
||||
<CopyIcon
|
||||
aria-label="Copy"
|
||||
|
@ -66,6 +87,7 @@ export const GetDataCopySingletonInline = ({
|
|||
value,
|
||||
onAction,
|
||||
setTooltipVisible,
|
||||
gleanDataAttrs,
|
||||
}: GetDataTrioProps) => {
|
||||
return (
|
||||
<>
|
||||
|
@ -84,6 +106,12 @@ export const GetDataCopySingletonInline = ({
|
|||
}}
|
||||
onBlur={() => setTooltipVisible(false)}
|
||||
data-testid="databutton-copy"
|
||||
{...(gleanDataAttrs?.copy && {
|
||||
'data-glean-id': gleanDataAttrs.copy.id,
|
||||
})}
|
||||
{...(gleanDataAttrs?.copy?.type && {
|
||||
'data-glean-type': gleanDataAttrs.copy.type,
|
||||
})}
|
||||
className="-my-3 -me-4 p-3 rounded text-grey-500 bg-transparent border border-transparent hover:bg-grey-100 active:bg-grey-200 focus:outline focus:outline-2 focus:outline-offset-2 focus:outline-blue-500 focus:bg-grey-50"
|
||||
>
|
||||
<InlineCopyIcon
|
||||
|
@ -117,6 +145,7 @@ export const GetDataTrio = ({
|
|||
onAction,
|
||||
setTooltipVisible,
|
||||
email,
|
||||
gleanDataAttrs,
|
||||
}: GetDataTrioProps) => {
|
||||
const ftlMsgResolver = useFtlMsgResolver();
|
||||
|
||||
|
@ -161,6 +190,12 @@ export const GetDataTrio = ({
|
|||
onBlur={() => {
|
||||
setTooltipVisible(false);
|
||||
}}
|
||||
{...(gleanDataAttrs?.download && {
|
||||
'data-glean-id': gleanDataAttrs.download.id,
|
||||
})}
|
||||
{...(gleanDataAttrs?.download?.type && {
|
||||
'data-glean-type': gleanDataAttrs.download.type,
|
||||
})}
|
||||
>
|
||||
<DownloadIcon
|
||||
aria-label="Download"
|
||||
|
@ -172,7 +207,7 @@ export const GetDataTrio = ({
|
|||
</FtlMsg>
|
||||
|
||||
<GetDataCopySingleton
|
||||
{...{ onAction, value, setTooltipVisible, email }}
|
||||
{...{ onAction, value, setTooltipVisible, email, gleanDataAttrs }}
|
||||
/>
|
||||
|
||||
{/** This only opens the page that is responsible
|
||||
|
@ -193,6 +228,12 @@ export const GetDataTrio = ({
|
|||
onBlur={() => setTooltipVisible(false)}
|
||||
data-testid="databutton-print"
|
||||
className="w-12 h-12 relative inline-block text-grey-500 rounded active:text-blue-600 focus:outline focus:outline-2 focus:outline-offset-2 focus:outline-blue-500 hover:bg-grey-50"
|
||||
{...(gleanDataAttrs?.print && {
|
||||
'data-glean-id': gleanDataAttrs.print.id,
|
||||
})}
|
||||
{...(gleanDataAttrs?.print?.type && {
|
||||
'data-glean-type': gleanDataAttrs.print.type,
|
||||
})}
|
||||
>
|
||||
<PrintIcon
|
||||
aria-label="Print"
|
||||
|
|
|
@ -78,6 +78,11 @@ export const FlowRecoveryKeyDownload = ({
|
|||
}
|
||||
isInline
|
||||
{...{ email }}
|
||||
gleanDataAttrs={{
|
||||
copy: {
|
||||
id: 'account_pref_recovery_key_copy',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<div className="bg-grey-10 p-4 rounded-lg text-grey-400 text-sm">
|
||||
<FtlMsg id="flow-recovery-key-download-storage-ideas-heading-v2">
|
||||
|
|
|
@ -148,6 +148,20 @@ export const Page2faReplaceRecoveryCodes = (_: RouteComponentProps) => {
|
|||
onCopy={copyRecoveryCodes}
|
||||
contentType="Backup authentication codes"
|
||||
email={account.email}
|
||||
gleanDataAttrs={{
|
||||
download: {
|
||||
id: 'two_step_auth_codes_download',
|
||||
type: 'replace',
|
||||
},
|
||||
copy: {
|
||||
id: 'two_step_auth_codes_copy',
|
||||
type: 'replace',
|
||||
},
|
||||
print: {
|
||||
id: 'two_step_auth_codes_print',
|
||||
type: 'replace',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<LoadingSpinner />
|
||||
|
|
|
@ -330,6 +330,20 @@ export const PageTwoStepAuthentication = (_: RouteComponentProps) => {
|
|||
onCopy={copyRecoveryCodes}
|
||||
contentType="Backup authentication codes"
|
||||
email={account.email}
|
||||
gleanDataAttrs={{
|
||||
download: {
|
||||
id: 'two_step_auth_codes_download',
|
||||
type: 'setup',
|
||||
},
|
||||
copy: {
|
||||
id: 'two_step_auth_codes_copy',
|
||||
type: 'setup',
|
||||
},
|
||||
print: {
|
||||
id: 'two_step_auth_codes_print',
|
||||
type: 'setup',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -76,7 +76,8 @@ describe('lib/glean', () => {
|
|||
setUtmTermStub: SinonStub,
|
||||
setEntrypointExperimentStub: SinonStub,
|
||||
setEntrypointVariationStub: SinonStub,
|
||||
pageLoadStub: SinonStub;
|
||||
pageLoadStub: SinonStub,
|
||||
handleClickEvent: SinonStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockMetricsContext.metricsFlow = {
|
||||
|
@ -117,6 +118,10 @@ describe('lib/glean', () => {
|
|||
setEntrypointVariationStub = sandbox.stub(entrypointQuery.variation, 'set');
|
||||
submitPingStub = sandbox.stub(pings.accountsEvents, 'submit');
|
||||
pageLoadStub = sandbox.stub(GleanMetricsAPI.default, 'pageLoad');
|
||||
handleClickEvent = sandbox.stub(
|
||||
GleanMetricsAPI.default,
|
||||
'handleClickEvent'
|
||||
);
|
||||
|
||||
await testResetGlean('glean-test');
|
||||
});
|
||||
|
@ -193,6 +198,7 @@ describe('lib/glean', () => {
|
|||
channel: mockConfig.channel,
|
||||
serverEndpoint: mockConfig.serverEndpoint,
|
||||
enableAutoPageLoadEvents: true,
|
||||
enableAutoElementClickEvents: true,
|
||||
}
|
||||
);
|
||||
sinon.assert.calledWith(logPingsStub, mockConfig.logPings);
|
||||
|
@ -947,4 +953,12 @@ describe('lib/glean', () => {
|
|||
sinon.assert.calledOnce(pageLoadStub);
|
||||
});
|
||||
});
|
||||
describe('handleClickEvent', () => {
|
||||
it('resolves', async () => {
|
||||
const fakeEvent = new Event('click');
|
||||
GleanMetrics.handleClickEvent(fakeEvent);
|
||||
sinon.assert.calledOnce(handleClickEvent);
|
||||
sinon.assert.calledWith(handleClickEvent, fakeEvent);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -64,6 +64,7 @@ type GleanMetricsT = {
|
|||
getEnabled: () => boolean;
|
||||
isDone: () => Promise<void>;
|
||||
pageLoad: () => void;
|
||||
handleClickEvent(event: Event): void;
|
||||
} & {
|
||||
[k in EventMapKeys]: { [eventKey in keyof EventsMap[k]]: PingFn };
|
||||
};
|
||||
|
@ -514,7 +515,12 @@ const createEventFn =
|
|||
|
||||
export const GleanMetrics: Pick<
|
||||
GleanMetricsT,
|
||||
'initialize' | 'setEnabled' | 'getEnabled' | 'isDone' | 'pageLoad'
|
||||
| 'initialize'
|
||||
| 'setEnabled'
|
||||
| 'getEnabled'
|
||||
| 'isDone'
|
||||
| 'pageLoad'
|
||||
| 'handleClickEvent'
|
||||
> = {
|
||||
initialize: (config: GleanMetricsConfig, context: GleanMetricsContext) => {
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1859629
|
||||
|
@ -527,6 +533,7 @@ export const GleanMetrics: Pick<
|
|||
channel: config.channel,
|
||||
serverEndpoint: config.serverEndpoint,
|
||||
enableAutoPageLoadEvents: true,
|
||||
enableAutoElementClickEvents: true,
|
||||
});
|
||||
Glean.setLogPings(config.logPings);
|
||||
if (config.debugViewTag) {
|
||||
|
@ -556,6 +563,10 @@ export const GleanMetrics: Pick<
|
|||
GleanMetricsAPI.pageLoad();
|
||||
},
|
||||
|
||||
handleClickEvent(event: Event) {
|
||||
GleanMetricsAPI.handleClickEvent(event);
|
||||
},
|
||||
|
||||
/**
|
||||
* The ping calls are awaited internally for ease of use and that works in
|
||||
* most cases. But in the scenario where we want to wait for the pings to
|
||||
|
|
|
@ -202,6 +202,20 @@ const InlineRecoverySetup = ({
|
|||
onCopy={copyRecoveryCodes}
|
||||
contentType="Backup authentication codes"
|
||||
{...{ email }}
|
||||
gleanDataAttrs={{
|
||||
download: {
|
||||
id: 'two_step_auth_codes_download',
|
||||
type: 'inline setup',
|
||||
},
|
||||
copy: {
|
||||
id: 'two_step_auth_codes_copy',
|
||||
type: 'inline setup',
|
||||
},
|
||||
print: {
|
||||
id: 'two_step_auth_codes_print',
|
||||
type: 'inline setup',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<div className="flex justify-center mt-6 mb-4 mx-auto max-w-64">
|
||||
<FtlMsg id="inline-recovery-cancel-button">
|
||||
|
|
Загрузка…
Ссылка в новой задаче