1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Make event tracking idempotent using unique messageId (#16267)

This commit is contained in:
Jyoti Puri 2022-11-08 23:38:08 +05:30 committed by GitHub
parent 6f27c9065c
commit 6c0930899d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 86 additions and 33 deletions

View File

@ -32,6 +32,22 @@ const defaultCaptureException = (err) => {
}); });
}; };
// The function is used to build a unique messageId for segment messages
// It uses actionId and uniqueIdentifier from event if present
const buildUniqueMessageId = (args) => {
let messageId = '';
if (args.uniqueIdentifier) {
messageId += `${args.uniqueIdentifier}-`;
}
if (args.actionId) {
messageId += args.actionId;
}
if (messageId.length) {
return messageId;
}
return generateRandomId();
};
const exceptionsToFilter = { const exceptionsToFilter = {
[`You must pass either an "anonymousId" or a "userId".`]: true, [`You must pass either an "anonymousId" or a "userId".`]: true,
}; };
@ -231,14 +247,6 @@ export default class MetaMetricsController {
); );
} }
const existingFragment = this.getExistingEventFragment(
options.actionId,
options.uniqueIdentifier,
);
if (existingFragment) {
return existingFragment;
}
const { fragments } = this.store.getState(); const { fragments } = this.store.getState();
const id = options.uniqueIdentifier ?? uuidv4(); const id = options.uniqueIdentifier ?? uuidv4();
@ -266,6 +274,8 @@ export default class MetaMetricsController {
value: fragment.value, value: fragment.value,
currency: fragment.currency, currency: fragment.currency,
environmentType: fragment.environmentType, environmentType: fragment.environmentType,
actionId: options.actionId,
uniqueIdentifier: options.uniqueIdentifier,
}); });
} }
@ -287,26 +297,6 @@ export default class MetaMetricsController {
return fragment; return fragment;
} }
/**
* Returns the fragment stored in memory with provided id or undefined if it
* does not exist.
*
* @param {string} actionId - actionId passed from UI
* @param {string} uniqueIdentifier - uniqueIdentifier of the event
* @returns {[MetaMetricsEventFragment]}
*/
getExistingEventFragment(actionId, uniqueIdentifier) {
const { fragments } = this.store.getState();
const existingFragment = Object.values(fragments).find(
(fragment) =>
fragment.actionId === actionId &&
fragment.uniqueIdentifier === uniqueIdentifier,
);
return existingFragment;
}
/** /**
* Updates an event fragment in state * Updates an event fragment in state
* *
@ -367,6 +357,8 @@ export default class MetaMetricsController {
value: fragment.value, value: fragment.value,
currency: fragment.currency, currency: fragment.currency,
environmentType: fragment.environmentType, environmentType: fragment.environmentType,
actionId: fragment.actionId,
uniqueIdentifier: fragment.uniqueIdentifier,
}); });
const { fragments } = this.store.getState(); const { fragments } = this.store.getState();
delete fragments[id]; delete fragments[id];
@ -453,7 +445,10 @@ export default class MetaMetricsController {
* @param {MetaMetricsPageOptions} [options] - options for handling the page * @param {MetaMetricsPageOptions} [options] - options for handling the page
* view * view
*/ */
trackPage({ name, params, environmentType, page, referrer }, options) { trackPage(
{ name, params, environmentType, page, referrer, actionId },
options,
) {
try { try {
if (this.state.participateInMetaMetrics === false) { if (this.state.participateInMetaMetrics === false) {
return; return;
@ -469,6 +464,7 @@ export default class MetaMetricsController {
const idTrait = metaMetricsId ? 'userId' : 'anonymousId'; const idTrait = metaMetricsId ? 'userId' : 'anonymousId';
const idValue = metaMetricsId ?? METAMETRICS_ANONYMOUS_ID; const idValue = metaMetricsId ?? METAMETRICS_ANONYMOUS_ID;
this._submitSegmentAPICall('page', { this._submitSegmentAPICall('page', {
messageId: buildUniqueMessageId({ actionId }),
[idTrait]: idValue, [idTrait]: idValue,
name, name,
properties: { properties: {
@ -659,6 +655,7 @@ export default class MetaMetricsController {
} = rawPayload; } = rawPayload;
return { return {
event, event,
messageId: buildUniqueMessageId(rawPayload),
properties: { properties: {
// These values are omitted from properties because they have special meaning // These values are omitted from properties because they have special meaning
// in segment. https://segment.com/docs/connections/spec/track/#properties. // in segment. https://segment.com/docs/connections/spec/track/#properties.

View File

@ -20,6 +20,7 @@ const NETWORK = 'Mainnet';
const FAKE_CHAIN_ID = '0x1338'; const FAKE_CHAIN_ID = '0x1338';
const LOCALE = 'en_US'; const LOCALE = 'en_US';
const TEST_META_METRICS_ID = '0xabc'; const TEST_META_METRICS_ID = '0xabc';
const DUMMY_ACTION_ID = 'DUMMY_ACTION_ID';
const MOCK_TRAITS = { const MOCK_TRAITS = {
test_boolean: true, test_boolean: true,
@ -634,6 +635,50 @@ describe('MetaMetricsController', function () {
); );
mock.verify(); mock.verify();
}); });
it('multiple trackPage call with same actionId should result in same messageId being sent to segment', function () {
const mock = sinon.mock(segment);
const metaMetricsController = getMetaMetricsController({
preferencesStore: getMockPreferencesStore({
participateInMetaMetrics: null,
}),
});
mock
.expects('page')
.twice()
.withArgs({
name: 'home',
userId: TEST_META_METRICS_ID,
context: DEFAULT_TEST_CONTEXT,
properties: {
params: null,
...DEFAULT_PAGE_PROPERTIES,
},
messageId: DUMMY_ACTION_ID,
timestamp: new Date(),
});
metaMetricsController.trackPage(
{
name: 'home',
params: null,
actionId: DUMMY_ACTION_ID,
environmentType: ENVIRONMENT_TYPE_BACKGROUND,
page: METAMETRICS_BACKGROUND_PAGE_OBJECT,
},
{ isOptInPath: true },
);
metaMetricsController.trackPage(
{
name: 'home',
params: null,
actionId: DUMMY_ACTION_ID,
environmentType: ENVIRONMENT_TYPE_BACKGROUND,
page: METAMETRICS_BACKGROUND_PAGE_OBJECT,
},
{ isOptInPath: true },
);
mock.verify();
});
}); });
describe('_buildUserTraitsObject', function () { describe('_buildUserTraitsObject', function () {

View File

@ -2041,7 +2041,7 @@ export default class MetamaskController extends EventEmitter {
} }
} }
async addCustomNetwork(customRpc) { async addCustomNetwork(customRpc, actionId) {
const { chainId, chainName, rpcUrl, ticker, blockExplorerUrl } = customRpc; const { chainId, chainName, rpcUrl, ticker, blockExplorerUrl } = customRpc;
await this.preferencesController.addToFrequentRpcList( await this.preferencesController.addToFrequentRpcList(
@ -2077,6 +2077,7 @@ export default class MetamaskController extends EventEmitter {
sensitiveProperties: { sensitiveProperties: {
rpc_url: rpcUrlOrigin, rpc_url: rpcUrlOrigin,
}, },
actionId,
}); });
} }

View File

@ -3516,7 +3516,10 @@ export async function closeNotificationPopup() {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
export function trackMetaMetricsEvent(payload, options) { export function trackMetaMetricsEvent(payload, options) {
return submitRequestToBackground('trackMetaMetricsEvent', [payload, options]); return submitRequestToBackground('trackMetaMetricsEvent', [
{ ...payload, actionId: generateActionId() },
options,
]);
} }
export function createEventFragment(options) { export function createEventFragment(options) {
@ -3548,7 +3551,10 @@ export function finalizeEventFragment(id, options) {
* @param {MetaMetricsPageOptions} options - options for handling the page view * @param {MetaMetricsPageOptions} options - options for handling the page view
*/ */
export function trackMetaMetricsPage(payload, options) { export function trackMetaMetricsPage(payload, options) {
return submitRequestToBackground('trackMetaMetricsPage', [payload, options]); return submitRequestToBackground('trackMetaMetricsPage', [
{ ...payload, actionId: generateActionId() },
options,
]);
} }
export function updateViewedNotifications(notificationIdViewedStatusMap) { export function updateViewedNotifications(notificationIdViewedStatusMap) {
@ -3578,6 +3584,7 @@ export async function setSmartTransactionsOptInStatus(
prevOptInState, prevOptInState,
) { ) {
trackMetaMetricsEvent({ trackMetaMetricsEvent({
actionId: generateActionId(),
event: 'STX OptIn', event: 'STX OptIn',
category: EVENT.CATEGORIES.SWAPS, category: EVENT.CATEGORIES.SWAPS,
sensitiveProperties: { sensitiveProperties: {
@ -3841,7 +3848,10 @@ export function addCustomNetwork(customRpc) {
return async (dispatch) => { return async (dispatch) => {
try { try {
dispatch(setNewCustomNetworkAdded(customRpc)); dispatch(setNewCustomNetworkAdded(customRpc));
await submitRequestToBackground('addCustomNetwork', [customRpc]); await submitRequestToBackground('addCustomNetwork', [
customRpc,
generateActionId(),
]);
} catch (error) { } catch (error) {
log.error(error); log.error(error);
dispatch(displayWarning('Had a problem changing networks!')); dispatch(displayWarning('Had a problem changing networks!'));