Skip to content

Commit

Permalink
feat(metrics): question to activate telemetry + documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
matthieu-crouzet committed Jan 30, 2024
1 parent 380a2cc commit 84eca8e
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 21 deletions.
25 changes: 25 additions & 0 deletions docs/telemetry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Telemetry

You can help the Otter Team to prioritize features and improvements by permitting the Otter team to send command-line command usage statistics to Amadeus. The Otter Team does not collect usage statistics unless you explicitly opt in.

## What is collected?

Usage analytics may include the following information:
- Your operating system (macOS, Linux distribution, Windows) and its version.
- Package manager name and version (local version only).
- Node.js version (local version only).
- Otter version (local version only).
- Command name that was run.
- The time it took to run.
- Project name.
- The schematic/builder options.

> [!WARNING]
> We don't use it but your IP address will also be stored during one month for security reason.
## How to disable telemetry?

To disable it for:
- your project, set `cli.o3rMetrics` to false in your `package.json`.
- your machine, set `O3R_METRICS` to false in your environment variables.
- a builder/schematic run, run it with `--no-o3r-metrics`
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -280,5 +280,8 @@
"tools/@*/*",
"apps/*"
],
"cli": {
"o3rMetrics": true
},
"packageManager": "[email protected]"
}
2 changes: 2 additions & 0 deletions packages/@o3r/extractors/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
},
"dependencies": {
"@microsoft/tsdoc": "~0.14.1",
"inquirer": "~8.2.6",
"jsonschema": "~1.4.1",
"tslib": "^2.5.3",
"typedoc": "~0.25.0"
Expand Down Expand Up @@ -67,6 +68,7 @@
"@o3r/schematics": "workspace:^",
"@o3r/telemetry": "workspace:^",
"@o3r/test-helpers": "workspace:^",
"@types/inquirer": "~8.2.10",
"@types/jest": "~29.5.2",
"@types/node": "^20.0.0",
"@typescript-eslint/eslint-plugin": "^6.19.0",
Expand Down
54 changes: 52 additions & 2 deletions packages/@o3r/extractors/src/core/wrapper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { getPackageManagerRunner } from '@o3r/schematics';
import type { BuilderWrapper } from '@o3r/telemetry';
import { prompt, Question } from 'inquirer';
import { execSync } from 'node:child_process';
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import * as path from 'node:path';

const noopBuilderWrapper: BuilderWrapper = (fn) => fn;

Expand All @@ -8,10 +13,55 @@ const noopBuilderWrapper: BuilderWrapper = (fn) => fn;
* @param builderFn
*/
export const createBuilderWithMetricsIfInstalled: BuilderWrapper = (builderFn) => async (opts, ctx) => {
const packageJsonPath = path.join(ctx.workspaceRoot, 'package.json');
const packageJson = existsSync(packageJsonPath)
? JSON.parse(readFileSync(packageJsonPath, {encoding: 'utf8'}).toString())
: {};
let wrapper: BuilderWrapper = noopBuilderWrapper;
try {
const { createBuilderWithMetrics } = await import('@o3r/telemetry');
wrapper = createBuilderWithMetrics;
} catch {}
if (packageJson.cli?.o3rMetrics) {
wrapper = createBuilderWithMetrics;
}
} catch (e: any) {
// Do not throw if `@o3r/telemetry is not installed
if (packageJson.cli?.o3rMetrics === true) {
ctx.logger.info('`cli.o3rMetrics` is set to true in your package.json, please install the telemetry package with `ng add @o3r/telemetry` to enable the collect of metrics.');
} else if ((!process.env.CI || process.env.CI === 'false') && typeof packageJson.cli?.o3rMetrics === 'undefined') {
ctx.logger.debug('`@o3r/telemetry` is not available.\nAsking to add the dependency\n' + e.toString());

const question: Question = {
type: 'confirm',
name: 'isReplyPositive',
message: `
Would you like to share anonymous usage data about this project with the Otter Team at Amadeus ?
It will help us to improve our tools.
For more details and instructions on how to change these settings, see https://github.com/AmadeusITGroup/otter/blob/main/docs/telemetry/README.md.
`
};
const { isReplyPositive } = await prompt([question]);

if (isReplyPositive) {
const pmr = getPackageManagerRunner(packageJson);

try {
const version = JSON.parse(readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf-8').toString()).version;
execSync(`${pmr} ng add @o3r/telemetry@${version}`);
} catch {}

const { createBuilderWithMetrics } = await import('@o3r/telemetry');
return createBuilderWithMetrics(builderFn)(opts, ctx);
} else {
ctx.logger.info('You can activate it at any time by running `ng add @o3r/telemetry`.');

packageJson.cli ||= {};
packageJson.cli.o3rMetrics = false;

if (existsSync(packageJsonPath)) {
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
}
}
}
}
return wrapper(builderFn)(opts, ctx);
};
57 changes: 54 additions & 3 deletions packages/@o3r/schematics/src/utility/wrapper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import type { JsonObject } from '@angular-devkit/core';
import { askConfirmation } from '@angular/cli/src/utilities/prompt';
import type { SchematicWrapper } from '@o3r/telemetry';
import { NodeDependencyType } from '@schematics/angular/utility/dependencies';
import { readFileSync } from 'node:fs';
import * as path from 'node:path';
import { lastValueFrom } from 'rxjs';
import { NodePackageNgAddTask } from '../tasks/index';

const noopSchematicWrapper: SchematicWrapper = (fn) => fn;

Expand All @@ -7,14 +14,58 @@ const noopSchematicWrapper: SchematicWrapper = (fn) => fn;
* if @o3r/telemetry is installed
* @param schematicFn
*/
export const createSchematicWithMetricsIfInstalled: SchematicWrapper = (schematicFn) => (opts) => async (_, context) => {
export const createSchematicWithMetricsIfInstalled: SchematicWrapper = (schematicFn) => (opts) => async (tree, context) => {
let wrapper: SchematicWrapper = noopSchematicWrapper;
const packageJsonPath = '/angular.json';
const packageJson = tree.exists(packageJsonPath) ? tree.readJson(packageJsonPath) as JsonObject : {};
try {
const { createSchematicWithMetrics } = await import('@o3r/telemetry');
wrapper = createSchematicWithMetrics;
if ((packageJson.cli as JsonObject)?.o3rMetrics) {
wrapper = createSchematicWithMetrics;
}
} catch (e: any) {
// Do not throw if `@o3r/telemetry is not installed
context.logger.debug('`@o3r/telemetry` is not available\n' + e.toString());
if ((packageJson.cli as JsonObject)?.o3rMetrics === true) {
context.logger.info('`cli.o3rMetrics` is set to true in your package.json, please install the telemetry package with `ng add @o3r/telemetry` to enable the collect of metrics.');
} else if (context.interactive && (packageJson.cli as JsonObject)?.o3rMetrics !== false) {
context.logger.debug('`@o3r/telemetry` is not available.\nAsking to add the dependency\n' + e.toString());

const isReplyPositive = await askConfirmation(
`
Would you like to share anonymous usage data about this project with the Otter Team at Amadeus ?
It will help us to improve our tools.
For more details and instructions on how to change these settings, see https://github.com/AmadeusITGroup/otter/blob/main/docs/telemetry/README.md.
`,
false
);

if (isReplyPositive) {
const version = JSON.parse(readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf-8').toString()).version;
context.addTask(
new NodePackageNgAddTask(
'@o3r/telemetry',
{
dependencyType: NodeDependencyType.Dev,
version
}
)
);
await lastValueFrom(context.engine.executePostTasks());

const { createSchematicWithMetrics } = await import('@o3r/telemetry');
return createSchematicWithMetrics(schematicFn)(opts);
} else {
context.logger.info('You can activate it at any time by running `ng add @o3r/telemetry`.');

packageJson.cli ||= {};
(packageJson.cli as JsonObject).o3rMetrics = false;

tree.overwrite(
packageJsonPath,
JSON.stringify(packageJson, null, 2)
);
}
}
}
return wrapper(schematicFn)(opts);
};
17 changes: 15 additions & 2 deletions packages/@o3r/telemetry/schematics/ng-add/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { noop, Rule } from '@angular-devkit/schematics';
import type { JsonObject } from '@angular-devkit/core';
import type { Rule } from '@angular-devkit/schematics';
import type { NgAddSchematicsSchema } from './schema';

/**
* Add Otter telemetry to an Otter Project
* @param options
*/
export function ngAdd(_options: NgAddSchematicsSchema): Rule {
return noop();
return (tree, context) => {
if (tree.exists('/package.json')) {
const packageJson = tree.readJson('/package.json') as JsonObject;
packageJson.cli ||= {};
(packageJson.cli as JsonObject).o3rMetrics = true;
tree.overwrite('/package.json', JSON.stringify(packageJson, null, 2));
}
context.logger.info(`
By installing '@o3r/telemetry', you have activated the collect of anonymous usage data.
You can deactivate it at any time by changing 'cli.o3rMetrics' in 'package.json' or by setting 'O3R_METRICS' to false as environment variable.
You can also temporarily deactivate it by running your builder or schematic with '--no-o3r-metrics'.
`);
};
}
29 changes: 23 additions & 6 deletions packages/@o3r/telemetry/src/builders/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import type {
BuilderContext,
BuilderOutput
} from '@angular-devkit/architect';
import { getEnvironmentInfo } from '../environment/index';
import { existsSync, readFileSync } from 'node:fs';
import * as path from 'node:path';
import { performance } from 'node:perf_hooks';
import { getEnvironmentInfo } from '../environment/index';
import { BuilderMetricData, sendData as defaultSendData, type SendDataFn } from '../sender';

type BuilderWrapperFn<S, O extends BuilderOutput = BuilderOutput> =
Expand Down Expand Up @@ -59,11 +61,26 @@ export const createBuilderWithMetrics: BuilderWrapper = (builderFn, sendData = d
error
};
context.logger.debug(JSON.stringify(data, null, 2));
void sendData(data, context.logger).catch((e) => {
// Do not throw error if we don't manage to collect data
const err = (e instanceof Error ? e : new Error(error));
context.logger.error(err.stack || err.toString());
});
const packageJsonPath = path.join(context.currentDirectory, 'package.json');
const packageJson = existsSync(packageJsonPath) ? JSON.parse(readFileSync(packageJsonPath, 'utf-8')) : {};
const shouldSendData = !!(
(options as any).o3rMetrics
?? ((process.env.O3R_METRICS || '').length > 0 ? process.env.O3R_METRICS !== 'false' : undefined)
?? packageJson.cli?.o3rMetrics
);
if (shouldSendData) {
if (typeof ((options as any).o3rMetrics ?? typeof process.env.O3R_METRICS) === 'undefined') {
context.logger.info(
'Telemetry is globally activated for the project (`cli.o3rMetrics` in package.json).'
+ 'If you personnaly don\'t want to send telemetry, you can deactivate it by setting `O3R_METRICS` to false in your environment variables, or calling builder with `--no-o3r-metrics`.'
);
}
void sendData(data, context.logger).catch((e) => {
// Do not throw error if we don't manage to collect data
const err = (e instanceof Error ? e : new Error(error));
context.logger.error(err.stack || err.toString());
});
}
}
};

3 changes: 2 additions & 1 deletion packages/@o3r/telemetry/src/environment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ export const getEnvironmentInfo = (): EnvironmentMetricData => {
os: osInfo,
node: nodeInfo,
packageManager: packageManagerInfo,
otter: otterInfo, ci,
otter: otterInfo,
ci,
...(projectName ? { project: { name: projectName } } : {})
};
};
Expand Down
25 changes: 20 additions & 5 deletions packages/@o3r/telemetry/src/schematics/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { JsonObject } from '@angular-devkit/core';
import { callRule, Rule } from '@angular-devkit/schematics';
import { performance } from 'node:perf_hooks';
import { lastValueFrom } from 'rxjs';
Expand Down Expand Up @@ -48,11 +49,25 @@ export const createSchematicWithMetrics: SchematicWrapper =
error
};
context.logger.debug(JSON.stringify(data, null, 2));
void sendData(data, context.logger).catch((e) => {
// Do not throw error if we don't manage to collect data
const err = (e instanceof Error ? e : new Error(error));
context.logger.error(err.stack || err.toString());
});
const packageJson = (tree.exists('/package.json') ? tree.readJson('/package.json') : {}) as JsonObject;
const shouldSendData = !!(
(options as any).o3rMetrics
?? ((process.env.O3R_METRICS || '').length > 0 ? process.env.O3R_METRICS !== 'false' : undefined)
?? (packageJson.cli as JsonObject)?.o3rMetrics
);
if (shouldSendData) {
if (typeof ((options as any).o3rMetrics ?? typeof process.env.O3R_METRICS) === 'undefined') {
context.logger.info(
'Telemetry is globally activated for the project (`cli.o3rMetrics` in package.json).'
+ 'If you personnaly don\'t want to send telemetry, you can deactivate it by setting `O3R_METRICS` to false in your environment variables, or calling schematic with `--no-o3r-metrics`.'
);
}
void sendData(data, context.logger).catch((e) => {
// Do not throw error if we don't manage to collect data
const err = (e instanceof Error ? e : new Error(error));
context.logger.error(err.stack || err.toString());
});
}
}
};

25 changes: 23 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7498,6 +7498,7 @@ __metadata:
"@o3r/schematics": "workspace:^"
"@o3r/telemetry": "workspace:^"
"@o3r/test-helpers": "workspace:^"
"@types/inquirer": "npm:~8.2.10"
"@types/jest": "npm:~29.5.2"
"@types/node": "npm:^20.0.0"
"@typescript-eslint/eslint-plugin": "npm:^6.19.0"
Expand All @@ -7509,6 +7510,7 @@ __metadata:
eslint-plugin-jsdoc: "npm:~48.0.0"
eslint-plugin-prefer-arrow: "npm:~1.2.3"
eslint-plugin-unicorn: "npm:^50.0.0"
inquirer: "npm:~8.2.6"
intl-messageformat: "npm:~10.5.1"
jest: "npm:~29.7.0"
jest-environment-jsdom: "npm:~29.7.0"
Expand Down Expand Up @@ -11732,6 +11734,16 @@ __metadata:
languageName: node
linkType: hard

"@types/inquirer@npm:~8.2.10":
version: 8.2.10
resolution: "@types/inquirer@npm:8.2.10"
dependencies:
"@types/through": "npm:*"
rxjs: "npm:^7.2.0"
checksum: d7c0c5ec95af583191942ac33f8af2eb1fe839da6b4560277a8c251fa289f2dd3a5d14850baf910343700646200258ecff89dc9e1d57df29c16a1082d91a5ae3
languageName: node
linkType: hard

"@types/ioredis@npm:^4.27.1":
version: 4.28.10
resolution: "@types/ioredis@npm:4.28.10"
Expand Down Expand Up @@ -12206,6 +12218,15 @@ __metadata:
languageName: node
linkType: hard

"@types/through@npm:*":
version: 0.0.33
resolution: "@types/through@npm:0.0.33"
dependencies:
"@types/node": "npm:*"
checksum: fd0b73f873a64ed5366d1d757c42e5dbbb2201002667c8958eda7ca02fff09d73de91360572db465ee00240c32d50c6039ea736d8eca374300f9664f93e8da39
languageName: node
linkType: hard

"@types/tough-cookie@npm:*":
version: 4.0.5
resolution: "@types/tough-cookie@npm:4.0.5"
Expand Down Expand Up @@ -20893,7 +20914,7 @@ __metadata:
languageName: node
linkType: hard

"inquirer@npm:8.2.6":
"inquirer@npm:8.2.6, inquirer@npm:~8.2.6":
version: 8.2.6
resolution: "inquirer@npm:8.2.6"
dependencies:
Expand Down Expand Up @@ -27975,7 +27996,7 @@ __metadata:
languageName: node
linkType: hard

"rxjs@npm:7.8.1, rxjs@npm:^7.5.5, rxjs@npm:^7.8.0, rxjs@npm:^7.8.1":
"rxjs@npm:7.8.1, rxjs@npm:^7.2.0, rxjs@npm:^7.5.5, rxjs@npm:^7.8.0, rxjs@npm:^7.8.1":
version: 7.8.1
resolution: "rxjs@npm:7.8.1"
dependencies:
Expand Down

0 comments on commit 84eca8e

Please sign in to comment.