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 25, 2024
1 parent 0536b3c commit 6cd75fa
Show file tree
Hide file tree
Showing 11 changed files with 202 additions and 15 deletions.
20 changes: 20 additions & 0 deletions docs/telemetry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 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.

## How to disable telemetry?

To globally disable it, you can set `cli.o3rMetrics` to false in your `angular.json` (or `nx.json`).
To disable it only for a run, you can run your builder/schematic with `--o3r-metrics=false`
3 changes: 2 additions & 1 deletion nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"enable": true,
"environment": "all",
"path": ".cache/angular"
}
},
"o3rMetrics": true
},
"namedInputs": {
"global": [
Expand Down
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
60 changes: 58 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,61 @@ const noopBuilderWrapper: BuilderWrapper = (fn) => fn;
* @param builderFn
*/
export const createBuilderWithMetricsIfInstalled: BuilderWrapper = (builderFn) => async (opts, ctx) => {
const angularJsonPath = path.join(ctx.workspaceRoot, 'angular.json');
const nxJsonPath = path.join(ctx.workspaceRoot, 'nx.json');
const configPath = existsSync(angularJsonPath)
? angularJsonPath
: existsSync(nxJsonPath)
? nxJsonPath
: '';
const configJson = JSON.parse(
existsSync(configPath)
? readFileSync(configPath, {encoding: 'utf8'}).toString()
: '{}'
);
let wrapper: BuilderWrapper = noopBuilderWrapper;
try {
const { createBuilderWithMetrics } = await import('@o3r/telemetry');
wrapper = createBuilderWithMetrics;
} catch {}
if (configJson.cli?.o3rMetrics) {
wrapper = createBuilderWithMetrics;
}
} catch (e: any) {
// Do not throw if `@o3r/telemetry is not installed
if (configJson.cli?.o3rMetrics !== false) {
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(configJson);

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`.');

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

if (existsSync(configPath)) {
writeFileSync(configPath, JSON.stringify(configJson, null, 2));
}
}
}
}
return wrapper(builderFn)(opts, ctx);
};
55 changes: 52 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,56 @@ 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 angularJsonPath = '/angular.json';
const angularJson = tree.exists(angularJsonPath) ? tree.readJson(angularJsonPath) as JsonObject : {};
try {
const { createSchematicWithMetrics } = await import('@o3r/telemetry');
wrapper = createSchematicWithMetrics;
if ((angularJson.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 (context.interactive && (angularJson.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`.');

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

tree.overwrite(
tree.exists('/nx.json') ? '/nx.json' : angularJsonPath,
JSON.stringify(angularJson, 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('/angular.json')) {
const angularJson = tree.readJson('/angular.json') as JsonObject;
angularJson.cli ||= {};
(angularJson.cli as JsonObject).o3rMetrics = true;
tree.overwrite('/angular.json', JSON.stringify(angularJson, 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 '${tree.exists('/nx.json') ? 'nx' : 'angular'}.json'.
You can also temporarily deactivate it by running your builder or schematic with '--o3r-metrics=false'.
`);
};
}
18 changes: 16 additions & 2 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 } from '../sender';

type BuilderWrapperFn<S, O extends BuilderOutput = BuilderOutput> =
Expand Down Expand Up @@ -61,7 +63,19 @@ export const createBuilderWithMetrics: BuilderWrapper = (builderFn, sendData = d
error
};
context.logger.debug(JSON.stringify(data, null, 2));
await sendData(data, context.logger);
let shouldSendData = !!(options as any).o3rMetrics;
const nxJsonPath = path.join(context.currentDirectory, 'nx.json');
const angularJsonPath = path.join(context.currentDirectory, 'angular.json');
if (shouldSendData && existsSync(nxJsonPath)) {
const nxJson = JSON.parse(readFileSync(nxJsonPath, 'utf-8'));
shouldSendData &&= !!nxJson.cli.o3rMetrics;
} else if (shouldSendData && existsSync(angularJsonPath)) {
const angularJson = JSON.parse(readFileSync(angularJsonPath, 'utf-8'));
shouldSendData &&= !!angularJson.cli.o3rMetrics;
}
if (shouldSendData) {
await sendData(data, context.logger);
}
} catch (e: any) {
// Do not throw error if we don't manage to collect data
const err = (e instanceof Error ? e : new Error(error));
Expand Down
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
13 changes: 12 additions & 1 deletion 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 @@ -47,7 +48,17 @@ export const createSchematicWithMetrics: SchematicWrapper =
error
};
context.logger.debug(JSON.stringify(data, null, 2));
await sendData(data, context.logger);
let shouldSendData = !!(options as any).o3rMetrics;
if (shouldSendData && tree.exists('/nx.json')) {
const nxJson = tree.readJson('/nx.json') as JsonObject;
shouldSendData &&= !!(nxJson.cli as JsonObject).o3rMetrics;
} else if (shouldSendData && tree.exists('/angular.json')) {
const angularJson = tree.readJson('/angular.json') as JsonObject;
shouldSendData &&= !!(angularJson.cli as JsonObject).o3rMetrics;
}
if (shouldSendData) {
await sendData(data, context.logger);
}
} catch (e: any) {
// Do not throw error if we don't manage to collect data
const err = (e instanceof Error ? e : new Error(error));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ export function ngAddIframeFn(options: NgAddIframeSchematicsSchema): Rule {
options.skipLinter ? noop() : applyEsLintFix()
]);
} catch (e) {
debugger;
if (e instanceof NoOtterComponent && context.interactive) {
const shouldConvertComponent = await askConfirmationToConvertComponent();
if (shouldConvertComponent) {
Expand Down
25 changes: 23 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7305,6 +7305,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 @@ -7316,6 +7317,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 @@ -11512,6 +11514,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 @@ -11986,6 +11998,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 @@ -20609,7 +20630,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 @@ -27686,7 +27707,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 6cd75fa

Please sign in to comment.