Skip to content

Commit 85e6191

Browse files
authored
feat: usage data consent (#685)
* feat: user consent for telemetry * fix: removing unused vars * fix: lint errors * feat: integrate with usage_data_emitter * fix: update API.md * update comment * fix: refactor and move LocalConfigController to platform-core * fix: add encoding when reading config file * fix: adding additional file verification check * fix: update unit test * fix: export all types to update API.md * fix: update API.md * fix: open file using file descriptor * fix: update fs mocks in unit test * fix: update fs.open mock * fix: mock fs open for unit test
1 parent d010539 commit 85e6191

22 files changed

+594
-17
lines changed

.changeset/forty-geese-decide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'create-amplify': patch
3+
---
4+
5+
Adding message about usage data tracking when creating new project

.changeset/nine-socks-attack.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@aws-amplify/backend-cli': patch
3+
---
4+
5+
Added subcommands under configure data tracking preferences

.changeset/slimy-cups-draw.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@aws-amplify/platform-core': patch
3+
'@aws-amplify/backend-cli': patch
4+
---
5+
6+
integrate usage data tracking consent with usage-data-emitter

.eslint_dictionary.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"localhost",
6363
"lsof",
6464
"lstat",
65+
"macos",
6566
"matchers",
6667
"mfas",
6768
"mkdtemp",

package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/src/commands/configure/configure_command_factory.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { configControllerFactory } from '@aws-amplify/platform-core';
12
import { CommandModule } from 'yargs';
23
import { ConfigureProfileCommand } from './configure_profile_command.js';
34
import { ProfileController } from './profile_controller.js';
45
import { ConfigureCommand } from './configure_command.js';
6+
import { ConfigureTelemetryCommand } from './telemetry/configure_telemetry_command.js';
57
/**
68
* Creates a configure command.
79
*/
@@ -10,8 +12,12 @@ export const createConfigureCommand = (): CommandModule<object> => {
1012
const configureProfileCommand = new ConfigureProfileCommand(
1113
profileController
1214
);
15+
const configureTelemetryCommand = new ConfigureTelemetryCommand(
16+
configControllerFactory.getInstance('usage_data_preferences.json')
17+
);
1318

1419
return new ConfigureCommand([
1520
configureProfileCommand as unknown as CommandModule,
21+
configureTelemetryCommand as unknown as CommandModule,
1622
]);
1723
};
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Printer } from '@aws-amplify/cli-core';
2+
import { beforeEach, describe, it, mock } from 'node:test';
3+
import yargs, { CommandModule } from 'yargs';
4+
import assert from 'node:assert';
5+
import { TestCommandRunner } from '../../../test-utils/command_runner.js';
6+
import { ConfigureTelemetryCommand } from './configure_telemetry_command.js';
7+
import {
8+
USAGE_DATA_TRACKING_ENABLED,
9+
configControllerFactory,
10+
} from '@aws-amplify/platform-core';
11+
12+
void describe('configure command', () => {
13+
const mockedConfigControllerSet = mock.fn();
14+
mock.method(configControllerFactory, 'getInstance', () => ({
15+
set: mockedConfigControllerSet,
16+
}));
17+
const telemetryCommand = new ConfigureTelemetryCommand(
18+
configControllerFactory.getInstance('usage_data_preferences.json')
19+
);
20+
const parser = yargs().command(telemetryCommand as unknown as CommandModule);
21+
const commandRunner = new TestCommandRunner(parser);
22+
23+
const mockedPrint = mock.method(Printer, 'print');
24+
25+
beforeEach(() => {
26+
mockedPrint.mock.resetCalls();
27+
mockedConfigControllerSet.mock.resetCalls();
28+
});
29+
30+
void it('enable telemetry & updates local config', async () => {
31+
await commandRunner.runCommand(`telemetry enable`);
32+
assert.match(
33+
mockedPrint.mock.calls[0].arguments[0],
34+
/You have enabled telemetry data collection/
35+
);
36+
assert.strictEqual(
37+
mockedConfigControllerSet.mock.calls[0].arguments[0],
38+
USAGE_DATA_TRACKING_ENABLED
39+
);
40+
assert.strictEqual(
41+
mockedConfigControllerSet.mock.calls[0].arguments[1],
42+
true
43+
);
44+
});
45+
46+
void it('disables telemetry & updates local config', async () => {
47+
await commandRunner.runCommand(`telemetry disable`);
48+
assert.match(
49+
mockedPrint.mock.calls[0].arguments[0],
50+
/You have disabled telemetry data collection/
51+
);
52+
assert.strictEqual(
53+
mockedConfigControllerSet.mock.calls[0].arguments[0],
54+
USAGE_DATA_TRACKING_ENABLED
55+
);
56+
assert.strictEqual(
57+
mockedConfigControllerSet.mock.calls[0].arguments[1],
58+
false
59+
);
60+
});
61+
62+
void it('if subcommand is not defined, it should list of subcommands and demandCommand', async () => {
63+
await commandRunner.runCommand(`telemetry`);
64+
assert.match(
65+
mockedPrint.mock.calls[0].arguments[0],
66+
/Not enough non-option arguments: got 0, need at least 1/
67+
);
68+
assert.strictEqual(mockedConfigControllerSet.mock.callCount(), 0);
69+
});
70+
});
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { Printer } from '@aws-amplify/cli-core';
2+
import {
3+
ConfigurationController,
4+
USAGE_DATA_TRACKING_ENABLED,
5+
} from '@aws-amplify/platform-core';
6+
import { Argv, CommandModule } from 'yargs';
7+
import { handleCommandFailure } from '../../../command_failure_handler.js';
8+
/**
9+
* Command to configure AWS Amplify profile.
10+
*/
11+
export class ConfigureTelemetryCommand implements CommandModule<object> {
12+
/**
13+
* @inheritDoc
14+
*/
15+
readonly command: string;
16+
17+
/**
18+
* @inheritDoc
19+
*/
20+
readonly describe: string;
21+
22+
/**
23+
* Configure profile command.
24+
*/
25+
constructor(private readonly configController: ConfigurationController) {
26+
this.command = 'telemetry';
27+
this.describe = 'Configure anonymous usage data collection';
28+
}
29+
30+
/**
31+
* @inheritDoc
32+
*/
33+
handler = () => {
34+
// CommandModule requires handler implementation. But this is never called if top level command
35+
// is configured to require subcommand.
36+
// Help is printed by default in that case before ever attempting to call handler.
37+
throw new Error('Top level generate handler should never be called');
38+
};
39+
40+
/**
41+
* @inheritDoc
42+
*/
43+
builder = (yargs: Argv) => {
44+
return yargs
45+
.command('enable', 'Enable anonymous data collection', {}, async () => {
46+
await this.configController.set(USAGE_DATA_TRACKING_ENABLED, true);
47+
48+
Printer.print('You have enabled telemetry data collection');
49+
})
50+
.command('disable', 'Disable anonymous data collection', {}, async () => {
51+
await this.configController.set(USAGE_DATA_TRACKING_ENABLED, false);
52+
53+
Printer.print('You have disabled telemetry data collection');
54+
})
55+
.demandCommand()
56+
.strictCommands()
57+
.recommendCommands()
58+
.fail((msg, err) => {
59+
handleCommandFailure(msg, err, yargs);
60+
yargs.exit(1, err);
61+
});
62+
};
63+
}

packages/cli/src/commands/sandbox/sandbox_command_factory.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,14 @@ export const createSandboxCommand = (): CommandModule<
5050
credentialProvider
5151
);
5252

53+
const libraryVersion =
54+
new PackageJsonReader().read(
55+
fileURLToPath(new URL('../../../package.json', import.meta.url))
56+
).version ?? '';
57+
5358
const eventHandlerFactory = new SandboxEventHandlerFactory(
5459
sandboxBackendIdentifierResolver,
55-
new UsageDataEmitterFactory().getInstance(
56-
new PackageJsonReader().read(
57-
fileURLToPath(new URL('../../../package.json', import.meta.url))
58-
).version ?? ''
59-
)
60+
async () => await new UsageDataEmitterFactory().getInstance(libraryVersion)
6061
);
6162

6263
const commandMiddleWare = new CommandMiddleware();

packages/cli/src/commands/sandbox/sandbox_event_handler_factory.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ void describe('sandbox_event_handler_factory', () => {
4040
name: 'name',
4141
type: 'sandbox',
4242
}),
43-
usageDataEmitterMock
43+
async () => usageDataEmitterMock
4444
);
4545

4646
afterEach(() => {

0 commit comments

Comments
 (0)