Skip to content

Commit

Permalink
feat(turbo-ignore): usage metrics (#7897)
Browse files Browse the repository at this point in the history
### Description

Anonymous usage metrics for turbo-ignore. Will help inform future
roadmap for the tool.
  • Loading branch information
tknickman committed Apr 8, 2024
1 parent d80c7f2 commit bed9970
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 15 deletions.
2 changes: 1 addition & 1 deletion packages/create-turbo/package.json
Expand Up @@ -24,7 +24,7 @@
},
"dependencies": {
"chalk": "4.1.2",
"commander": "^10.0.0",
"commander": "^11.0.0",
"fs-extra": "^11.1.1",
"inquirer": "^8.0.0",
"proxy-agent": "^6.2.2",
Expand Down
54 changes: 50 additions & 4 deletions packages/turbo-ignore/__tests__/ignore.test.ts
@@ -1,4 +1,4 @@
// eslint-disable-next-line camelcase
// eslint-disable-next-line camelcase -- This is a test file
import child_process, {
type ChildProcess,
type ExecException,
Expand All @@ -11,6 +11,7 @@ import {
validateLogs,
} from "@turbo/test-utils";
import { turboIgnore } from "../src/ignore";
import { TurboIgnoreTelemetry, TelemetryConfig } from "@turbo/telemetry";

function expectBuild(mockExit: SpyExit) {
expect(mockExit.exit).toHaveBeenCalledWith(1);
Expand All @@ -25,6 +26,22 @@ describe("turboIgnore()", () => {
const mockExit = spyExit();
const mockConsole = spyConsole();

const telemetry = new TurboIgnoreTelemetry({
api: "https://example.com",
packageInfo: {
name: "turbo-ignore",
version: "1.0.0",
},
config: new TelemetryConfig({
configPath: "test-config-path",
config: {
telemetry_enabled: false,
telemetry_id: "telemetry-test-id",
telemetry_salt: "telemetry-salt",
},
}),
});

it("throws error and allows build when exec fails", () => {
const mockExec = jest
.spyOn(child_process, "exec")
Expand All @@ -39,7 +56,7 @@ describe("turboIgnore()", () => {
return {} as unknown as ChildProcess;
});

turboIgnore("test-workspace", {});
turboIgnore("test-workspace", { telemetry });

expect(mockExec).toHaveBeenCalledWith(
`npx turbo run build --filter="test-workspace...[HEAD^]" --dry=json`,
Expand Down Expand Up @@ -117,7 +134,7 @@ describe("turboIgnore()", () => {
return {} as unknown as ChildProcess;
});

turboIgnore("test-workspace", {});
turboIgnore("test-workspace", { telemetry });

expect(mockExec).toHaveBeenCalledWith(
`npx turbo run build --filter="test-workspace...[too-far-back]" --dry=json`,
Expand Down Expand Up @@ -607,7 +624,7 @@ describe("turboIgnore()", () => {
mockExec.mockRestore();
});

it("passes max buffer to turbo exectuion", () => {
it("passes max buffer to turbo execution", () => {
const mockExec = jest
.spyOn(child_process, "exec")
.mockImplementation((command, options, callback) => {
Expand All @@ -631,4 +648,33 @@ describe("turboIgnore()", () => {

mockExec.mockRestore();
});

it("runs with telemetry", () => {
const mockExec = jest
.spyOn(child_process, "exec")
.mockImplementation((command, options, callback) => {
if (callback) {
return callback(
null,
'{"packages": [],"tasks":[]}',
"stderr"
) as unknown as ChildProcess;
}
return {} as unknown as ChildProcess;
});

turboIgnore(undefined, {
directory: "__fixtures__/app",
maxBuffer: 1024,
telemetry,
});

expect(mockExec).toHaveBeenCalledWith(
`npx turbo run build --filter="test-app...[HEAD^]" --dry=json`,
expect.objectContaining({ maxBuffer: 1024 }),
expect.anything()
);

mockExec.mockRestore();
});
});
1 change: 1 addition & 0 deletions packages/turbo-ignore/package.json
Expand Up @@ -27,6 +27,7 @@
},
"devDependencies": {
"@turbo/eslint-config": "workspace:*",
"@turbo/telemetry": "workspace:*",
"@turbo/test-utils": "workspace:*",
"@turbo/tsconfig": "workspace:*",
"@turbo/types": "workspace:*",
Expand Down
27 changes: 27 additions & 0 deletions packages/turbo-ignore/src/cli.ts
@@ -1,16 +1,40 @@
#!/usr/bin/env node

import { Command, Option } from "commander";
import {
type TurboIgnoreTelemetry,
initTelemetry,
withTelemetryCommand,
} from "@turbo/telemetry";
import cliPkg from "../package.json";
import { turboIgnore } from "./ignore";

// Global telemetry client
let telemetryClient: TurboIgnoreTelemetry | undefined;

const turboIgnoreCli = new Command();

turboIgnoreCli
.name(cliPkg.name)
.description(
"Only proceed with deployment if the workspace or any of its dependencies have changed"
)
.hook("preAction", async (_, thisAction) => {
const { telemetry } = await initTelemetry<"turbo-ignore">({
packageInfo: {
name: "turbo-ignore",
version: cliPkg.version,
},
});
// inject telemetry into the action as an option
thisAction.addOption(
new Option("--telemetry").default(telemetry).hideHelp()
);
telemetryClient = telemetry;
})
.hook("postAction", async () => {
await telemetryClient?.close();
})
.argument(
"[workspace]",
`The workspace being deployed. If [workspace] is not provided, it will be inferred from the "name" field of the "package.json" located at the current working directory.`
Expand Down Expand Up @@ -41,4 +65,7 @@ turboIgnoreCli
.showHelpAfterError(false)
.action(turboIgnore);

// Add telemetry command to the CLI
withTelemetryCommand(turboIgnoreCli);

turboIgnoreCli.parse();
14 changes: 14 additions & 0 deletions packages/turbo-ignore/src/ignore.ts
Expand Up @@ -11,6 +11,13 @@ import { shouldWarn } from "./errors";
import type { TurboIgnoreArg, TurboIgnoreOptions } from "./types";
import { checkCommit } from "./checkCommit";

function trackOptions(opts: TurboIgnoreOptions) {
opts.telemetry?.trackOptionTask(opts.task);
opts.telemetry?.trackOptionFallback(opts.fallback);
opts.telemetry?.trackOptionDirectory(opts.directory);
opts.telemetry?.trackOptionMaxBuffer(opts.maxBuffer);
}

function ignoreBuild() {
log("⏭ Ignoring the change");
return process.exit(0);
Expand All @@ -25,6 +32,10 @@ export function turboIgnore(
workspaceArg: TurboIgnoreArg,
opts: TurboIgnoreOptions
) {
opts.telemetry?.trackCommandStatus({ command: "ignore", status: "start" });
opts.telemetry?.trackArgumentWorkspace(workspaceArg !== undefined);
trackOptions(opts);

const inputs = {
workspace: workspaceArg,
...opts,
Expand Down Expand Up @@ -108,6 +119,7 @@ export function turboIgnore(
if (err) {
const { level, code, message } = shouldWarn({ err: err.message });
if (level === "warn") {
opts.telemetry?.trackCommandWarning(message);
warn(message);
} else {
error(`${code}: ${err.message}`);
Expand Down Expand Up @@ -142,6 +154,8 @@ export function turboIgnore(
error(`Failed to parse JSON output from \`${command}\`.`);
error(e);
return continueBuild();
} finally {
opts.telemetry?.trackCommandStatus({ command: "ignore", status: "end" });
}
});
}
4 changes: 4 additions & 0 deletions packages/turbo-ignore/src/types.ts
@@ -1,3 +1,5 @@
import { type TurboIgnoreTelemetry } from "@turbo/telemetry";

export type NonFatalErrorKey =
| "MISSING_LOCKFILE"
| "NO_PACKAGE_MANAGER"
Expand All @@ -24,4 +26,6 @@ export interface TurboIgnoreOptions {
fallback?: string;
// The maxBuffer for the child process in KB
maxBuffer?: number;
// The telemetry client
telemetry?: TurboIgnoreTelemetry;
}
3 changes: 2 additions & 1 deletion packages/turbo-telemetry/package.json
Expand Up @@ -26,6 +26,7 @@
},
"dependencies": {
"chalk": "^4.1.2",
"ci-info": "^4.0.0",
"got": "^11.8.6",
"uuid": "^9.0.1",
"zod": "^3.22.4"
Expand All @@ -45,6 +46,6 @@
"typescript": "5.3.3"
},
"peerDependencies": {
"commander": "^10.0.0"
"commander": "^11.0.0"
}
}
14 changes: 14 additions & 0 deletions packages/turbo-telemetry/src/client.ts
Expand Up @@ -191,4 +191,18 @@ export class TelemetryClient {
value: status,
});
}

trackCommandWarning(warning: string): Event | undefined {
return this.track({
key: "warning",
value: warning,
});
}

trackCommandError(error: string): Event | undefined {
return this.track({
key: "error",
value: error,
});
}
}
3 changes: 2 additions & 1 deletion packages/turbo-telemetry/src/events/create-turbo.ts
Expand Up @@ -56,11 +56,12 @@ export class CreateTurboTelemetry extends TelemetryClient {
}
}

// only track that the argument was provided, not what it was
trackArgumentDirectory(provided: boolean): Event | undefined {
if (provided) {
return this.trackCliArgument({
argument: "project_directory",
value: provided.toString(),
value: "provided",
});
}
}
Expand Down
76 changes: 73 additions & 3 deletions packages/turbo-telemetry/src/events/turbo-ignore.ts
@@ -1,11 +1,81 @@
import { name } from "ci-info";
import { TelemetryClient } from "../client";
import type { Event } from "./types";

const TASK_ALLOWLIST: Readonly<Array<string>> = [
"build",
"test",
"lint",
"typecheck",
"checktypes",
"check-types",
"type-check",
"check",
] as const;

export class TurboIgnoreTelemetry extends TelemetryClient {
trackExecutionEnv(): Event | undefined {
trackCI(): Event | undefined {
return this.track({
key: "execution_env",
value: process.env.VERCEL === "1" ? "vercel" : "local",
key: "ci",
value: name ?? "unknown",
});
}

/**
* Track the workspace argument if it's provided.
* We only track if it's provided, not what it was
*/
trackArgumentWorkspace(provided: boolean): Event | undefined {
if (provided) {
return this.trackCliArgument({
argument: "workspace",
value: "provided",
});
}
}

/**
* Track the task option if it's provided.
* We only track the exact task name if it's in the allowlist
* Otherwise, we track it as "other"
*/
trackOptionTask(value: string | undefined): Event | undefined {
if (value) {
return this.trackCliOption({
option: "task",
value: TASK_ALLOWLIST.includes(value) ? value : "other",
});
}
}

trackOptionFallback(value: string | undefined): Event | undefined {
if (value) {
return this.trackCliOption({
option: "fallback",
value,
});
}
}

/**
* Track the directory argument if it's provided.
* We only track if it's provided, not what it was
*/
trackOptionDirectory(value: string | undefined): Event | undefined {
if (value) {
return this.trackCliOption({
option: "directory",
value: "custom",
});
}
}

trackOptionMaxBuffer(value: number | undefined): Event | undefined {
if (value !== undefined) {
return this.trackCliOption({
option: "max_buffer",
value: value.toString(),
});
}
}
}
1 change: 1 addition & 0 deletions packages/turbo-telemetry/src/index.ts
Expand Up @@ -5,3 +5,4 @@ export { withTelemetryCommand } from "./cli";

// Event Classes
export { CreateTurboTelemetry } from "./events/create-turbo";
export { TurboIgnoreTelemetry } from "./events/turbo-ignore";

0 comments on commit bed9970

Please sign in to comment.