Skip to content

Commit

Permalink
feat(@angular/cli): add ng analytics info command
Browse files Browse the repository at this point in the history
With this change we add a subcommand to `ng analytics`. This command can be used tp display analytics gathering and reporting configuration.

Example:
```
$ ng analytics info
Global setting: disabled
Local setting: enabled
Effective status: disabled
```
  • Loading branch information
alan-agius4 authored and clydin committed Mar 15, 2022
1 parent afafa57 commit bb55043
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 91 deletions.
23 changes: 19 additions & 4 deletions packages/angular/cli/src/analytics/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ export function isPackageNameSafeForAnalytics(name: string): boolean {

/**
* Set analytics settings. This does not work if the user is not inside a project.
* @param level Which config to use. "global" for user-level, and "local" for project-level.
* @param global Which config to use. "global" for user-level, and "local" for project-level.
* @param value Either a user ID, true to generate a new User ID, or false to disable analytics.
*/
export function setAnalyticsConfig(level: 'global' | 'local', value: string | boolean) {
export function setAnalyticsConfig(global: boolean, value: string | boolean): void {
const level = global ? 'global' : 'local';
analyticsDebug('setting %s level analytics to: %s', level, value);
const [config, configPath] = getWorkspaceRaw(level);
if (!config || !configPath) {
Expand All @@ -79,6 +80,8 @@ export function setAnalyticsConfig(level: 'global' | 'local', value: string | bo
throw new Error(`Invalid config found at ${configPath}. CLI should be an object.`);
}

console.log(`Configured ${level} analytics to "${analyticsConfigValueToHumanFormat(value)}".`);

if (value === true) {
value = uuidV4();
}
Expand All @@ -89,6 +92,18 @@ export function setAnalyticsConfig(level: 'global' | 'local', value: string | bo
analyticsDebug('done');
}

export function analyticsConfigValueToHumanFormat(value: unknown): 'on' | 'off' | 'not set' | 'ci' {
if (value === false) {
return 'off';
} else if (value === 'ci') {
return 'ci';
} else if (typeof value === 'string' || value === true) {
return 'on';
} else {
return 'not set';
}
}

/**
* Prompt the user for usage gathering permission.
* @param force Whether to ask regardless of whether or not the user is using an interactive shell.
Expand All @@ -110,7 +125,7 @@ export async function promptGlobalAnalytics(force = false) {
},
]);

setAnalyticsConfig('global', answers.analytics);
setAnalyticsConfig(true, answers.analytics);

if (answers.analytics) {
console.log('');
Expand Down Expand Up @@ -169,7 +184,7 @@ export async function promptProjectAnalytics(force = false): Promise<boolean> {
},
]);

setAnalyticsConfig('local', answers.analytics);
setAnalyticsConfig(false, answers.analytics);

if (answers.analytics) {
console.log('');
Expand Down
1 change: 1 addition & 0 deletions packages/angular/cli/src/command-builder/command-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface CommandContext {
positional: string[];
options: {
help: boolean;
jsonHelp: boolean;
} & Record<string, unknown>;
};
}
Expand Down
22 changes: 4 additions & 18 deletions packages/angular/cli/src/command-builder/command-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { VersionCommandModule } from '../commands/version/cli';
import { colors } from '../utilities/color';
import { AngularWorkspace } from '../utilities/config';
import { CommandContext, CommandModuleError, CommandScope } from './command-module';
import { addCommandModuleToYargs, demandCommandFailureMessage } from './utilities/command';
import { jsonHelpUsage } from './utilities/json-help';

const COMMANDS = [
Expand Down Expand Up @@ -75,6 +76,7 @@ export async function runCommand(
positional: positional.map((v) => v.toString()),
options: {
help,
jsonHelp,
...rest,
},
},
Expand All @@ -90,23 +92,7 @@ export async function runCommand(
}
}

const commandModule = new CommandModule(context);
const describe = jsonHelp ? commandModule.fullDescribe : commandModule.describe;

localYargs = localYargs.command({
command: commandModule.command,
aliases: 'aliases' in commandModule ? commandModule.aliases : undefined,
describe:
// We cannot add custom fields in help, such as long command description which is used in AIO.
// Therefore, we get around this by adding a complex object as a string which we later parse when generating the help files.
describe !== undefined && typeof describe === 'object'
? JSON.stringify(describe)
: describe,
deprecated: 'deprecated' in commandModule ? commandModule.deprecated : undefined,
builder: (argv) => commandModule.builder(argv) as yargs.Argv,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handler: (args: any) => commandModule.handler(args),
});
localYargs = addCommandModuleToYargs(localYargs, CommandModule, context);
}

if (jsonHelp) {
Expand Down Expand Up @@ -142,7 +128,7 @@ export async function runCommand(
'deprecated: %s': colors.yellow('deprecated:') + ' %s',
'Did you mean %s?': 'Unknown command. Did you mean %s?',
})
.demandCommand()
.demandCommand(1, demandCommandFailureMessage)
.recommendCommands()
.version(false)
.showHelpOnFail(false)
Expand Down
34 changes: 34 additions & 0 deletions packages/angular/cli/src/command-builder/utilities/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { Argv } from 'yargs';
import { CommandContext, CommandModule, CommandModuleImplementation } from '../command-module';

export const demandCommandFailureMessage = `You need to specify a command before moving on. Use '--help' to view the available commands.`;

export function addCommandModuleToYargs<
T,
U extends Partial<CommandModuleImplementation> & {
new (context: CommandContext): Partial<CommandModuleImplementation> & CommandModule;
},
>(localYargs: Argv<T>, commandModule: U, context: CommandContext): Argv<T> {
const cmd = new commandModule(context);
const describe = context.args.options.jsonHelp ? cmd.fullDescribe : cmd.describe;

return localYargs.command({
command: cmd.command,
aliases: cmd.aliases,
describe:
// We cannot add custom fields in help, such as long command description which is used in AIO.
// Therefore, we get around this by adding a complex object as a string which we later parse when generating the help files.
typeof describe === 'object' ? JSON.stringify(describe) : describe,
deprecated: cmd.deprecated,
builder: (argv) => cmd.builder(argv) as Argv<T>,
handler: (args) => cmd.handler(args),
});
}
84 changes: 34 additions & 50 deletions packages/angular/cli/src/commands/analytics/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,61 +6,45 @@
* found in the LICENSE file at https://angular.io/license
*/

import { join } from 'path';
import { Argv } from 'yargs';
import {
promptGlobalAnalytics,
promptProjectAnalytics,
setAnalyticsConfig,
} from '../../analytics/analytics';
import { CommandModule, Options } from '../../command-builder/command-module';
CommandModule,
CommandModuleImplementation,
Options,
} from '../../command-builder/command-module';
import {
addCommandModuleToYargs,
demandCommandFailureMessage,
} from '../../command-builder/utilities/command';
import { AnalyticsInfoCommandModule } from './info/cli';
import {
AnalyticsCIModule,
AnalyticsOffModule,
AnalyticsOnModule,
AnalyticsPromptModule,
} from './settings/cli';

interface AnalyticsCommandArgs {
setting: 'on' | 'off' | 'prompt' | 'ci' | string;
global: boolean;
}
export class AnalyticsCommandModule extends CommandModule implements CommandModuleImplementation {
command = 'analytics';
describe =
'Configures the gathering of Angular CLI usage metrics. See https://angular.io/cli/usage-analytics-gathering';
longDescriptionPath?: string | undefined;

export class AnalyticsCommandModule extends CommandModule<AnalyticsCommandArgs> {
command = 'analytics <setting>';
describe = 'Configures the gathering of Angular CLI usage metrics.';
longDescriptionPath = join(__dirname, 'long-description.md');
builder(localYargs: Argv): Argv {
const subcommands = [
AnalyticsCIModule,
AnalyticsInfoCommandModule,
AnalyticsOffModule,
AnalyticsOnModule,
AnalyticsPromptModule,
].sort();

builder(localYargs: Argv): Argv<AnalyticsCommandArgs> {
return localYargs
.positional('setting', {
description: 'Directly enables or disables all usage analytics for the user.',
choices: ['on', 'off', 'ci', 'prompt'],
type: 'string',
demandOption: true,
})
.option('global', {
description: `Access the global configuration in the caller's home directory.`,
alias: ['g'],
type: 'boolean',
default: false,
})
.strict();
}

async run({ setting, global }: Options<AnalyticsCommandArgs>): Promise<void> {
const level = global ? 'global' : 'local';
switch (setting) {
case 'off':
setAnalyticsConfig(level, false);
break;
case 'on':
setAnalyticsConfig(level, true);
break;
case 'ci':
setAnalyticsConfig(level, 'ci');
break;
case 'prompt':
if (global) {
await promptGlobalAnalytics(true);
} else {
await promptProjectAnalytics(true);
}
break;
for (const module of subcommands) {
localYargs = addCommandModuleToYargs(localYargs, module, this.context);
}

return localYargs.demandCommand(1, demandCommandFailureMessage).strict();
}

run(_options: Options<{}>): void {}
}
52 changes: 52 additions & 0 deletions packages/angular/cli/src/commands/analytics/info/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { tags } from '@angular-devkit/core';
import { Argv } from 'yargs';
import { analyticsConfigValueToHumanFormat, createAnalytics } from '../../../analytics/analytics';
import {
CommandModule,
CommandModuleImplementation,
Options,
} from '../../../command-builder/command-module';
import { getWorkspaceRaw } from '../../../utilities/config';

export class AnalyticsInfoCommandModule
extends CommandModule
implements CommandModuleImplementation
{
command = 'info';
describe = 'Prints analytics gathering and reporting configuration in the console.';
longDescriptionPath?: string | undefined;

builder(localYargs: Argv): Argv {
return localYargs.strict();
}

async run(_options: Options<{}>): Promise<void> {
const [globalWorkspace] = getWorkspaceRaw('global');
const [localWorkspace] = getWorkspaceRaw('local');
const globalSetting = globalWorkspace?.get(['cli', 'analytics']);
const localSetting = localWorkspace?.get(['cli', 'analytics']);

const effectiveSetting = await createAnalytics(
!!this.context.workspace /** workspace */,
true /** skipPrompt */,
);

this.context.logger.info(tags.stripIndents`
Global setting: ${analyticsConfigValueToHumanFormat(globalSetting)}
Local setting: ${
this.context.workspace
? analyticsConfigValueToHumanFormat(localSetting)
: 'No local workspace configuration file.'
}
Effective status: ${effectiveSetting ? 'enabled' : 'disabled'}
`);
}
}

This file was deleted.

Loading

0 comments on commit bb55043

Please sign in to comment.