Skip to content

Commit

Permalink
chore: Add App Opened Metric Event (#28927)
Browse files Browse the repository at this point in the history
## **Description**
This PR adds tracking for when the MetaMask app is opened in order to
track MAU/MTU.


[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28927?quickstart=1)

## **Related issues**

Fixes:  MetaMask/MetaMask-planning#3401

## **Manual testing steps**

1. Ensure MM is not "fullscreen" in any tabs
2. Open chrome dev tools for service worker 
3. Clear the network tab
4. Open metamask (via Dapp or by clicking on extension)
5. There should be one or more new network request for POST
https://api.segment.io/v1/batch – One of those should should have event:
"App Opened"

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->

## **Pre-merge author checklist**

- [ ] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

---------

Co-authored-by: Niranjana Binoy <[email protected]>
  • Loading branch information
dbrans and NiranjanaBinoy authored Dec 12, 2024
1 parent 2e941a8 commit b3c4759
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 0 deletions.
44 changes: 44 additions & 0 deletions app/scripts/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,49 @@ function trackDappView(remotePort) {
}
}

/**
* Emit App Opened event
*/
function emitAppOpenedMetricEvent() {
const { metaMetricsId, participateInMetaMetrics } =
controller.metaMetricsController.state;

// Skip if user hasn't opted into metrics
if (metaMetricsId === null && !participateInMetaMetrics) {
return;
}

controller.metaMetricsController.trackEvent({
event: MetaMetricsEventName.AppOpened,
category: MetaMetricsEventCategory.App,
});
}

/**
* This function checks if the app is being opened
* and emits an event only if no other UI instances are currently open.
*
* @param {string} environment - The environment type where the app is opening
*/
function trackAppOpened(environment) {
// List of valid environment types to track
const environmentTypeList = [
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_FULLSCREEN,
];

// Check if any UI instances are currently open
const isFullscreenOpen = Object.values(openMetamaskTabsIDs).some(Boolean);
const isAlreadyOpen =
isFullscreenOpen || notificationIsOpen || openPopupCount > 0;

// Only emit event if no UI is open and environment is valid
if (!isAlreadyOpen && environmentTypeList.includes(environment)) {
emitAppOpenedMetricEvent();
}
}

/**
* Initializes the MetaMask Controller with any initial state and default language.
* Configures platform-specific error reporting strategy.
Expand Down Expand Up @@ -883,6 +926,7 @@ export function setupController(
// communication with popup
controller.isClientOpen = true;
controller.setupTrustedCommunication(portStream, remotePort.sender);
trackAppOpened(processName);

initializeRemoteFeatureFlags();

Expand Down
1 change: 1 addition & 0 deletions shared/constants/metametrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@ export enum MetaMetricsEventName {
ActivityDetailsClosed = 'Activity Details Closed',
AnalyticsPreferenceSelected = 'Analytics Preference Selected',
AppInstalled = 'App Installed',
AppOpened = 'App Opened',
AppUnlocked = 'App Unlocked',
AppUnlockedFailed = 'App Unlocked Failed',
AppLocked = 'App Locked',
Expand Down
104 changes: 104 additions & 0 deletions test/e2e/tests/metrics/app-opened.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { strict as assert } from 'assert';
import { Mockttp } from 'mockttp';
import {
withFixtures,
getEventPayloads,
unlockWallet,
connectToDapp,
} from '../../helpers';
import FixtureBuilder from '../../fixture-builder';
import { loginWithoutBalanceValidation } from '../../page-objects/flows/login.flow';

/**
* Mocks the segment API for the App Opened event that we expect to see when
* these tests are run.
*
* @param mockServer - The mock server instance.
* @returns The mocked endpoints
*/
async function mockSegment(mockServer: Mockttp) {
return [
await mockServer
.forPost('https://api.segment.io/v1/batch')
.withJsonBodyIncluding({
batch: [{ type: 'track', event: 'App Opened' }],
})
.thenCallback(() => {
return {
statusCode: 200,
};
}),
];
}

describe('App Opened metric @no-mmi', function () {
it('should send AppOpened metric when app is opened and metrics are enabled', async function () {
await withFixtures(
{
fixtures: new FixtureBuilder()
.withMetaMetricsController({
metaMetricsId: 'fake-metrics-fd20',
participateInMetaMetrics: true,
})
.build(),
title: this.test?.fullTitle(),
testSpecificMock: mockSegment,
},
async ({ driver, mockedEndpoint: mockedEndpoints }) => {
await loginWithoutBalanceValidation(driver);

const events = await getEventPayloads(driver, mockedEndpoints);
assert.equal(events.length, 1);
assert.equal(events[0].properties.category, 'App');
},
);
});

it('should not send AppOpened metric when metrics are disabled', async function () {
await withFixtures(
{
fixtures: new FixtureBuilder()
.withMetaMetricsController({
metaMetricsId: 'fake-metrics-fd20',
participateInMetaMetrics: false,
})
.build(),
title: this.test?.fullTitle(),
testSpecificMock: mockSegment,
},
async ({ driver, mockedEndpoint: mockedEndpoints }) => {
await unlockWallet(driver);

const events = await getEventPayloads(driver, mockedEndpoints);
assert.equal(events.length, 0);
},
);
});

it('should send AppOpened metric when dapp opens MetaMask', async function () {
await withFixtures(
{
dapp: true,
fixtures: new FixtureBuilder()
.withMetaMetricsController({
metaMetricsId: 'fake-metrics-fd20',
participateInMetaMetrics: true,
})
.build(),
title: this.test?.fullTitle(),
testSpecificMock: mockSegment,
},
async ({ driver, mockedEndpoint: mockedEndpoints }) => {
await unlockWallet(driver);

// Connect to dapp which will trigger MetaMask to open
await connectToDapp(driver);

// Wait for events to be tracked
const events = await getEventPayloads(driver, mockedEndpoints);
assert.equal(events.length, 1);
assert.equal(events[0].properties.category, 'App');
},
);
});
});

0 comments on commit b3c4759

Please sign in to comment.