Skip to content

Commit

Permalink
Merge branch 'develop' into rm-enzyme
Browse files Browse the repository at this point in the history
  • Loading branch information
tmashuang authored Nov 8, 2022
2 parents 0ecce6c + d4ffc3a commit 9792460
Show file tree
Hide file tree
Showing 14 changed files with 233 additions and 45 deletions.
60 changes: 56 additions & 4 deletions app/scripts/controllers/metametrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import { SECOND } from '../../../shared/constants/time';
import { isManifestV3 } from '../../../shared/modules/mv3.utils';
import { METAMETRICS_FINALIZE_EVENT_FRAGMENT_ALARM } from '../../../shared/constants/alarms';
import { checkAlarmExists } from '../lib/util';
import { checkAlarmExists, generateRandomId, isValidDate } from '../lib/util';

const EXTENSION_UNINSTALL_URL = 'https://metamask.io/uninstalled';

Expand Down Expand Up @@ -110,6 +110,7 @@ export default class MetaMetricsController {
this.environment = environment;

const abandonedFragments = omitBy(initState?.fragments, 'persist');
const segmentApiCalls = initState?.segmentApiCalls || {};

this.store = new ObservableStore({
participateInMetaMetrics: null,
Expand All @@ -120,6 +121,9 @@ export default class MetaMetricsController {
fragments: {
...initState?.fragments,
},
segmentApiCalls: {
...segmentApiCalls,
},
});

preferencesStore.subscribe(({ currentLocale }) => {
Expand All @@ -142,6 +146,15 @@ export default class MetaMetricsController {
this.finalizeEventFragment(fragment.id, { abandoned: true });
});

// Code below submits any pending segmentApiCalls to Segment if/when the controller is re-instantiated
if (isManifestV3) {
Object.values(segmentApiCalls).forEach(
({ eventType, payload, callback }) => {
this._submitSegmentAPICall(eventType, payload, callback);
},
);
}

// Close out event fragments that were created but not progressed. An
// interval is used to routinely check if a fragment has not been updated
// within the fragment's timeout window. When creating a new event fragment
Expand Down Expand Up @@ -455,7 +468,7 @@ export default class MetaMetricsController {
const { metaMetricsId } = this.state;
const idTrait = metaMetricsId ? 'userId' : 'anonymousId';
const idValue = metaMetricsId ?? METAMETRICS_ANONYMOUS_ID;
this.segment.page({
this._submitSegmentAPICall('page', {
[idTrait]: idValue,
name,
properties: {
Expand Down Expand Up @@ -808,7 +821,7 @@ export default class MetaMetricsController {
}

try {
this.segment.identify({
this._submitSegmentAPICall('identify', {
userId: metaMetricsId,
traits: userTraits,
});
Expand Down Expand Up @@ -937,10 +950,49 @@ export default class MetaMetricsController {
return resolve();
};

this.segment.track(payload, callback);
this._submitSegmentAPICall('track', payload, callback);
if (flushImmediately) {
this.segment.flush();
}
});
}

// Method below submits the request to analytics SDK.
// It will also add event to controller store
// and pass a callback to remove it from store once request is submitted to segment
// Saving segmentApiCalls in controller store in MV3 ensures that events are tracked
// even if service worker terminates before events are submiteed to segment.
_submitSegmentAPICall(eventType, payload, callback) {
const messageId = payload.messageId || generateRandomId();
let timestamp = new Date();
if (payload.timestamp) {
const payloadDate = new Date(payload.timestamp);
if (isValidDate(payloadDate)) {
timestamp = payloadDate;
}
}
const modifiedPayload = { ...payload, messageId, timestamp };
this.store.updateState({
segmentApiCalls: {
...this.store.getState().segmentApiCalls,
[messageId]: {
eventType,
payload: {
...modifiedPayload,
timestamp: modifiedPayload.timestamp.toString(),
},
callback,
},
},
});
const modifiedCallback = (result) => {
const { segmentApiCalls } = this.store.getState();
delete segmentApiCalls[messageId];
this.store.updateState({
segmentApiCalls,
});
return callback?.(result);
};
this.segment[eventType](modifiedPayload, modifiedCallback);
}
}
72 changes: 65 additions & 7 deletions app/scripts/controllers/metametrics.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '../../../shared/constants/metametrics';
import waitUntilCalled from '../../../test/lib/wait-until-called';
import { CHAIN_IDS, CURRENCY_SYMBOLS } from '../../../shared/constants/network';
import * as Utils from '../lib/util';
import MetaMetricsController from './metametrics';
import { NETWORK_EVENTS } from './network';

Expand Down Expand Up @@ -124,9 +125,10 @@ function getMetaMetricsController({
metaMetricsId = TEST_META_METRICS_ID,
preferencesStore = getMockPreferencesStore(),
networkController = getMockNetworkController(),
segmentInstance,
} = {}) {
return new MetaMetricsController({
segment,
segment: segmentInstance || segment,
getNetworkIdentifier:
networkController.getNetworkIdentifier.bind(networkController),
getCurrentChainId:
Expand All @@ -145,10 +147,17 @@ function getMetaMetricsController({
testid: SAMPLE_PERSISTED_EVENT,
testid2: SAMPLE_NON_PERSISTED_EVENT,
},
events: {},
},
});
}
describe('MetaMetricsController', function () {
const now = new Date();
let clock;
beforeEach(function () {
clock = sinon.useFakeTimers(now.getTime());
sinon.stub(Utils, 'generateRandomId').returns('DUMMY_RANDOM_ID');
});
describe('constructor', function () {
it('should properly initialize', function () {
const mock = sinon.mock(segment);
Expand All @@ -163,6 +172,8 @@ describe('MetaMetricsController', function () {
...DEFAULT_EVENT_PROPERTIES,
test: true,
},
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});
const metaMetricsController = getMetaMetricsController();
assert.strictEqual(metaMetricsController.version, VERSION);
Expand Down Expand Up @@ -233,15 +244,18 @@ describe('MetaMetricsController', function () {
});
const mock = sinon.mock(segment);

mock
.expects('identify')
.once()
.withArgs({ userId: TEST_META_METRICS_ID, traits: MOCK_TRAITS });
mock.expects('identify').once().withArgs({
userId: TEST_META_METRICS_ID,
traits: MOCK_TRAITS,
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});

metaMetricsController.identify({
...MOCK_TRAITS,
...MOCK_INVALID_TRAITS,
});

mock.verify();
});

Expand All @@ -263,6 +277,8 @@ describe('MetaMetricsController', function () {
traits: {
test_date: mockDateISOString,
},
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});

metaMetricsController.identify({
Expand Down Expand Up @@ -358,6 +374,8 @@ describe('MetaMetricsController', function () {
test: 1,
...DEFAULT_EVENT_PROPERTIES,
},
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});
metaMetricsController.submitEvent(
{
Expand Down Expand Up @@ -388,6 +406,8 @@ describe('MetaMetricsController', function () {
test: 1,
...DEFAULT_EVENT_PROPERTIES,
},
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});
metaMetricsController.submitEvent(
{
Expand Down Expand Up @@ -417,6 +437,8 @@ describe('MetaMetricsController', function () {
legacy_event: true,
...DEFAULT_EVENT_PROPERTIES,
},
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});
metaMetricsController.submitEvent(
{
Expand All @@ -439,12 +461,14 @@ describe('MetaMetricsController', function () {
.once()
.withArgs({
event: 'Fake Event',
userId: TEST_META_METRICS_ID,
context: DEFAULT_TEST_CONTEXT,
properties: {
test: 1,
...DEFAULT_EVENT_PROPERTIES,
},
context: DEFAULT_TEST_CONTEXT,
userId: TEST_META_METRICS_ID,
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});
metaMetricsController.submitEvent({
event: 'Fake Event',
Expand Down Expand Up @@ -519,6 +543,8 @@ describe('MetaMetricsController', function () {
foo: 'bar',
...DEFAULT_EVENT_PROPERTIES,
},
messageId: Utils.generateRandomId(),
timestamp: new Date(),
}),
);
assert.ok(
Expand All @@ -527,6 +553,8 @@ describe('MetaMetricsController', function () {
userId: TEST_META_METRICS_ID,
context: DEFAULT_TEST_CONTEXT,
properties: DEFAULT_EVENT_PROPERTIES,
messageId: Utils.generateRandomId(),
timestamp: new Date(),
}),
);
});
Expand All @@ -547,6 +575,8 @@ describe('MetaMetricsController', function () {
params: null,
...DEFAULT_PAGE_PROPERTIES,
},
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});
metaMetricsController.trackPage({
name: 'home',
Expand Down Expand Up @@ -590,6 +620,8 @@ describe('MetaMetricsController', function () {
params: null,
...DEFAULT_PAGE_PROPERTIES,
},
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});
metaMetricsController.trackPage(
{
Expand Down Expand Up @@ -788,9 +820,35 @@ describe('MetaMetricsController', function () {
});
});

describe('submitting segmentApiCalls to segment SDK', function () {
it('should add event to store when submitting to SDK', function () {
const metaMetricsController = getMetaMetricsController({});
metaMetricsController.trackPage({}, { isOptIn: true });
const { segmentApiCalls } = metaMetricsController.store.getState();
assert(Object.keys(segmentApiCalls).length > 0);
});

it('should remove event from store when callback is invoked', function () {
const segmentInstance = createSegmentMock(2, 10000);
const stubFn = (_, cb) => {
cb();
};
sinon.stub(segmentInstance, 'track').callsFake(stubFn);
sinon.stub(segmentInstance, 'page').callsFake(stubFn);

const metaMetricsController = getMetaMetricsController({
segmentInstance,
});
metaMetricsController.trackPage({}, { isOptIn: true });
const { segmentApiCalls } = metaMetricsController.store.getState();
assert(Object.keys(segmentApiCalls).length === 0);
});
});

afterEach(function () {
// flush the queues manually after each test
segment.flush();
clock.restore();
sinon.restore();
});
});
13 changes: 1 addition & 12 deletions app/scripts/lib/segment/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,10 @@ import removeSlash from 'remove-trailing-slash';
import looselyValidate from '@segment/loosely-validate-event';
import { isString } from 'lodash';
import isRetryAllowed from 'is-retry-allowed';
import { generateRandomId } from '../util';

const noop = () => ({});

// Taken from https://stackoverflow.com/a/1349426/3696652
const characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const generateRandomId = () => {
let result = '';
const charactersLength = characters.length;
for (let i = 0; i < 20; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
};

// Method below is inspired from axios-retry https://github.com/softonic/axios-retry
function isNetworkError(error) {
return (
Expand Down
16 changes: 16 additions & 0 deletions app/scripts/lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,19 @@ export {
getChainType,
checkAlarmExists,
};

// Taken from https://stackoverflow.com/a/1349426/3696652
const characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
export const generateRandomId = () => {
let result = '';
const charactersLength = characters.length;
for (let i = 0; i < 20; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
};

export const isValidDate = (d) => {
return d instanceof Date && !isNaN(d);
};
2 changes: 1 addition & 1 deletion test/e2e/tests/send-eth.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ describe('Send ETH non-contract address with data that matches ERC20 transfer da

await driver.clickElement({ text: 'Next', tag: 'button' });

await driver.clickElement({ text: '0xc42...cd28' });
await driver.clickElement({ text: 'New contract' });

const recipientAddress = await driver.findElements({
text: '0xc427D562164062a23a5cFf596A4a3208e72Acd28',
Expand Down
8 changes: 4 additions & 4 deletions test/e2e/tests/send-hex-address.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('Send ETH to a 40 character hexadecimal address', function () {
);
await sendTransactionListItem.click();
await driver.clickElement({ text: 'Activity log', tag: 'summary' });
await driver.clickElement('.sender-to-recipient__name:nth-of-type(2)');
await driver.clickElement('[data-testid="sender-to-recipient__name"]');

// Verify address in activity log
const publicAddress = await driver.findElement(
Expand Down Expand Up @@ -108,7 +108,7 @@ describe('Send ETH to a 40 character hexadecimal address', function () {
);
await sendTransactionListItem.click();
await driver.clickElement({ text: 'Activity log', tag: 'summary' });
await driver.clickElement('.sender-to-recipient__name:nth-of-type(2)');
await driver.clickElement('[data-testid="sender-to-recipient__name"]');

// Verify address in activity log
const publicAddress = await driver.findElement(
Expand Down Expand Up @@ -212,7 +212,7 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () {
);
await sendTransactionListItem.click();
await driver.clickElement({ text: 'Activity log', tag: 'summary' });
await driver.clickElement('.sender-to-recipient__name:nth-of-type(2)');
await driver.clickElement('[data-testid="sender-to-recipient__name"]');

// Verify address in activity log
const publicAddress = await driver.findElement(
Expand Down Expand Up @@ -302,7 +302,7 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () {
);
await sendTransactionListItem.click();
await driver.clickElement({ text: 'Activity log', tag: 'summary' });
await driver.clickElement('.sender-to-recipient__name:nth-of-type(2)');
await driver.clickElement('[data-testid="sender-to-recipient__name"]');

// Verify address in activity log
const publicAddress = await driver.findElement(
Expand Down
Loading

0 comments on commit 9792460

Please sign in to comment.