docstat quiet option (#58812)
This commit is contained in:
@@ -30,7 +30,7 @@
|
|||||||
"debug": "cross-env NODE_ENV=development ENABLED_LANGUAGES=en nodemon --inspect src/frame/server.ts",
|
"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",
|
"delete-orphan-translation-files": "tsx src/workflows/delete-orphan-translation-files.ts",
|
||||||
"docsaudit": "tsx src/metrics/scripts/docsaudit.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-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",
|
"deleted-features-pr-comment": "tsx src/data-directory/scripts/deleted-features-pr-comment.ts",
|
||||||
"deprecate-ghes": "tsx src/ghes-releases/scripts/deprecate/index.ts",
|
"deprecate-ghes": "tsx src/ghes-releases/scripts/deprecate/index.ts",
|
||||||
|
|||||||
@@ -11,9 +11,66 @@ CLI tools to fetch data from the Kusto API.
|
|||||||
1. Enter your `<username>@githubazure.com` credentials.
|
1. Enter your `<username>@githubazure.com` credentials.
|
||||||
* These will get cached for future logins.
|
* 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. 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. 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`.
|
1. Add the `KUSTO_CLUSTER` and `KUSTO_DATABASE` values to the `.env` (_these values are pinned in slack_):
|
||||||
```
|
```
|
||||||
KUSTO_CLUSTER='<value>'
|
KUSTO_CLUSTER='<value>'
|
||||||
KUSTO_DATABASE='<value>'
|
KUSTO_DATABASE='<value>'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## docstat usage
|
||||||
|
|
||||||
|
Run `npm run docstat -- <URL>` 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 -- <content directory name>
|
||||||
|
```
|
||||||
|
For example:
|
||||||
|
```
|
||||||
|
npm run docsaudit -- actions
|
||||||
|
```
|
||||||
|
|
||||||
|
## Future development
|
||||||
|
|
||||||
|
Applies to all scripts:
|
||||||
|
|
||||||
|
* The date range option only accepts a start date (via `-r <number>`, where the number means "`<number>` 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.
|
||||||
@@ -4,9 +4,7 @@ import {
|
|||||||
KustoResultTable,
|
KustoResultTable,
|
||||||
} from 'azure-kusto-data'
|
} from 'azure-kusto-data'
|
||||||
|
|
||||||
import dotenv from 'dotenv'
|
import 'dotenv/config'
|
||||||
|
|
||||||
dotenv.config()
|
|
||||||
|
|
||||||
if (!(process.env.KUSTO_CLUSTER || process.env.KUSTO_DATABASE)) {
|
if (!(process.env.KUSTO_CLUSTER || process.env.KUSTO_DATABASE)) {
|
||||||
console.error(`Add KUSTO_CLUSTER and KUSTO_DATABASE to your .env file`)
|
console.error(`Add KUSTO_CLUSTER and KUSTO_DATABASE to your .env file`)
|
||||||
|
|||||||
@@ -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/<SCRIPT_NAME>.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 <url>` 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 <content directory name>
|
|
||||||
```
|
|
||||||
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 <number>`, where the number means "`<number>` 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.
|
|
||||||
@@ -42,6 +42,7 @@ interface CliOptions {
|
|||||||
defaultToAll?: boolean
|
defaultToAll?: boolean
|
||||||
showDocset?: boolean
|
showDocset?: boolean
|
||||||
allVersions?: boolean
|
allVersions?: boolean
|
||||||
|
quiet?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface JsonOutput {
|
interface JsonOutput {
|
||||||
@@ -103,10 +104,16 @@ program
|
|||||||
'Get data for free-pro-team@latest only (default: all versions if URL is versionless)',
|
'Get data for free-pro-team@latest only (default: all versions if URL is versionless)',
|
||||||
)
|
)
|
||||||
.option('--verbose', 'Display Kusto queries being executed')
|
.option('--verbose', 'Display Kusto queries being executed')
|
||||||
|
.option('-q, --quiet', 'Suppress all output except results (automatically enabled with --json)')
|
||||||
.parse(process.argv)
|
.parse(process.argv)
|
||||||
|
|
||||||
const options = program.opts<CliOptions>()
|
const options = program.opts<CliOptions>()
|
||||||
|
|
||||||
|
// Auto-enable quiet mode when JSON output is requested
|
||||||
|
if (options.json) {
|
||||||
|
options.quiet = true
|
||||||
|
}
|
||||||
|
|
||||||
// If specific options are not provided, default to all
|
// If specific options are not provided, default to all
|
||||||
options.defaultToAll = !(
|
options.defaultToAll = !(
|
||||||
options.views ||
|
options.views ||
|
||||||
@@ -141,26 +148,34 @@ const usingFptOnly = !!options.fptOnly
|
|||||||
if (version === FREE_PRO_TEAM) {
|
if (version === FREE_PRO_TEAM) {
|
||||||
if (usingFptOnly) {
|
if (usingFptOnly) {
|
||||||
// User explicitly wants only free-pro-team@latest
|
// User explicitly wants only free-pro-team@latest
|
||||||
console.log(
|
if (!options.quiet) {
|
||||||
'\nFetching data for free-pro-team@latest only. To get all versions, omit the --fptOnly flag.\n',
|
console.log(
|
||||||
)
|
'\nFetching data for free-pro-team@latest only. To get all versions, omit the --fptOnly flag.\n',
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Default: all versions
|
// Default: all versions
|
||||||
version = null
|
version = null
|
||||||
console.log(
|
if (!options.quiet) {
|
||||||
'\nFetching data for all versions (no version specified in URL). To get only free-pro-team@latest, pass "--fptOnly".\n',
|
console.log(
|
||||||
)
|
'\nFetching data for all versions (no version specified in URL). To get only free-pro-team@latest, pass "--fptOnly".\n',
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Version is specified in the URL (e.g. enterprise-server@)
|
// Version is specified in the URL (e.g. enterprise-server@)
|
||||||
console.log(
|
if (!options.quiet) {
|
||||||
`\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) {
|
|
||||||
console.log(
|
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
|
// Always use the version from the URL
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,21 +209,22 @@ const queryPaths = [cleanPath].concat(redirects)
|
|||||||
const dates: DateRange = getDates(options.range)
|
const dates: DateRange = getDates(options.range)
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const spinner = ora('Connecting to Kusto...').start()
|
const spinner = options.quiet ? null : ora('Connecting to Kusto...').start()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const client = getKustoClient()
|
const client = getKustoClient()
|
||||||
|
|
||||||
if (!client) {
|
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)
|
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.
|
// Only show docset stats if option is passed AND if the given path is not already a docset.
|
||||||
options.showDocset = !(cleanPath === docsetPath) && options.compare
|
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`)
|
console.log(`\n\nSkipping comparison, since '${cleanPath}' is already a docset.\n`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +288,7 @@ async function main(): Promise<void> {
|
|||||||
: undefined,
|
: undefined,
|
||||||
])
|
])
|
||||||
|
|
||||||
spinner.succeed('Data retrieved successfully!\n')
|
if (spinner) spinner.succeed('Data retrieved successfully!\n')
|
||||||
|
|
||||||
// Output JSON and exit
|
// Output JSON and exit
|
||||||
if (options.json) {
|
if (options.json) {
|
||||||
@@ -399,17 +415,48 @@ async function main(): Promise<void> {
|
|||||||
|
|
||||||
console.log(green('-------------------------------------------'))
|
console.log(green('-------------------------------------------'))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
spinner.fail('Error getting data')
|
if (spinner) spinner.fail('Error getting data')
|
||||||
console.error(red('Error details:'))
|
|
||||||
console.error(error)
|
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 {
|
try {
|
||||||
await main()
|
await main()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(red('Unexpected error:'))
|
if (options.json) {
|
||||||
console.error(error)
|
// 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)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user