From e7b6146b901bdd3dc83ca9490a562a9f4ac3f28c Mon Sep 17 00:00:00 2001 From: Sarah Schneider Date: Wed, 10 Dec 2025 16:33:36 -0500 Subject: [PATCH] docstat quiet option (#58812) --- package.json | 2 +- src/metrics/README.md | 63 +++++++++++++++++++- src/metrics/lib/kusto-client.ts | 4 +- src/metrics/scripts/README.md | 102 -------------------------------- src/metrics/scripts/docstat.ts | 89 +++++++++++++++++++++------- 5 files changed, 130 insertions(+), 130 deletions(-) delete mode 100644 src/metrics/scripts/README.md diff --git a/package.json b/package.json index f4e6315d50..db07b9267b 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "debug": "cross-env NODE_ENV=development ENABLED_LANGUAGES=en nodemon --inspect src/frame/server.ts", "delete-orphan-translation-files": "tsx src/workflows/delete-orphan-translation-files.ts", "docsaudit": "tsx src/metrics/scripts/docsaudit.ts", - "docstat": "tsx src/metrics/scripts/docstat.ts", + "docstat": "tsx --disable-warning=DEP0190 src/metrics/scripts/docstat.ts", "deleted-assets-pr-comment": "tsx src/assets/scripts/deleted-assets-pr-comment.ts", "deleted-features-pr-comment": "tsx src/data-directory/scripts/deleted-features-pr-comment.ts", "deprecate-ghes": "tsx src/ghes-releases/scripts/deprecate/index.ts", diff --git a/src/metrics/README.md b/src/metrics/README.md index 1fb56d230e..e41d9bc370 100644 --- a/src/metrics/README.md +++ b/src/metrics/README.md @@ -11,9 +11,66 @@ CLI tools to fetch data from the Kusto API. 1. Enter your `@githubazure.com` credentials. * These will get cached for future logins. 1. At the prompt in Terminal asking which subscription you want to use, just press Enter to choose the default. -1. Open or create an `.env` file in the root directory of your checkout (this file is already in `.gitignore`). -1. Add the `KUSTO_CLUSTER` and `KUSTO_DATABASE` values to the `.env`. +1. Open or create an `.env` file in the root directory of your checkout (this file is already in `.gitignore` so it won't be tracked by Git). +1. Add the `KUSTO_CLUSTER` and `KUSTO_DATABASE` values to the `.env` (_these values are pinned in slack_): ``` KUSTO_CLUSTER='' KUSTO_DATABASE='' - ``` \ No newline at end of file + ``` + +## docstat usage + +Run `npm run docstat -- ` on any GitHub Docs URL to gather a set of default metrics about it, including 30d views, users, view duration, bounces, helpfulness score, and exits to support. + +Notes: +* If the URL doesn't include a version, `docstat` will return data that includes **all versions** (so FPT, Cloud, Server, etc.). + * If you want data for FPT only, pass the `--fptOnly` option. +* `docstat` only accepts URLs with an `en` language code or no language code, and it only fetches English data. + +To see all the options: +``` +npm run docstat -- --help +``` +You can combine options like this: +``` +npm run docstat -- https://docs.github.com/copilot/tutorials/modernize-legacy-code --compare --range 60 +``` +Use `--redirects` to include `redirect_from` frontmatter paths in the queries (this is helpful if the article may have moved recently): +``` +npm run docstat -- https://docs.github.com/copilot/tutorials/modernize-legacy-code --redirects +``` +Use the `--json` (or `-j`) option to output JSON: +``` +npm run docstat -- https://docs.github.com/copilot/tutorials/modernize-legacy-code --json +``` +If you want to pass the results of the JSON to `jq`, you need to use `silent` mode: +``` +npm run --silent docstat -- https://docs.github.com/copilot/tutorials/modernize-legacy-code --json | jq .data.users +``` + +## docsaudit usage + +Run `npm run docsaudit` on a top-level content directory to gather data about its files—including title, path, versions, 30d views, and 30d users—and output it to a CSV file. + +To see all the options: +``` +npm run docsaudit -- --help +``` +Run the script on any top-level content directory: +``` +npm run docsaudit -- +``` +For example: +``` +npm run docsaudit -- actions +``` + +## Future development + +Applies to all scripts: + +* The date range option only accepts a start date (via `-r `, where the number means "`` days ago"). The end date will always be the current date. + * In the future, we can add an option to set a custom end date. + +* The only Kusto queries available are hardcoded in the `kusto/queries` directory. + * In the future, we can hardcode more queries, add the ability to send custom queries, or perhaps create pre-defined sets of queries. \ No newline at end of file diff --git a/src/metrics/lib/kusto-client.ts b/src/metrics/lib/kusto-client.ts index 72a84a062e..fcfbd5d7d9 100644 --- a/src/metrics/lib/kusto-client.ts +++ b/src/metrics/lib/kusto-client.ts @@ -4,9 +4,7 @@ import { KustoResultTable, } from 'azure-kusto-data' -import dotenv from 'dotenv' - -dotenv.config() +import 'dotenv/config' if (!(process.env.KUSTO_CLUSTER || process.env.KUSTO_DATABASE)) { console.error(`Add KUSTO_CLUSTER and KUSTO_DATABASE to your .env file`) diff --git a/src/metrics/scripts/README.md b/src/metrics/scripts/README.md deleted file mode 100644 index cdd3c9b322..0000000000 --- a/src/metrics/scripts/README.md +++ /dev/null @@ -1,102 +0,0 @@ -## Scripts - -See documentation below for: - -* [docstat](#docstat) - - Run this on any GitHub Docs URL to gather a set of metrics about it. - -* [docsaudit](#docsaudit) - - Run this on a top-level content directory to gather info about its files and output to a CSV. - -Print usage info for any script in this directory: - -```bash -tsx src/metrics/scripts/.ts --help -``` -If you get `command not found: tsx`, run: -``` -npm install -g tsx -``` - -## docstat - -Run `docstat` on any GitHub Docs URL to gather a set of default metrics about it, including 30d views, users, view duration, bounces, helpfulness score, and exits to support. - -`docstat` checks the URL against the Docs API pagelist at https://docs.github.com/api/pagelist to see if it's valid. - -If the URL doesn't include a version, `docstat` will return data for **all versions**. Pass `--fptOnly` for free-pro-team data. - -`docstat` only accepts URLs with an `en` language code or no language code, and it only fetches English data. - -### Usage - -The steps below show the [global alias](#set-a-global-alias). Use the full command path (`tsx src/metrics/scripts/docstat.ts`) if you don't set up an alias. - -To see the available options: -``` -docstat --help -``` -Run the script on any GitHub Docs URL: -``` -docstat https://docs.github.com/en/copilot/rolling-out-github-copilot-at-scale -``` -Spell options out like this: -``` -docstat https://docs.github.com/en/copilot/rolling-out-github-copilot-at-scale --compare --range 60 -``` -or use the shorthand versions: -``` -docstat https://docs.github.com/en/copilot/rolling-out-github-copilot-at-scale -c -r 60 -``` -Use `--redirects` to include `redirect_from` frontmatter paths in the queries: -``` -docstat https://docs.github.com/en/copilot/rolling-out-github-copilot-at-scale --redirects -``` -Use the `--json` (or `-j`) option to output JSON: -``` -docstat https://docs.github.com/en/copilot/rolling-out-github-copilot-at-scale --json -``` - -### Set a global alias - -To use `docstat` from any location in Terminal, set up a global alias: - -1. Open your shell configuration file (like `~/.bash_profile` or `~/.zshrc`) in a text editor. -2. Add the following line, replacing the path with the actual path to your local directory, for example: - ```bash - alias docstat="tsx ~/gh-repos/docs-internal/src/metrics/scripts/docstat.ts" - ``` -3. Save the file and reload your configuration: - ```bash - source ~/.bash_profile # or ~/.zshrc, etc. - ``` -Now you can run `docstat ` from any directory. - -## docsaudit - -Run `docsaudit` on a top-level content directory to gather data about its files—including title, path, versions, 30d views, and 30d users—and output it to a CSV file. - -To see the available options: -``` -tsx src/metrics/scripts/docsaudit.ts --help -``` -Run the script on any top-level content directory: -``` -tsx src/metrics/scripts/docsaudit.ts -``` -For example: -``` -tsx src/metrics/scripts/docsaudit.ts actions -``` - -## Future development - -Applies to all scripts in this directory: - -* The date range option only accepts a start date (via `-r `, where the number means "`` days ago"). The end date will always be the current date. - * In the future, we can add an option to set a custom end date. - -* The only Kusto queries available are hardcoded in the `kusto/queries` directory. - * In the future, we can hardcode more queries, add the ability to send custom queries, or perhaps create pre-defined sets of queries. diff --git a/src/metrics/scripts/docstat.ts b/src/metrics/scripts/docstat.ts index f21f67a68d..ca50b5a9fd 100644 --- a/src/metrics/scripts/docstat.ts +++ b/src/metrics/scripts/docstat.ts @@ -42,6 +42,7 @@ interface CliOptions { defaultToAll?: boolean showDocset?: boolean allVersions?: boolean + quiet?: boolean } interface JsonOutput { @@ -103,10 +104,16 @@ program 'Get data for free-pro-team@latest only (default: all versions if URL is versionless)', ) .option('--verbose', 'Display Kusto queries being executed') + .option('-q, --quiet', 'Suppress all output except results (automatically enabled with --json)') .parse(process.argv) const options = program.opts() +// Auto-enable quiet mode when JSON output is requested +if (options.json) { + options.quiet = true +} + // If specific options are not provided, default to all options.defaultToAll = !( options.views || @@ -141,26 +148,34 @@ const usingFptOnly = !!options.fptOnly if (version === FREE_PRO_TEAM) { if (usingFptOnly) { // User explicitly wants only free-pro-team@latest - console.log( - '\nFetching data for free-pro-team@latest only. To get all versions, omit the --fptOnly flag.\n', - ) + if (!options.quiet) { + console.log( + '\nFetching data for free-pro-team@latest only. To get all versions, omit the --fptOnly flag.\n', + ) + } } else { // Default: all versions version = null - console.log( - '\nFetching data for all versions (no version specified in URL). To get only free-pro-team@latest, pass "--fptOnly".\n', - ) + if (!options.quiet) { + console.log( + '\nFetching data for all versions (no version specified in URL). To get only free-pro-team@latest, pass "--fptOnly".\n', + ) + } } } else { // Version is specified in the URL (e.g. enterprise-server@) - console.log( - `\nFetching data for version "${version}" as specified in the URL. To get data for all versions, remove the version segment from the URL.\n`, - ) - if (usingFptOnly) { + if (!options.quiet) { console.log( - `You specified a version in the URL (${version}), but also passed --fptOnly. Only the version in the URL will be used.\n`, + `\nFetching data for version "${version}" as specified in the URL. To get data for all versions, remove the version segment from the URL.\n`, ) } + if (usingFptOnly) { + if (!options.quiet) { + console.log( + `You specified a version in the URL (${version}), but also passed --fptOnly. Only the version in the URL will be used.\n`, + ) + } + } // Always use the version from the URL } @@ -194,21 +209,22 @@ const queryPaths = [cleanPath].concat(redirects) const dates: DateRange = getDates(options.range) async function main(): Promise { - const spinner = ora('Connecting to Kusto...').start() + const spinner = options.quiet ? null : ora('Connecting to Kusto...').start() try { const client = getKustoClient() if (!client) { - spinner.fail('Failed to connect to Kusto') + if (spinner) spinner.fail('Failed to connect to Kusto') + else if (!options.quiet) console.error('Failed to connect to Kusto') process.exit(1) } - spinner.text = 'Connected! Querying Kusto...' + if (spinner) spinner.text = 'Connected! Querying Kusto...' // Only show docset stats if option is passed AND if the given path is not already a docset. options.showDocset = !(cleanPath === docsetPath) && options.compare - if (options.compare && cleanPath === docsetPath) { + if (options.compare && cleanPath === docsetPath && !options.quiet) { console.log(`\n\nSkipping comparison, since '${cleanPath}' is already a docset.\n`) } @@ -272,7 +288,7 @@ async function main(): Promise { : undefined, ]) - spinner.succeed('Data retrieved successfully!\n') + if (spinner) spinner.succeed('Data retrieved successfully!\n') // Output JSON and exit if (options.json) { @@ -399,17 +415,48 @@ async function main(): Promise { console.log(green('-------------------------------------------')) } catch (error) { - spinner.fail('Error getting data') - console.error(red('Error details:')) - console.error(error) + if (spinner) spinner.fail('Error getting data') + + if (options.json) { + // Output error in JSON format for consistent parsing + console.log( + JSON.stringify( + { + error: true, + message: 'Error getting data', + details: error instanceof Error ? error.message : String(error), + }, + null, + 2, + ), + ) + } else { + console.error(red('Error details:')) + console.error(error) + } } } try { await main() } catch (error) { - console.error(red('Unexpected error:')) - console.error(error) + if (options.json) { + // Output error in JSON format for consistent parsing + console.log( + JSON.stringify( + { + error: true, + message: 'Unexpected error', + details: error instanceof Error ? error.message : String(error), + }, + null, + 2, + ), + ) + } else { + console.error(red('Unexpected error:')) + console.error(error) + } process.exit(1) }