Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adding delete metametrics data to security and privacy tab #24571

Merged
merged 40 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
958d1a9
UI changes for the Data deletion
NiranjanaBinoy May 17, 2024
ec0ff64
updating the status changes
NiranjanaBinoy May 22, 2024
036d914
adding teh error modal
NiranjanaBinoy May 22, 2024
337ab3c
updating the method name to updateDataDeletionTaskStatus
NiranjanaBinoy May 22, 2024
ea06cad
refactoring the new delete metametrics entry to a functional componenet
NiranjanaBinoy May 27, 2024
d125e38
moving hasDataRecorded to controller state and managing the case wher…
NiranjanaBinoy May 30, 2024
d015c93
adding unit tests
NiranjanaBinoy Jun 7, 2024
21aca78
updating the design change for when metametrics id is not available
NiranjanaBinoy Jun 13, 2024
d724452
fixing rebase issues
NiranjanaBinoy Jun 13, 2024
6d21092
adding e2e for delete metametrics data
NiranjanaBinoy Jun 20, 2024
0b70e69
adding e2e for delete metametrics data
NiranjanaBinoy Jun 20, 2024
b5b3a9a
updating privacy snapshot
NiranjanaBinoy Jun 20, 2024
98865a0
trying to fix teh e2e and lint errors
NiranjanaBinoy Jun 26, 2024
e2a36a9
updating the background function call and fixing e2e
NiranjanaBinoy Jul 12, 2024
a8c3c69
reverting changes to privacy snapshot
NiranjanaBinoy Jul 12, 2024
3f28e4e
adding state to keep track of data being recorded
NiranjanaBinoy Jul 12, 2024
382946f
renaming the toggle files
NiranjanaBinoy Jul 12, 2024
f6a354c
renaming the toggle files
NiranjanaBinoy Jul 12, 2024
5a83469
updating the state variable name
NiranjanaBinoy Jul 12, 2024
664661b
updating privacy-snapshot
NiranjanaBinoy Jul 12, 2024
e6b2449
fix: mitigate race condition and flakiness for delete metametrics tes…
seaona Jul 18, 2024
c7a0c99
updating UI to use timestamp to track teh data recording
NiranjanaBinoy Jul 18, 2024
7c838e4
fixing e2e
NiranjanaBinoy Jul 19, 2024
6b12b0e
updating the e2e
NiranjanaBinoy Jul 22, 2024
751b2b9
renaming rowLocator to selectors
NiranjanaBinoy Aug 20, 2024
d854e42
updaing settings constant and fixing e2e
NiranjanaBinoy Aug 21, 2024
4931f26
fixing e2e
NiranjanaBinoy Aug 29, 2024
ec16861
Update delete-metametrics-data.spec.ts
NiranjanaBinoy Aug 29, 2024
d67c2f7
fixing settings search
NiranjanaBinoy Sep 5, 2024
a0bcc1d
updates after rebaseing
NiranjanaBinoy Sep 11, 2024
8e3172d
updaing the DeleteRegulationStatus enum
NiranjanaBinoy Sep 13, 2024
3b50e53
addressing review comments - updating unit tests
NiranjanaBinoy Sep 20, 2024
70caa44
rebase error fix
NiranjanaBinoy Sep 23, 2024
f87ab44
adding error occured event
NiranjanaBinoy Sep 23, 2024
26d520c
resolving conflict
NiranjanaBinoy Sep 23, 2024
472709c
adding forwardRef for settings search
NiranjanaBinoy Oct 1, 2024
9913bce
adding unit tests to satisfy sonar cloud
NiranjanaBinoy Oct 2, 2024
57d8544
fixing sonar cloud failure
NiranjanaBinoy Oct 3, 2024
2e15e0e
addressing comments
NiranjanaBinoy Oct 3, 2024
8013ee7
updating unit test
NiranjanaBinoy Oct 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions privacy-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"mainnet.infura.io",
"metamask.eth",
"metamask.github.io",
"metametrics.metamask.test",
"min-api.cryptocompare.com",
"nft.api.cx.metamask.io",
"oidc.api.cx.metamask.io",
NiranjanaBinoy marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -42,6 +43,7 @@
"portfolio.metamask.io",
"price.api.cx.metamask.io",
"proxy.api.cx.metamask.io",
"proxy.dev-api.cx.metamask.io",
"raw.githubusercontent.com",
"registry.npmjs.org",
"responsive-rpc.test",
Expand Down
2 changes: 2 additions & 0 deletions shared/constants/metametrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ export enum MetaMetricsEventName {
EncryptionPublicKeyApproved = 'Encryption Approved',
EncryptionPublicKeyRejected = 'Encryption Rejected',
EncryptionPublicKeyRequested = 'Encryption Requested',
ErrorOccured = 'Error occured',
ExternalLinkClicked = 'External Link Clicked',
KeyExportSelected = 'Key Export Selected',
KeyExportRequested = 'Key Export Requested',
Expand All @@ -552,6 +553,7 @@ export enum MetaMetricsEventName {
MarkAllNotificationsRead = 'Notifications Marked All as Read',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

MetricsOptIn = 'Metrics Opt In',
MetricsOptOut = 'Metrics Opt Out',
MetricsDataDeletionRequest = 'Delete MetaMetrics Data Request Submitted',
NavAccountMenuOpened = 'Account Menu Opened',
NavConnectedSitesOpened = 'Connected Sites Opened',
NavMainMenuOpened = 'Main Menu Opened',
Expand Down
246 changes: 246 additions & 0 deletions test/e2e/tests/metrics/delete-metametrics-data.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
import { strict as assert } from 'assert';
import { MockedEndpoint, Mockttp } from 'mockttp';
import { Suite } from 'mocha';
import {
defaultGanacheOptions,
withFixtures,
getEventPayloads,
unlockWallet,
} from '../../helpers';
import FixtureBuilder from '../../fixture-builder';
import { Driver } from '../../webdriver/driver';
import { TestSuiteArguments } from '../confirmations/transactions/shared';
import { WebElementWithWaitForElementState } from '../../webdriver/types';

const selectors = {
accountOptionsMenuButton: '[data-testid="account-options-menu-button"]',
globalMenuSettingsButton: '[data-testid="global-menu-settings"]',
securityAndPrivacySettings: { text: 'Security & privacy', tag: 'div' },
experimentalSettings: { text: 'Experimental', tag: 'div' },
deletMetaMetricsSettings: '[data-testid="delete-metametrics-data-button"]',
deleteMetaMetricsDataButton: {
text: 'Delete MetaMetrics data',
tag: 'button',
},
clearButton: { text: 'Clear', tag: 'button' },
backButton: '[data-testid="settings-back-button"]',
};

/**
* mocks the segment api multiple times for specific payloads that we expect to
* see when these tests are run. In this case we are looking for
* 'Permissions Requested' and 'Permissions Received'. Do not use the constants
* from the metrics constants files, because if these change we want a strong
* indicator to our data team that the shape of data will change.
*
* @param mockServer
* @returns
*/
const mockSegment = async (mockServer: Mockttp) => {
return [
await mockServer
.forPost('https://api.segment.io/v1/batch')
.withJsonBodyIncluding({
batch: [
{ type: 'track', event: 'Delete MetaMetrics Data Request Submitted' },
],
})
.thenCallback(() => {
return {
statusCode: 200,
};
}),
await mockServer
.forPost('https://metametrics.metamask.test/regulations/sources/test')
.withHeaders({ 'Content-Type': 'application/vnd.segment.v1+json' })
.withBodyIncluding(
JSON.stringify({
regulationType: 'DELETE_ONLY',
subjectType: 'USER_ID',
subjectIds: ['fake-metrics-id'],
}),
)
.thenCallback(() => ({
statusCode: 200,
json: { data: { regulateId: 'fake-delete-regulation-id' } },
})),
await mockServer
.forGet(
'https://metametrics.metamask.test/regulations/fake-delete-regulation-id',
)
.withHeaders({ 'Content-Type': 'application/vnd.segment.v1+json' })
.thenCallback(() => ({
statusCode: 200,
json: {
data: {
regulation: {
overallStatus: 'FINISHED',
},
},
},
})),
];
};
/**
* Scenarios:
* 1. Deletion while Metrics is Opted in.
* 2. Deletion while Metrics is Opted out.
* 3. Deletion when user never opted for metrics.
*/
describe('Delete MetaMetrics Data @no-mmi', function (this: Suite) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Is it a lot of effort to migrate this to POM e2e tests pattern? We can also leave it a TODO to be targeted in another PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we aim to get it merged as soon as possible, I would prefer to add a TODO and create a ticket for the same.

it('while user has opted in for metrics tracking', async function () {
await withFixtures(
{
fixtures: new FixtureBuilder()
.withMetaMetricsController({
metaMetricsId: 'fake-metrics-id',
participateInMetaMetrics: true,
})
.build(),
defaultGanacheOptions,
title: this.test?.fullTitle(),
testSpecificMock: mockSegment,
},
async ({
driver,
mockedEndpoint: mockedEndpoints,
}: TestSuiteArguments) => {
await unlockWallet(driver);

await driver.clickElement(selectors.accountOptionsMenuButton);
await driver.clickElement(selectors.globalMenuSettingsButton);
await driver.clickElement(selectors.securityAndPrivacySettings);

await driver.findElement(selectors.deletMetaMetricsSettings);
await driver.clickElement(selectors.deleteMetaMetricsDataButton);

// there is a race condition, where we need to wait before clicking clear button otherwise an error is thrown in the background
// we cannot wait for a UI conditon, so we a delay to mitigate this until another solution is found
await driver.delay(3000);
await driver.clickElementAndWaitToDisappear(selectors.clearButton);

const deleteMetaMetricsDataButton = await driver.findElement(
selectors.deleteMetaMetricsDataButton,
);
await (
deleteMetaMetricsDataButton as WebElementWithWaitForElementState
).waitForElementState('disabled');

const events = await getEventPayloads(
driver,
mockedEndpoints as MockedEndpoint[],
);
assert.equal(events.length, 3);
assert.deepStrictEqual(events[0].properties, {
category: 'Settings',
locale: 'en',
chain_id: '0x539',
environment_type: 'fullscreen',
});

await driver.clickElementAndWaitToDisappear(
'.mm-box button[aria-label="Close"]',
);
await driver.clickElement(selectors.accountOptionsMenuButton);
await driver.clickElement(selectors.globalMenuSettingsButton);
await driver.clickElement(selectors.securityAndPrivacySettings);

const deleteMetaMetricsDataButtonRefreshed =
await driver.findClickableElement(
selectors.deleteMetaMetricsDataButton,
);
assert.equal(
await deleteMetaMetricsDataButtonRefreshed.isEnabled(),
true,
'Delete MetaMetrics data button is enabled',
);
},
);
});
it('while user has opted out for metrics tracking', async function () {
await withFixtures(
{
fixtures: new FixtureBuilder()
.withMetaMetricsController({
metaMetricsId: 'fake-metrics-id',
})
.build(),
defaultGanacheOptions,
title: this.test?.fullTitle(),
testSpecificMock: mockSegment,
},
async ({
driver,
mockedEndpoint: mockedEndpoints,
}: TestSuiteArguments) => {
await unlockWallet(driver);

await driver.clickElement(selectors.accountOptionsMenuButton);
await driver.clickElement(selectors.globalMenuSettingsButton);
await driver.clickElement(selectors.securityAndPrivacySettings);

await driver.findElement(selectors.deletMetaMetricsSettings);
await driver.clickElement(selectors.deleteMetaMetricsDataButton);

// there is a race condition, where we need to wait before clicking clear button otherwise an error is thrown in the background
// we cannot wait for a UI conditon, so we a delay to mitigate this until another solution is found
await driver.delay(3000);
await driver.clickElementAndWaitToDisappear(selectors.clearButton);

const deleteMetaMetricsDataButton = await driver.findElement(
selectors.deleteMetaMetricsDataButton,
);
await (
deleteMetaMetricsDataButton as WebElementWithWaitForElementState
).waitForElementState('disabled');

const events = await getEventPayloads(
driver,
mockedEndpoints as MockedEndpoint[],
);
assert.equal(events.length, 2);

await driver.clickElementAndWaitToDisappear(
'.mm-box button[aria-label="Close"]',
);
await driver.clickElement(selectors.accountOptionsMenuButton);
await driver.clickElement(selectors.globalMenuSettingsButton);
await driver.clickElement(selectors.securityAndPrivacySettings);

const deleteMetaMetricsDataButtonRefreshed = await driver.findElement(
selectors.deleteMetaMetricsDataButton,
);
await (
deleteMetaMetricsDataButtonRefreshed as WebElementWithWaitForElementState
).waitForElementState('disabled');
},
);
});
it('when the user has never opted in for metrics', async function () {
await withFixtures(
{
fixtures: new FixtureBuilder().build(),
defaultGanacheOptions,
title: this.test?.fullTitle(),
testSpecificMock: mockSegment,
},
async ({ driver }: { driver: Driver }) => {
await unlockWallet(driver);

await driver.clickElement(selectors.accountOptionsMenuButton);
await driver.clickElement(selectors.globalMenuSettingsButton);
await driver.clickElement(selectors.securityAndPrivacySettings);
await driver.findElement(selectors.deletMetaMetricsSettings);

const deleteMetaMetricsDataButton = await driver.findElement(
selectors.deleteMetaMetricsDataButton,
);
assert.equal(
await deleteMetaMetricsDataButton.isEnabled(),
false,
'Delete MetaMetrics data button is disabled',
);
},
);
});
});
2 changes: 2 additions & 0 deletions test/e2e/webdriver/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ function wrapElementWithAPI(element, driver) {
return await driver.wait(until.stalenessOf(element), timeout);
case 'visible':
return await driver.wait(until.elementIsVisible(element), timeout);
case 'disabled':
return await driver.wait(until.elementIsDisabled(element), timeout);
default:
throw new Error(`Provided state: '${state}' is not supported`);
}
Expand Down
5 changes: 5 additions & 0 deletions test/e2e/webdriver/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { WebElement, WebElementPromise } from 'selenium-webdriver';

export type WebElementWithWaitForElementState = WebElement & {
waitForElementState: (state: unknown, timeout?: unknown) => WebElementPromise;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as React from 'react';
import { fireEvent } from '@testing-library/react';
import configureStore from '../../../store/store';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import * as Actions from '../../../store/actions';
import { DELETE_METAMETRICS_DATA_MODAL_CLOSE } from '../../../store/actionConstants';
import ClearMetaMetricsData from './clear-metametrics-data';

const mockCloseDeleteMetaMetricsDataModal = jest.fn().mockImplementation(() => {
return {
type: DELETE_METAMETRICS_DATA_MODAL_CLOSE,
};
});

jest.mock('../../../store/actions', () => ({
createMetaMetricsDataDeletionTask: jest.fn(),
}));

jest.mock('../../../ducks/app/app.ts', () => {
return {
hideDeleteMetaMetricsDataModal: () => {
return mockCloseDeleteMetaMetricsDataModal();
},
};
});

describe('ClearMetaMetricsData', () => {
NiranjanaBinoy marked this conversation as resolved.
Show resolved Hide resolved
afterEach(() => {
jest.clearAllMocks();
});

it('should render the data deletion error modal', async () => {
const store = configureStore({});
const { getByText } = renderWithProvider(<ClearMetaMetricsData />, store);

expect(getByText('Delete MetaMetrics data?')).toBeInTheDocument();
expect(
getByText(
'We are about to remove all your MetaMetrics data. Are you sure?',
),
).toBeInTheDocument();
});

it('should call createMetaMetricsDataDeletionTask when Clear button is clicked', () => {
const store = configureStore({});
const { getByText } = renderWithProvider(<ClearMetaMetricsData />, store);
expect(getByText('Clear')).toBeEnabled();
fireEvent.click(getByText('Clear'));
expect(Actions.createMetaMetricsDataDeletionTask).toHaveBeenCalledTimes(1);
});

it('should call hideDeleteMetaMetricsDataModal when Cancel button is clicked', () => {
const store = configureStore({});
const { getByText } = renderWithProvider(<ClearMetaMetricsData />, store);
expect(getByText('Cancel')).toBeEnabled();
fireEvent.click(getByText('Cancel'));
expect(mockCloseDeleteMetaMetricsDataModal).toHaveBeenCalledTimes(1);
});
});
Loading