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 Feb 5, 2024
1 parent 7ad5533 commit c61e90a
Show file tree
Hide file tree
Showing 19 changed files with 247 additions and 44 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 `config.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 @@ -281,5 +281,8 @@
"tools/@*/*",
"apps/*"
],
"config": {
"o3rMetrics": true
},
"packageManager": "[email protected]"
}
4 changes: 2 additions & 2 deletions packages/@ama-sdk/create/src/index.it.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe('Create new sdk command', () => {

test('should generate a full SDK when the specification is provided', () => {
expect(() =>
packageManagerCreate(`@ama-sdk typescript ${sdkPackageName} --package-manager ${packageManager} --spec-path ./swagger-spec.yml`, execAppOptions)).not.toThrow();
packageManagerCreate(`@ama-sdk typescript ${sdkPackageName} --package-manager ${packageManager} --spec-path ./swagger-spec.yml --no-o3r-metrics`, execAppOptions)).not.toThrow();
expect(() => packageManagerRun('build', { ...execAppOptions, cwd: sdkPackagePath })).not.toThrow();
});

Expand All @@ -60,7 +60,7 @@ describe('Create new sdk command', () => {
expect(() => packageManagerRun('build', { ...execAppOptions, cwd: sdkPackagePath })).not.toThrow();
expect(() =>
packageManagerExec(
`schematics @ama-sdk/schematics:typescript-core --spec-path ${path.join(path.relative(sdkPackagePath, sdkFolderPath), 'swagger-spec.yml')}`,
`schematics @ama-sdk/schematics:typescript-core --spec-path ${path.join(path.relative(sdkPackagePath, sdkFolderPath), 'swagger-spec.yml')} --no-o3r-metrics`,
{ ...execAppOptions, cwd: sdkPackagePath }
)).not.toThrow();
expect(() => packageManagerRun('build', { ...execAppOptions, cwd: sdkPackagePath })).not.toThrow();
Expand Down
3 changes: 2 additions & 1 deletion packages/@ama-sdk/create/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ const schematicArgs = [
'--package', pck,
'--package-manager', packageManager,
'--directory', targetDirectory,
...(argv['spec-path'] ? ['--spec-path', argv['spec-path']] : [])
...(argv['spec-path'] ? ['--spec-path', argv['spec-path']] : []),
...(argv['o3r-metrics'] ? [`--${!argv['o3r-metrics'] ? 'no-' : ''}o3r-metrics`] : [])
];

const getSchematicStepInfo = (schematic: string) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ function ngGenerateTypescriptSDKFn(options: NgGenerateTypescriptSDKCoreSchematic
* @param _context
*/
const clearGeneratedCode = (tree: Tree, _context: SchematicContext) => {
treeGlob(tree, path.posix.join(targetPath, 'src', 'api', '**', '*.ts')).forEach((file) => tree.delete(file));
treeGlob(tree, path.posix.join(targetPath, 'src', 'api', '**', '*.ts')).forEach((file) => tree.delete(file));
treeGlob(tree, path.posix.join(targetPath, 'src', 'models', 'base', '**', '!(index).ts')).forEach((file) => tree.delete(file));
treeGlob(tree, path.posix.join(targetPath, 'src', 'spec', '!(operation-adapter|index).ts')).forEach((file) => tree.delete(file));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ const baseFileList = [
'/src/spec/index.ts',
'/src/spec/mock-factory/index.ts',
'/testing/tsconfig.spec.json',
'/.husky/commit-msg',
'/.husky/pre-commit',
'/.husky/.gitignore',
'/tsconfigs/esm2020/tsconfig.jest.json',
'/tsconfigs/esm2020/tsconfig.json',
'/tsconfigs/esm2020/tsconfig.source.json',
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
"files:pack": "amasdk-files-pack",
"test": "jest --passWithNoTests",
"publish:package": "npm publish ./dist",
"postinstall": "husky install",
"generate:mock": "schematics @ama-sdk/schematics:mock",
"doc:generate": "typedoc",
"tools:changelog": "standard-version"
Expand Down
5 changes: 2 additions & 3 deletions packages/@o3r/extractors/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,13 @@
"typescript": "~5.2.2"
},
"peerDependenciesMeta": {
"@o3r/schematics": {
"optional": true
},
"@o3r/telemetry": {
"optional": true
}
},
"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 @@ -71,6 +69,7 @@
"@o3r/telemetry": "workspace:^",
"@o3r/test-helpers": "workspace:^",
"@stylistic/eslint-plugin-ts": "^1.5.4",
"@types/inquirer": "~8.2.10",
"@types/jest": "~29.5.2",
"@types/node": "^20.0.0",
"@typescript-eslint/eslint-plugin": "^6.19.0",
Expand Down
62 changes: 60 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 { execFileSync } from 'node:child_process';
import { existsSync, promises } from 'node:fs';
import * as path from 'node:path';

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

Expand All @@ -8,10 +13,63 @@ 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(await promises.readFile(packageJsonPath, {encoding: 'utf8'}))
: {};
let wrapper: BuilderWrapper = noopBuilderWrapper;
try {
const { createBuilderWithMetrics } = await import('@o3r/telemetry');
wrapper = createBuilderWithMetrics;
} catch {}
if (packageJson.config?.o3rMetrics) {
wrapper = createBuilderWithMetrics;
}
} catch (e: any) {
// Do not throw if `@o3r/telemetry is not installed
if (packageJson.config?.o3rMetrics === true) {
ctx.logger.info('`config.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.config?.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(await promises.readFile(path.join(__dirname, '..', '..', 'package.json'), 'utf-8')).version;
execFileSync(`${pmr} ng add @o3r/telemetry@${version}`);
} catch {
ctx.logger.warn('Failed to install `@o3r/telemetry`.');
}

try {
const { createBuilderWithMetrics } = await import('@o3r/telemetry');
wrapper = createBuilderWithMetrics;
} catch {
// If pnp context package installed in the same process will not be available
}
} else {
ctx.logger.info('You can activate it at any time by running `ng add @o3r/telemetry`.');

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

if (existsSync(packageJsonPath)) {
await promises.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
} else {
ctx.logger.warn(`No package.json found in ${ctx.workspaceRoot}.`);
}
}
}
}
return wrapper(builderFn)(opts, ctx);
};
63 changes: 60 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,64 @@ 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 = 'package.json';
const packageJson = tree.exists(packageJsonPath) ? tree.readJson(packageJsonPath) as JsonObject : {};
try {
const { createSchematicWithMetrics } = await import('@o3r/telemetry');
wrapper = createSchematicWithMetrics;
if ((packageJson.config 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.config as JsonObject)?.o3rMetrics) {
context.logger.warn('`config.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.config 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')).version;
context.addTask(
new NodePackageNgAddTask(
'@o3r/telemetry',
{
dependencyType: NodeDependencyType.Dev,
version
}
)
);
await lastValueFrom(context.engine.executePostTasks());

try {
const { createSchematicWithMetrics } = await import('@o3r/telemetry');
wrapper = createSchematicWithMetrics;
} catch {
// If pnp context package installed in the same process will not be available
}
} else {
context.logger.info('You can activate it at any time by running `ng add @o3r/telemetry`.');

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

if (tree.exists(packageJsonPath)) {
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.config ||= {};
(packageJson.config 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 'config.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 @@ -58,11 +60,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.config?.o3rMetrics
);
if (shouldSendData) {
if (typeof ((options as any).o3rMetrics ?? process.env.O3R_METRICS) === 'undefined') {
context.logger.info(
'Telemetry is globally activated for the project (`config.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 = async (): Promise<EnvironmentMetricData> => {
os: osInfo,
node: nodeInfo,
packageManager: packageManagerInfo,
otter: otterInfo, ci,
otter: otterInfo,
ci,
...(projectName ? { project: { name: projectName } } : {})
};
};
Expand Down
Loading

0 comments on commit c61e90a

Please sign in to comment.