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 a new controller for Metametrics Data Deletion #24503

Merged
merged 51 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
5dce95e
adding a new controller and service class for metametrics deletion
NiranjanaBinoy May 13, 2024
e1200ab
adding env variable
NiranjanaBinoy May 14, 2024
e175f0c
updating .metamaskrc.dist with new variables
NiranjanaBinoy May 14, 2024
eaba4e5
updating build.yml entries
NiranjanaBinoy May 14, 2024
666b723
updating build.yml entries to fix circle ci failure
NiranjanaBinoy May 14, 2024
74b4720
updating metamask-controller and action with the new controller
NiranjanaBinoy May 15, 2024
f0c988a
updating setupsentry
NiranjanaBinoy May 16, 2024
9dd231d
fixing circle ci failure
NiranjanaBinoy May 16, 2024
a9dba40
adding new state variable and addressing review comments
NiranjanaBinoy May 16, 2024
7bfb0b8
updating the empty check
NiranjanaBinoy May 16, 2024
a60c9c5
checking for the response being empty
NiranjanaBinoy May 17, 2024
aa59115
converting the response to json
NiranjanaBinoy May 18, 2024
850fd00
removing the return
NiranjanaBinoy May 20, 2024
ac041f5
updating the controller pattern, renaming the env variable, updating …
NiranjanaBinoy May 21, 2024
f01ec84
addressing review comments
NiranjanaBinoy May 22, 2024
63ae627
fixing the actions
NiranjanaBinoy May 22, 2024
eb45e7b
lint fix
NiranjanaBinoy May 23, 2024
dda5a2b
updating env variable and e2e errors
NiranjanaBinoy May 23, 2024
b0bd00d
feat: Add retry policy to the data deletion service (#24716)
Gudahtt May 24, 2024
9109601
updating teh actions and test
NiranjanaBinoy May 27, 2024
7c8ed9d
updating teh date format
NiranjanaBinoy May 27, 2024
5aeb0c2
fixing rebase conflict
NiranjanaBinoy May 27, 2024
c92183b
Add additional data deletion service configuration options (#24806)
Gudahtt May 28, 2024
14b8df0
updating the error states for e2e
NiranjanaBinoy May 28, 2024
bcbf1c6
refactor: Create dedicated services directory (#24863)
Gudahtt May 29, 2024
fb49511
refactor: Decouple the data deletion controller (#24870)
Gudahtt May 29, 2024
19f8fc2
test: Add a MetaMetricsDataDeletionController test (#24879)
Gudahtt May 31, 2024
8fdadd1
adding a new property to state
NiranjanaBinoy Jun 3, 2024
f6ff9a6
Add tests for `createDataDeletionRegulationTask` (#24816)
Gudahtt Jun 3, 2024
6f8ba43
updating test files of the controller and service class
NiranjanaBinoy Jun 4, 2024
3831b9c
fixing lint error
NiranjanaBinoy Jun 4, 2024
bc25613
fixing rebase errors
NiranjanaBinoy Jun 7, 2024
2273c5f
rebase fix
NiranjanaBinoy Jun 19, 2024
330313e
fixing lint error
NiranjanaBinoy Jun 19, 2024
cc9a5ee
addressing review comments
NiranjanaBinoy Jul 4, 2024
5e02705
updating the actions file
NiranjanaBinoy Jul 4, 2024
31b3b66
addressing review comments
NiranjanaBinoy Jul 8, 2024
102b024
fixing unit test and e2e failures
NiranjanaBinoy Jul 9, 2024
5fa9f65
fixing e2e failures
NiranjanaBinoy Jul 9, 2024
631a80d
removed the setHasMetaMetricsDataRecorded instead update the variable…
NiranjanaBinoy Jul 10, 2024
3a9be3b
fixing e2e failure
NiranjanaBinoy Jul 11, 2024
d03f030
correcting variable name
NiranjanaBinoy Jul 12, 2024
2148e37
updating the timestamp and removing teh boolean prop from state
NiranjanaBinoy Jul 17, 2024
e7e6dcf
addressing review comments
NiranjanaBinoy Jul 17, 2024
98cefc6
removing the background field for error.spec.js
NiranjanaBinoy Jul 18, 2024
cc92dee
fixing error.spec
NiranjanaBinoy Jul 18, 2024
12db029
updated package.json after rebase
NiranjanaBinoy Aug 19, 2024
d5f2d75
updating lavamoat
NiranjanaBinoy Aug 20, 2024
6a77df8
updating teh default state
NiranjanaBinoy Aug 29, 2024
28e4eec
fixing jest config file
NiranjanaBinoy Aug 29, 2024
886abe4
addressing review comments
NiranjanaBinoy Sep 12, 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
2 changes: 2 additions & 0 deletions .metamaskrc.dist
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ INFURA_PROJECT_ID=00000000000
;PASSWORD=METAMASK PASSWORD
;SEGMENT_WRITE_KEY=
;BRIDGE_USE_DEV_APIS=
;ANALYTICS_DATA_DELETION_SOURCE_ID=
;ANALYTICS_DATA_DELETION_ENDPOINT=
;SWAPS_USE_DEV_APIS=
;PORTFOLIO_URL=
;TRANSACTION_SECURITY_PROVIDER=
Expand Down
5 changes: 5 additions & 0 deletions app/scripts/constants/sentry-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ export const SENTRY_BACKGROUND_STATE = {
traits: false,
dataCollectionForMarketing: false,
marketingCampaignCookieId: true,
latestNonAnonymousEventTimestamp: true,
},
MetaMetricsDataDeletionController: {
metaMetricsDataDeletionId: true,
metaMetricsDataDeletionTimestamp: true,
},
NameController: {
names: false,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { ControllerMessenger } from '@metamask/base-controller';
import {
MetaMetricsDataDeletionController,
type MetaMetricsDataDeletionControllerMessengerActions,
} from './metametrics-data-deletion';

describe('MetaMetricsDataDeletionController', () => {
describe('createMetaMetricsDataDeletionTask', () => {
it('creates a data deletion task and stores ID when user is participating in metrics tracking', async () => {
const mockMetaMetricsId = 'mockId';
const mockTaskId = 'mockTaskId';
const { controller, dataDeletionService } = setupController({
options: {
getMetaMetricsId: jest.fn().mockReturnValue(mockMetaMetricsId),
dataDeletionService: {
createDataDeletionRegulationTask: jest
.fn()
.mockResolvedValue(mockTaskId),
fetchDeletionRegulationStatus: jest
.fn()
.mockResolvedValue('UNKNOWN'),
},
},
});

await controller.createMetaMetricsDataDeletionTask();
expect(
dataDeletionService.createDataDeletionRegulationTask,
).toHaveBeenCalledWith(mockMetaMetricsId);
expect(
dataDeletionService.createDataDeletionRegulationTask,
).toHaveBeenCalledTimes(1);
expect(
dataDeletionService.fetchDeletionRegulationStatus,
).toHaveBeenCalledTimes(1);
expect(controller.state).toStrictEqual({
metaMetricsDataDeletionId: mockTaskId,
metaMetricsDataDeletionTimestamp: expect.any(Number),
metaMetricsDataDeletionStatus: 'UNKNOWN',
});
});
it('creates a data deletion task and stores ID when user is not currently participating in metrics tracking', async () => {
const mockMetaMetricsId = 'mockId';
const mockTaskId = 'mockTaskId';
const { controller, dataDeletionService } = setupController({
options: {
getMetaMetricsId: jest.fn().mockReturnValue(mockMetaMetricsId),
dataDeletionService: {
createDataDeletionRegulationTask: jest
.fn()
.mockResolvedValue(mockTaskId),
fetchDeletionRegulationStatus: jest
.fn()
.mockResolvedValue('UNKNOWN'),
},
},
});

await controller.createMetaMetricsDataDeletionTask();

expect(
dataDeletionService.createDataDeletionRegulationTask,
).toHaveBeenCalledTimes(1);
expect(
dataDeletionService.fetchDeletionRegulationStatus,
).toHaveBeenCalledTimes(1);
expect(
dataDeletionService.createDataDeletionRegulationTask,
).toHaveBeenCalledWith(mockMetaMetricsId);
expect(controller.state).toStrictEqual({
metaMetricsDataDeletionId: mockTaskId,
metaMetricsDataDeletionTimestamp: expect.any(Number),
metaMetricsDataDeletionStatus: 'UNKNOWN',
});
});

it('fails to creates a data deletion task when user has never participating in metrics tracking', async () => {
const { controller } = setupController({
options: {
getMetaMetricsId: jest.fn().mockReturnValue(null),
},
});
await expect(
controller.createMetaMetricsDataDeletionTask(),
).rejects.toThrow();
expect(controller.state).toStrictEqual({
metaMetricsDataDeletionId: null,
metaMetricsDataDeletionTimestamp: expect.any(Number),
});
});
});
describe('updateDataDeletionTaskStatus', () => {
it('fetches and stores status of the delete regulation using delete regulation ID', async () => {
const mockMetaMetricsId = 'mockId';
const mockTaskId = 'mockTaskId';
const { controller, dataDeletionService } = setupController({
options: {
getMetaMetricsId: jest.fn().mockReturnValue(mockMetaMetricsId),
dataDeletionService: {
createDataDeletionRegulationTask: jest
.fn()
.mockResolvedValue(mockTaskId),
fetchDeletionRegulationStatus: jest
.fn()
.mockResolvedValue('UNKNOWN'),
},
},
});
await controller.createMetaMetricsDataDeletionTask();
await controller.updateDataDeletionTaskStatus();
expect(
dataDeletionService.fetchDeletionRegulationStatus,
).toHaveBeenCalledTimes(2);
expect(
dataDeletionService.fetchDeletionRegulationStatus,
).toHaveBeenCalledWith(mockTaskId);
expect(controller.state).toStrictEqual({
metaMetricsDataDeletionId: mockTaskId,
metaMetricsDataDeletionTimestamp: expect.any(Number),
metaMetricsDataDeletionStatus: 'UNKNOWN',
});
});
});
});

/**
* Setup a test controller instance.
*
* @param options - Setup options.
* @param options.options - Controller constructor options.
* @returns The test controller, a messenger instance, and related mocks.
*/
function setupController({
options,
}: {
options?: Partial<
ConstructorParameters<typeof MetaMetricsDataDeletionController>[0]
>;
} = {}): {
controller: MetaMetricsDataDeletionController;
dataDeletionService: ConstructorParameters<
typeof MetaMetricsDataDeletionController
>[0]['dataDeletionService'];
messenger: ControllerMessenger<
MetaMetricsDataDeletionControllerMessengerActions,
never
>;
} {
const messenger = new ControllerMessenger<
MetaMetricsDataDeletionControllerMessengerActions,
never
>();
const mockCreateDataDeletionRegulationTaskResponse = 'mockRegulateId';
const mockFetchDeletionRegulationStatusResponse = 'UNKNOWN';
const mockDataDeletionService = {
createDataDeletionRegulationTask: jest
.fn()
.mockResolvedValue(mockCreateDataDeletionRegulationTaskResponse),
fetchDeletionRegulationStatus: jest
.fn()
.mockResolvedValue(mockFetchDeletionRegulationStatusResponse),
...options?.dataDeletionService,
};
const constructorOptions = {
dataDeletionService: mockDataDeletionService,
getMetaMetricsId: jest.fn().mockReturnValue('mockMetaMetricsId'),
messenger: messenger.getRestricted({
name: 'MetaMetricsDataDeletionController',
allowedActions: [],
allowedEvents: [],
}),
...options,
};
const controller = new MetaMetricsDataDeletionController(constructorOptions);

return {
controller,
dataDeletionService: constructorOptions.dataDeletionService,
messenger,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import {
BaseController,
RestrictedControllerMessenger,
} from '@metamask/base-controller';
import { PublicInterface } from '@metamask/utils';
import type { DataDeletionService } from '../../services/data-deletion-service';
import { DeleteRegulationStatus } from '../../../../shared/constants/metametrics';

// Unique name for the controller
const controllerName = 'MetaMetricsDataDeletionController';

/**
* Timestamp at which regulation response is returned.
*/
export type DataDeleteTimestamp = number;
/**
* Regulation Id retuned while creating a delete regulation.
*/
export type DataDeleteRegulationId = string | null;

/**
* MetaMetricsDataDeletionController controller state
* metaMetricsDataDeletionId - Regulation Id retuned while creating a delete regulation.
* metaMetricsDataDeletionTimestamp - Timestamp at which the most recent regulation is created/requested for.
* metaMetricsDataDeletionStatus - Status of the current delete regulation.
*/
export type MetaMetricsDataDeletionState = {
metaMetricsDataDeletionId: DataDeleteRegulationId;
metaMetricsDataDeletionTimestamp: DataDeleteTimestamp;
metaMetricsDataDeletionStatus?: DeleteRegulationStatus;
};

const getDefaultState = (): MetaMetricsDataDeletionState => {
return {
metaMetricsDataDeletionId: null,
metaMetricsDataDeletionTimestamp: 0,
};
};

// Metadata for the controller state
const metadata = {
metaMetricsDataDeletionId: {
persist: true,
anonymous: true,
},
metaMetricsDataDeletionTimestamp: {
persist: true,
anonymous: true,
},
metaMetricsDataDeletionStatus: {
persist: true,
anonymous: true,
},
};

// Describes the action creating the delete regulation task
export type CreateMetaMetricsDataDeletionTaskAction = {
type: `${typeof controllerName}:createMetaMetricsDataDeletionTask`;
handler: MetaMetricsDataDeletionController['createMetaMetricsDataDeletionTask'];
};

// Describes the action to check the existing regulation status
export type UpdateDataDeletionTaskStatusAction = {
type: `${typeof controllerName}:updateDataDeletionTaskStatus`;
handler: MetaMetricsDataDeletionController['updateDataDeletionTaskStatus'];
};

// Union of all possible actions for the messenger
export type MetaMetricsDataDeletionControllerMessengerActions =
| CreateMetaMetricsDataDeletionTaskAction
| UpdateDataDeletionTaskStatusAction;

// Type for the messenger of MetaMetricsDataDeletionController
export type MetaMetricsDataDeletionControllerMessenger =
RestrictedControllerMessenger<
typeof controllerName,
MetaMetricsDataDeletionControllerMessengerActions,
never,
never,
never
>;

/**
* Controller responsible for maintaining
* state related to Metametrics data deletion
*/
export class MetaMetricsDataDeletionController extends BaseController<
typeof controllerName,
MetaMetricsDataDeletionState,
MetaMetricsDataDeletionControllerMessenger
> {
#dataDeletionService: PublicInterface<DataDeletionService>;

#getMetaMetricsId: () => string | null;

/**
* Creates a MetaMetricsDataDeletionController instance.
*
* @param args - The arguments to this function.
* @param args.dataDeletionService - The service used for deleting data.
* @param args.messenger - Messenger used to communicate with BaseV2 controller.
* @param args.state - Initial state to set on this controller.
* @param args.getMetaMetricsId - A function that returns the current MetaMetrics ID.
*/
constructor({
dataDeletionService,
messenger,
state,
getMetaMetricsId,
}: {
dataDeletionService: PublicInterface<DataDeletionService>;
messenger: MetaMetricsDataDeletionControllerMessenger;
state?: Partial<MetaMetricsDataDeletionState>;
getMetaMetricsId: () => string | null;
MajorLift marked this conversation as resolved.
Show resolved Hide resolved
}) {
// Call the constructor of BaseControllerV2
super({
messenger,
metadata,
name: controllerName,
state: { ...getDefaultState(), ...state },
});
this.#getMetaMetricsId = getMetaMetricsId;
this.#dataDeletionService = dataDeletionService;
this.#registerMessageHandlers();
}

/**
* Constructor helper for registering this controller's messaging system
* actions.
*/
#registerMessageHandlers(): void {
this.messagingSystem.registerActionHandler(
`${controllerName}:createMetaMetricsDataDeletionTask`,
this.createMetaMetricsDataDeletionTask.bind(this),
);

this.messagingSystem.registerActionHandler(
`${controllerName}:updateDataDeletionTaskStatus`,
this.updateDataDeletionTaskStatus.bind(this),
);
}
Gudahtt marked this conversation as resolved.
Show resolved Hide resolved

/**
* Creating the delete regulation using source regulation
*
*/
async createMetaMetricsDataDeletionTask(): Promise<void> {
NiranjanaBinoy marked this conversation as resolved.
Show resolved Hide resolved
const metaMetricsId = this.#getMetaMetricsId();
if (!metaMetricsId) {
throw new Error('MetaMetrics ID not found');
}

const deleteRegulateId =
await this.#dataDeletionService.createDataDeletionRegulationTask(
metaMetricsId,
);
this.update((state) => {
Copy link
Member

Choose a reason for hiding this comment

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

We should clear metaMetricsDataDeletionStatus here as well. If it's set, then it must have been pertaining to a previous deletion request, so it would be inaccurate at this point.

The same goes for hasMetaMetricsDataRecorded as well, though in my other comment I suggest removing that state altogether.

Copy link
Contributor

Choose a reason for hiding this comment

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

@NiranjanaBinoy was this comment addressed? I see that this.updateDataDeletionTaskStatus() is called, but it does not clear the status, rather it updates it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, now metaMetricsDataDeletionStatus will get updated with the current/newest request status with this call to this.updateDataDeletionTaskStatus().

state.metaMetricsDataDeletionId = deleteRegulateId ?? null;
state.metaMetricsDataDeletionTimestamp = Date.now();
});
await this.updateDataDeletionTaskStatus();
}

/**
* To check the status of the current delete regulation.
*/
async updateDataDeletionTaskStatus(): Promise<void> {
const deleteRegulationId = this.state.metaMetricsDataDeletionId;
if (!deleteRegulationId) {
throw new Error('Delete Regulation id not found');
}

const deletionStatus =
await this.#dataDeletionService.fetchDeletionRegulationStatus(
deleteRegulationId,
);

this.update((state) => {
state.metaMetricsDataDeletionStatus = deletionStatus ?? undefined;
});
}
}
Loading