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:
parent
6f27c9065c
commit
6c0930899d
@ -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.
|
||||||
|
@ -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 () {
|
||||||
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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!'));
|
||||||
|
Loading…
Reference in New Issue
Block a user