From a9b580969e4226a4dcd03592f43e0036486a34d7 Mon Sep 17 00:00:00 2001 From: Tim Rogers Date: Fri, 8 Dec 2023 22:45:13 +0000 Subject: [PATCH] Add basic, anonymous telemetry which can be disabled with a command line argument --- .github/workflows/end_to_end_tests.yml | 12 +-- README.md | 18 ++++- package-lock.json | 108 +++++++++++++++++++++++++ package.json | 1 + src/commands/export.ts | 26 ++++++ src/commands/import.ts | 24 ++++++ src/posthog.ts | 2 + 7 files changed, 183 insertions(+), 8 deletions(-) create mode 100644 src/posthog.ts diff --git a/.github/workflows/end_to_end_tests.yml b/.github/workflows/end_to_end_tests.yml index a871e2b..613bf7a 100644 --- a/.github/workflows/end_to_end_tests.yml +++ b/.github/workflows/end_to_end_tests.yml @@ -30,7 +30,7 @@ jobs: - name: Make macOS binary executable run: chmod +x bin/gh-migrate-project-darwin-amd64 - name: Export a project from GitHub.com - run: ./bin/gh-migrate-project-darwin-amd64 export --project-owner gh-migrate-project-sandbox --project-number 1 + run: ./bin/gh-migrate-project-darwin-amd64 export --project-owner gh-migrate-project-sandbox --project-number 1 --disable-telemetry env: EXPORT_GITHUB_TOKEN: ${{ secrets.GH_MIGRATE_PROJECT_SANDBOX_TOKEN }} - name: Copy outputs to output/ directory @@ -44,7 +44,7 @@ jobs: echo "source_repository,target_repository gh-migrate-project-sandbox/initial-repository,gh-migrate-project-sandbox/initial-repository" > completed-repository-mappings.csv - name: Import project to GitHub.com - run: ./bin/gh-migrate-project-darwin-amd64 import --input-path project.json --repository-mappings-path completed-repository-mappings.csv --project-owner gh-migrate-project-sandbox + run: ./bin/gh-migrate-project-darwin-amd64 import --input-path project.json --repository-mappings-path completed-repository-mappings.csv --project-owner gh-migrate-project-sandbox --disable-telemetry env: IMPORT_GITHUB_TOKEN: ${{ secrets.GH_MIGRATE_PROJECT_SANDBOX_TOKEN }} - name: Upload outputs as artifacts @@ -79,7 +79,7 @@ jobs: - name: Make Linux binary executable run: chmod +x bin/gh-migrate-project-linux-amd64 - name: Export a project from GitHub.com - run: ./bin/gh-migrate-project-linux-amd64 export --project-owner gh-migrate-project-sandbox --project-number 1 + run: ./bin/gh-migrate-project-linux-amd64 export --project-owner gh-migrate-project-sandbox --project-number 1 --disable-telemetry env: EXPORT_GITHUB_TOKEN: ${{ secrets.GH_MIGRATE_PROJECT_SANDBOX_TOKEN }} - name: Copy outputs to output/ directory @@ -93,7 +93,7 @@ jobs: echo "source_repository,target_repository gh-migrate-project-sandbox/initial-repository,gh-migrate-project-sandbox/initial-repository" > completed-repository-mappings.csv - name: Import project to GitHub.com - run: ./bin/gh-migrate-project-linux-amd64 import --input-path project.json --repository-mappings-path completed-repository-mappings.csv --project-owner gh-migrate-project-sandbox + run: ./bin/gh-migrate-project-linux-amd64 import --input-path project.json --repository-mappings-path completed-repository-mappings.csv --project-owner gh-migrate-project-sandbox --disable-telemetry env: IMPORT_GITHUB_TOKEN: ${{ secrets.GH_MIGRATE_PROJECT_SANDBOX_TOKEN }} - name: Upload outputs as artifacts @@ -133,7 +133,7 @@ jobs: - name: Create `output` directory run: mkdir output - name: Export a project from GitHub.com - run: bin/gh-migrate-project-windows-amd64.exe export --project-owner gh-migrate-project-sandbox --project-number 1 + run: bin/gh-migrate-project-windows-amd64.exe export --project-owner gh-migrate-project-sandbox --project-number 1 --disable-telemetry env: EXPORT_GITHUB_TOKEN: ${{ secrets.GH_MIGRATE_PROJECT_SANDBOX_TOKEN }} - name: Copy outputs to output/ directory @@ -147,7 +147,7 @@ jobs: echo "source_repository,target_repository gh-migrate-project-sandbox/initial-repository,gh-migrate-project-sandbox/initial-repository" > completed-repository-mappings.csv - name: Import project to GitHub.com - run: bin/gh-migrate-project-windows-amd64.exe import --input-path project.json --repository-mappings-path completed-repository-mappings.csv --project-owner gh-migrate-project-sandbox + run: bin/gh-migrate-project-windows-amd64.exe import --input-path project.json --repository-mappings-path completed-repository-mappings.csv --project-owner gh-migrate-project-sandbox --disable-telemetry env: IMPORT_GITHUB_TOKEN: ${{ secrets.GH_MIGRATE_PROJECT_SANDBOX_TOKEN }} - name: Upload outputs as artifacts diff --git a/README.md b/README.md index 84a6163..0cb16f1 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,9 @@ gh migrate-project export \ # OPTIONAL: The URL of an HTTP(S) proxy to use for requests to the GitHub API (e.g. `http://localhost:3128`). This can also be set using the EXPORT_PROXY_URL environment variable. --proxy-url https://10.0.0.1:3128 \ # OPTIONAL: Emit detailed, verbose logs (off by default) - --verbose + --verbose \ + # OPTIONAL: Disable anonymous telemetry that gives the maintainers of this tool basic information about real-world usage. + --disable-telemetry ``` When the export finishes, you'll have two files written to your current directory: @@ -137,7 +139,9 @@ gh migrate-project import \ # OPTIONAL: The title to use for the imported project. Defaults to the title of the source project. --project-title "My Imported Project" \ # OPTIONAL: Emit detailed, verbose logs (off by default) - --verbose + --verbose \ + # OPTIONAL: Disable anonymous telemetry that gives the maintainers of this tool basic information about real-world usage. + --disable-telemetry ``` Near the start of the import, the tool will ask you to manually set up your options for the "Status" field. It will explain exactly what to do, and will validate that you've correctly copied the options from your migration source. @@ -152,3 +156,13 @@ If you run into SSL connection errors and you are unable to set your proxy setti export NODE_TLS_REJECT_UNAUTHORIZED=0 # setting an environment variable in bash $Env:NODE_TLS_REJECT_UNAUTHORIZED = 0 # setting an environment variable in PowerShell ``` + +## Telemetry + +This extension includes basic, anonymous telemetry to give the maintainers information about real-world usage. This data is limited to: + +- The number of exports and imports being run +- The versions of GitHub Enterprise Server being used +- The versions of the extension currently in used + +You can disable all telemetry by specifying the `--disable-telemetry` argument. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index edd1c23..18a91a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "chalk": "^5.3.0", "commander": "^11.1.0", "octokit": "^3.1.2", + "posthog-node": "^3.2.0", "semver": "^7.5.4", "undici": "^6.0.1", "winston": "^3.11.0" @@ -1561,6 +1562,11 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", @@ -1570,6 +1576,16 @@ "node": ">= 4.0.0" } }, + "node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1854,6 +1870,17 @@ "text-hex": "1.0.x" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", @@ -1993,6 +2020,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/deprecation": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", @@ -2491,6 +2526,38 @@ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -3174,6 +3241,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", @@ -3768,6 +3854,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/posthog-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-3.2.0.tgz", + "integrity": "sha512-R/kNgZuJNt/vZ0ghEFzSZw5V0VjdhyBcXkDQN4fahbJy491u+FhBqghl1JIi8AHAoOxTdG0eDTedPvHp5usGmQ==", + "dependencies": { + "axios": "^1.6.2", + "rusha": "^0.8.14" + }, + "engines": { + "node": ">=15.0.0" + } + }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -3845,6 +3943,11 @@ "node": ">=0.4.0" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -4108,6 +4211,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rusha": { + "version": "0.8.14", + "resolved": "https://registry.npmjs.org/rusha/-/rusha-0.8.14.tgz", + "integrity": "sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", diff --git a/package.json b/package.json index a432715..9c1625e 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "chalk": "^5.3.0", "commander": "^11.1.0", "octokit": "^3.1.2", + "posthog-node": "^3.2.0", "semver": "^7.5.4", "undici": "^6.0.1", "winston": "^3.11.0" diff --git a/src/commands/export.ts b/src/commands/export.ts index 89077df..5582004 100644 --- a/src/commands/export.ts +++ b/src/commands/export.ts @@ -1,5 +1,6 @@ import * as commander from 'commander'; import { existsSync, writeFileSync } from 'fs'; +import crypto from 'crypto'; import { type Octokit } from 'octokit'; import semver from 'semver'; @@ -13,6 +14,8 @@ import { MINIMUM_SUPPORTED_GITHUB_ENTERPRISE_SERVER_VERSION_FOR_EXPORTS, getGitHubProductInformation, } from '../github-products.js'; +import { PostHog } from 'posthog-node'; +import { POSTHOG_API_KEY, POSTHOG_HOST } from '../posthog.js'; const command = new commander.Command(); const { Option } = commander; @@ -25,6 +28,7 @@ enum ProjectOwnerType { interface Arguments { accessToken?: string; baseUrl: string; + disableTelemetry: boolean; projectOutputPath: string; repositoryMappingsOutputPath: string; projectOwner: string; @@ -394,11 +398,17 @@ command process.env.EXPORT_PROXY_URL, ) .option('--verbose', 'Emit detailed, verbose logs', false) + .option( + '--disable-telemetry', + 'Disable anonymous telemetry that gives the maintainers of this tool basic information about real-world usage. For more detailed information about the built-in telemetry, see the readme at https://github.com/timrogers/gh-migrate-project.', + false, + ) .action( actionRunner(async (opts: Arguments) => { const { accessToken: accessTokenFromArguments, baseUrl, + disableTelemetry, projectNumber, projectOutputPath, projectOwner, @@ -408,6 +418,8 @@ command verbose, } = opts; + const posthog = new PostHog(POSTHOG_API_KEY, { host: POSTHOG_HOST }); + const accessToken = accessTokenFromArguments || process.env.EXPORT_GITHUB_TOKEN; if (!accessToken) { @@ -461,6 +473,18 @@ command logger.info(`Running export in GitHub.com mode`); } + if (!disableTelemetry) { + posthog.capture({ + distinctId: crypto.randomUUID(), + event: 'export_start', + properties: { + github_enterprise_server_version: gitHubEnterpriseServerVersion, + is_github_enterprise_server: isGitHubEnterpriseServer, + version: VERSION, + }, + }); + } + logger.info( `Looking up ID for project ${projectNumber} owned by ${projectOwnerType} ${projectOwner}...`, ); @@ -507,6 +531,8 @@ command logger.info( `Successfully wrote repositories mappings CSV to ${repositoryMappingsOutputPath}`, ); + + await posthog.shutdownAsync(); process.exit(0); }), ); diff --git a/src/commands/import.ts b/src/commands/import.ts index 1820466..e99ce5e 100644 --- a/src/commands/import.ts +++ b/src/commands/import.ts @@ -17,6 +17,8 @@ import { } from '../graphql-types.js'; import { getReferencedRepositories } from '../project-items.js'; import { getGitHubProductInformation } from '../github-products.js'; +import { PostHog } from 'posthog-node'; +import { POSTHOG_API_KEY, POSTHOG_HOST } from '../posthog.js'; const command = new commander.Command(); const { Option } = commander; @@ -34,6 +36,7 @@ const rl = readline.createInterface({ interface Arguments { accessToken?: string; baseUrl: string; + disableTelemetry: boolean; inputPath: string; projectOwner: string; projectOwnerType: ProjectOwnerType; @@ -712,6 +715,11 @@ command process.env.IMPORT_PROXY_URL, ) .option('--verbose', 'Emit detailed, verbose logs', false) + .option( + '--disable-telemetry', + 'Disable anonymous telemetry that gives the maintainers of this tool basic information about real-world usage. For more detailed information about the built-in telemetry, see the readme at https://github.com/timrogers/gh-migrate-project.', + false, + ) .addOption( new Option( '--project-owner-type ', @@ -725,6 +733,7 @@ command const { accessToken: accessTokenFromArguments, baseUrl, + disableTelemetry, inputPath, projectOwner, projectOwnerType, @@ -734,6 +743,8 @@ command verbose, } = opts; + const posthog = new PostHog(POSTHOG_API_KEY, { host: POSTHOG_HOST }); + const accessToken = accessTokenFromArguments || process.env.IMPORT_GITHUB_TOKEN; if (!accessToken) { @@ -795,6 +806,18 @@ command logger.info(`Running import in GitHub.com mode`); } + if (!disableTelemetry) { + posthog.capture({ + distinctId: crypto.randomUUID(), + event: 'import_start', + properties: { + github_enterprise_server_version: gitHubEnterpriseServerVersion, + is_github_enterprise_server: isGitHubEnterpriseServer, + version: VERSION, + }, + }); + } + logger.info(`Looking up ID for target ${projectOwnerType} ${projectOwner}...`); const ownerId = await getOwnerGlobalId({ octokit, projectOwner, projectOwnerType }); @@ -1024,6 +1047,7 @@ command ), ); + await posthog.shutdownAsync(); process.exit(0); }), ); diff --git a/src/posthog.ts b/src/posthog.ts new file mode 100644 index 0000000..c8634b9 --- /dev/null +++ b/src/posthog.ts @@ -0,0 +1,2 @@ +export const POSTHOG_API_KEY = 'phc_qsDAq7TFrwaOraJHwetEyRiYqxBun5IG44s8JBIXdUI'; +export const POSTHOG_HOST = 'https://eu.posthog.com';