Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add telemetry enable/disable command #2

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/angry-boxes-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@vercel-internals/types': patch
'vercel': patch
---

Add a command for enabling and disabling telemetry. Telemetry collection is not currently enabled and when it is, will be a major version bump for the CLI.
5 changes: 4 additions & 1 deletion internals/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,12 @@ export interface GlobalConfig {
'// Note'?: string;
'// Docs'?: string;
currentTeam?: string;
collectMetrics?: boolean;
api?: string;

telemetry?: {
enabled?: boolean;
};

// TODO: legacy - remove
updateChannel?: string;
desktop?: {
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ export default new Map([
['targets', 'target'],
['team', 'teams'],
['teams', 'teams'],
['telemetry', 'telemetry'],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider adding a comment explaining the purpose of the telemetry command for better code documentation

['whoami', 'whoami'],
]);
35 changes: 35 additions & 0 deletions packages/cli/src/commands/telemetry/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export const telemetryCommand = {
name: 'telemetry',
description: 'Allows you to enable or disable telemetry collection.',
arguments: [
{
name: 'command',
required: false,
},
],
subcommands: [
{
name: 'status',
description: 'Shows whether telemetry collection is enabled or disabled',
arguments: [],
options: [],
examples: [],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Add an example for the 'status' subcommand to improve user guidance

},
{
name: 'enable',
description: 'Enables telemetry collection',
arguments: [],
options: [],
examples: [],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Add an example for the 'enable' subcommand to improve user guidance

},
{
name: 'disable',
description: 'Disables telemetry collection',
arguments: [],
options: [],
examples: [],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Add an example for the 'disable' subcommand to improve user guidance

},
],
options: [],
examples: [],
} as const;
17 changes: 17 additions & 0 deletions packages/cli/src/commands/telemetry/disable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Client from '../../util/client';
import { writeToConfigFile } from '../../util/config/files';
import status from './status';

export default async function disable(client: Client) {
client.config = {
...client.config,
telemetry: {
...client.config.telemetry,
enabled: false,
},
};

writeToConfigFile(client.output, client.config);
await status(client);
return 0;
}
17 changes: 17 additions & 0 deletions packages/cli/src/commands/telemetry/enable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Client from '../../util/client';
import { writeToConfigFile } from '../../util/config/files';
import status from './status';

export default async function enable(client: Client) {
client.config = {
...client.config,
telemetry: {
...client.config.telemetry,
enabled: true,
},
};

writeToConfigFile(client.output, client.config);
await status(client);
return 0;
}
63 changes: 63 additions & 0 deletions packages/cli/src/commands/telemetry/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { handleError } from '../../util/error';
import Client from '../../util/client';
import { parseArguments } from '../../util/get-args';
import getSubcommand from '../../util/get-subcommand';
import { help } from '../help';
import status from './status';
import enable from './enable';
import disable from './disable';
import { telemetryCommand } from './command';
import { getFlagsSpecification } from '../../util/get-flags-specification';
import chalk from 'chalk';

const COMMAND_CONFIG = {
status: ['status'],
enable: ['enable'],
disable: ['disable'],
};

export default async function telemetry(client: Client) {
let parsedArguments;

const flagsSpecification = getFlagsSpecification(telemetryCommand.options);

try {
parsedArguments = parseArguments(client.argv.slice(2), flagsSpecification);
} catch (err) {
handleError(err);
return 1;
}

if (parsedArguments.flags['--help']) {
client.output.print(
help(telemetryCommand, { columns: client.stderr.columns })
);
}
Comment on lines +31 to +35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: The function returns undefined after printing help. Consider returning 0 to indicate successful execution.


const { subcommand } = getSubcommand(
parsedArguments.args.slice(1),
COMMAND_CONFIG
);
Comment on lines +37 to +40
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider using parsedArguments.args[1] instead of slicing, as you're only interested in the second argument.


switch (subcommand) {
case 'status':
return status(client);
case 'enable':
return enable(client);
case 'disable':
return disable(client);
default: {
const errorMessage =
parsedArguments.args.length !== 2
? `Invalid number of arguments`
: `Invalid subcommand`;
client.output.print(
`${chalk.red('Error')}: ${errorMessage}. See help instructions for usage:\n`
);
client.output.print(
help(telemetryCommand, { columns: client.stderr.columns })
);
return 2;
}
}
}
24 changes: 24 additions & 0 deletions packages/cli/src/commands/telemetry/status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import chalk from 'chalk';
import Client from '../../util/client';

export default async function status(client: Client) {
const status: 'disabled' | 'enabled' =
client.config.telemetry?.enabled === false ? 'disabled' : 'enabled';
Comment on lines +5 to +6
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider using a boolean type for status instead of string literals


const message =
status === 'disabled' ? chalk.red('Disabled') : chalk.green('Enabled');
await client.output.print(
`\n${chalk.bold('Telemetry status')}: ${message}\n\n`
);

// TODO: enable this message when we have a proper URL
// const learnMoreMessage = `\n\nLearn more: ${chalk.cyan(`https://vercel.com/some-link`)}\n`;
const learnMoreMessage = ``;
const optedInMessage = `You have opted in to Vercel CLI telemetry${learnMoreMessage}`;
const optedOutMessage = `You have opted out of Vercel CLI telemetry\nNo data will be collected from your machine${learnMoreMessage}`;
const optedInorOutMessage =
status === 'disabled' ? optedOutMessage : optedInMessage;
await client.output.print(optedInorOutMessage);

return 0;
}
32 changes: 18 additions & 14 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,20 +113,6 @@ const main = async () => {
noColor: isNoColor,
});

const telemetryEventStore = new TelemetryEventStore({
isDebug: isDebugging,
output,
});

const telemetry = new TelemetryBaseClient({
opts: {
store: telemetryEventStore,
output,
},
});

telemetry.trackCIVendorName();

debug = output.debug;

const localConfigPath = parsedArgs.flags['--local-config'];
Expand Down Expand Up @@ -258,6 +244,21 @@ const main = async () => {
}
}

const telemetryEventStore = new TelemetryEventStore({
isDebug: isDebugging,
output,
config: config.telemetry,
});

const telemetry = new TelemetryBaseClient({
opts: {
store: telemetryEventStore,
output,
},
});

telemetry.trackCIVendorName();

if (typeof parsedArgs.flags['--api'] === 'string') {
apiUrl = parsedArgs.flags['--api'];
} else if (config && config.api) {
Expand Down Expand Up @@ -630,6 +631,9 @@ const main = async () => {
case 'teams':
func = require('./commands/teams').default;
break;
case 'telemetry':
func = require('./commands/telemetry').default;
break;
case 'whoami':
func = require('./commands/whoami').default;
break;
Expand Down
1 change: 0 additions & 1 deletion packages/cli/src/util/config/get-default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export const defaultGlobalConfig: GlobalConfig = {
'This is your Vercel config file. For more information see the global configuration documentation.',
'// Docs':
'https://vercel.com/docs/projects/project-configuration/global-configuration#config.json',
collectMetrics: true,
};

export const defaultAuthConfig: AuthConfig = {
Expand Down
16 changes: 15 additions & 1 deletion packages/cli/src/util/telemetry/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { randomUUID } from 'node:crypto';
import type { Output } from '../output';
import { GlobalConfig } from '@vercel-internals/types';

const LogLabel = `['telemetry']:`;

Expand Down Expand Up @@ -113,12 +114,18 @@ export class TelemetryEventStore {
private output: Output;
private isDebug: boolean;
private sessionId: string;
private config: GlobalConfig['telemetry'];

constructor(opts: { output: Output; isDebug?: boolean }) {
constructor(opts: {
output: Output;
isDebug?: boolean;
config: GlobalConfig['telemetry'];
}) {
Comment on lines +119 to +123
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider making config a required parameter to ensure it's always provided

this.isDebug = opts.isDebug || false;
this.output = opts.output;
this.sessionId = randomUUID();
this.events = [];
this.config = opts.config;
}

add(event: Event) {
Expand All @@ -134,12 +141,19 @@ export class TelemetryEventStore {
this.events = [];
}

enabled() {
return this.config?.enabled === false ? false : true;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider simplifying this to return this.config?.enabled !== false; for better readability

}

save() {
if (this.isDebug) {
this.output.debug(`${LogLabel} Flushing Events`);
this.events.forEach(event => {
this.output.debug(JSON.stringify(event));
});
}
if (this.enabled()) {
// send events to the server
}
Comment on lines +155 to +157
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Implement the logic to send events to the server

}
}
Loading