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

MetaMetrics: add segment.identify (#14195)

* MetaMetrics: add segment.identify

* MetaMetrics: rm non-existent typedef

* MetaMetrics: WIP segment.identify pt. 2

* Revert "MetaMetrics: WIP segment.identify pt. 2"

This reverts commit 8126de4dff9312aab2275cba81d0432bfcdb097b.

* MetaMetrics: add identify tests

* MetaMetricsController: update identify w/ fix

* MetaMetricsController: fix identify tests

* MetaMetricsController#identify: warn instead of throw

* MetaMetrics: transform date into ISO string

* MetaMetricsController: alphabetize test

* MetaMetricsController: restore asset in test

* MetaMetrics: rn traits -> userTraits

* MetaMetrics: restore trackEvent location

* update to follow analytics-node api

* MetaMetricsController: update tests + @link

* MetaMetrics: destimation -> destination

Co-authored-by: brad-decker <bhdecker84@gmail.com>
This commit is contained in:
Ariella Vu 2022-03-31 16:21:01 -03:00 committed by GitHub
parent d2c5962463
commit f088db99a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 208 additions and 1 deletions

View File

@ -281,6 +281,30 @@ export default class MetaMetricsController {
this.store.updateState({ fragments });
}
/**
* Calls this._identify with validated metaMetricsId and user traits if user is participating
* in the MetaMetrics analytics program
*
* @param {Object} userTraits
*/
identify(userTraits) {
const { metaMetricsId, participateInMetaMetrics } = this.state;
if (!participateInMetaMetrics || !metaMetricsId || !userTraits) {
return;
}
if (typeof userTraits !== 'object') {
console.warn(
`MetaMetricsController#identify: userTraits parameter must be an object. Received type: ${typeof userTraits}`,
);
return;
}
const allValidTraits = this._buildValidTraits(userTraits);
this._identify(allValidTraits);
}
/**
* Setter for the `participateInMetaMetrics` property
*
@ -434,7 +458,7 @@ export default class MetaMetricsController {
handleMetaMaskStateUpdate(newState) {
const userTraits = this._buildUserTraitsObject(newState);
if (userTraits) {
// this.identify(userTraits);
this.identify(userTraits);
}
}
@ -535,6 +559,103 @@ export default class MetaMetricsController {
return null;
}
/**
* Returns a new object of all valid user traits. For dates, we transform them into ISO-8601 timestamp strings.
*
* @see {@link https://segment.com/docs/connections/spec/common/#timestamps}
* @param {Object} userTraits
* @returns {Object}
*/
_buildValidTraits(userTraits) {
return Object.entries(userTraits).reduce((validTraits, [key, value]) => {
if (this._isValidTraitDate(value)) {
validTraits[key] = value.toISOString();
} else if (this._isValidTrait(value)) {
validTraits[key] = value;
} else {
console.warn(
`MetaMetricsController: "${key}" value is not a valid trait type`,
);
}
return validTraits;
}, {});
}
/**
* Calls segment.identify with given user traits
*
* @see {@link https://segment.com/docs/connections/sources/catalog/libraries/server/node/#identify}
* @private
* @param {Object} userTraits
*/
_identify(userTraits) {
const { metaMetricsId } = this.state;
if (!userTraits || Object.keys(userTraits).length === 0) {
console.warn('MetaMetricsController#_identify: No userTraits found');
return;
}
try {
this.segment.identify({
userId: metaMetricsId,
traits: userTraits,
});
} catch (err) {
this._captureException(err);
}
}
/**
* Validates the trait value. Segment accepts any data type. We are adding validation here to
* support data types for our Segment destination(s) e.g. MixPanel
*
* @param {*} value
* @returns {boolean}
*/
_isValidTrait(value) {
const type = typeof value;
return (
type === 'string' ||
type === 'boolean' ||
type === 'number' ||
this._isValidTraitArray(value) ||
this._isValidTraitDate(value)
);
}
/**
* Segment accepts any data type value. We have special logic to validate arrays.
*
* @param {*} value
* @returns {boolean}
*/
_isValidTraitArray = (value) => {
return (
Array.isArray(value) &&
(value.every((element) => {
return typeof element === 'string';
}) ||
value.every((element) => {
return typeof element === 'boolean';
}) ||
value.every((element) => {
return typeof element === 'number';
}))
);
};
/**
* Returns true if the value is an accepted date type
*
* @param {*} value
* @returns {boolean}
*/
_isValidTraitDate = (value) => {
return Object.prototype.toString.call(value) === '[object Date]';
};
/**
* Perform validation on the payload and update the id type to use before
* sending to Segment. Also examines the options to route and handle the

View File

@ -23,6 +23,20 @@ const FAKE_CHAIN_ID = '0x1338';
const LOCALE = 'en_US';
const TEST_META_METRICS_ID = '0xabc';
const MOCK_TRAITS = {
test_boolean: true,
test_string: 'abc',
test_number: 123,
test_bool_array: [true, true, false],
test_string_array: ['test', 'test', 'test'],
test_boolean_array: [1, 2, 3],
};
const MOCK_INVALID_TRAITS = {
test_null: null,
test_array_multi_types: [true, 'a', 1],
};
const DEFAULT_TEST_CONTEXT = {
app: { name: 'MetaMask Extension', version: VERSION },
page: METAMETRICS_BACKGROUND_PAGE_OBJECT,
@ -216,6 +230,78 @@ describe('MetaMetricsController', function () {
});
});
describe('identify', function () {
it('should call segment.identify for valid traits if user is participating in metametrics', async function () {
const metaMetricsController = getMetaMetricsController({
participateInMetaMetrics: true,
metaMetricsId: TEST_META_METRICS_ID,
});
const mock = sinon.mock(segment);
mock
.expects('identify')
.once()
.withArgs({ userId: TEST_META_METRICS_ID, traits: MOCK_TRAITS });
metaMetricsController.identify({
...MOCK_TRAITS,
...MOCK_INVALID_TRAITS,
});
mock.verify();
});
it('should transform date type traits into ISO-8601 timestamp strings', async function () {
const metaMetricsController = getMetaMetricsController({
participateInMetaMetrics: true,
metaMetricsId: TEST_META_METRICS_ID,
});
const mock = sinon.mock(segment);
const mockDate = new Date();
const mockDateISOString = mockDate.toISOString();
mock
.expects('identify')
.once()
.withArgs({
userId: TEST_META_METRICS_ID,
traits: {
test_date: mockDateISOString,
},
});
metaMetricsController.identify({
test_date: mockDate,
});
mock.verify();
});
it('should not call segment.identify if user is not participating in metametrics', function () {
const metaMetricsController = getMetaMetricsController({
participateInMetaMetrics: false,
});
const mock = sinon.mock(segment);
mock.expects('identify').never();
metaMetricsController.identify(MOCK_TRAITS);
mock.verify();
});
it('should not call segment.identify if there are no valid traits to identify', async function () {
const metaMetricsController = getMetaMetricsController({
participateInMetaMetrics: true,
metaMetricsId: TEST_META_METRICS_ID,
});
const mock = sinon.mock(segment);
mock.expects('identify').never();
metaMetricsController.identify(MOCK_INVALID_TRAITS);
mock.verify();
});
});
describe('setParticipateInMetaMetrics', function () {
it('should update the value of participateInMetaMetrics', function () {
const metaMetricsController = getMetaMetricsController({