From 2f2a141face402e89453a9bdd12fe72d0b6991d5 Mon Sep 17 00:00:00 2001 From: Sarah Schneider Date: Tue, 24 Jun 2025 20:30:54 -0400 Subject: [PATCH 1/9] Docstat: CLI tool to fetch metrics from the Kusto API (#55917) --- package-lock.json | 522 +++++++++++++++++++++++- package.json | 1 + src/metrics/README.md | 19 + src/metrics/lib/dates.js | 28 ++ src/metrics/lib/kusto-client.js | 40 ++ src/metrics/queries/bounces.js | 39 ++ src/metrics/queries/constants.js | 24 ++ src/metrics/queries/exits-to-support.js | 38 ++ src/metrics/queries/survey-score.js | 42 ++ src/metrics/queries/users.js | 32 ++ src/metrics/queries/view-duration.js | 37 ++ src/metrics/queries/views.js | 32 ++ src/metrics/scripts/README.md | 81 ++++ src/metrics/scripts/docstat.js | 503 +++++++++++++++++++++++ 14 files changed, 1427 insertions(+), 11 deletions(-) create mode 100644 src/metrics/README.md create mode 100644 src/metrics/lib/dates.js create mode 100644 src/metrics/lib/kusto-client.js create mode 100644 src/metrics/queries/bounces.js create mode 100644 src/metrics/queries/constants.js create mode 100644 src/metrics/queries/exits-to-support.js create mode 100644 src/metrics/queries/survey-score.js create mode 100644 src/metrics/queries/users.js create mode 100644 src/metrics/queries/view-duration.js create mode 100644 src/metrics/queries/views.js create mode 100644 src/metrics/scripts/README.md create mode 100755 src/metrics/scripts/docstat.js diff --git a/package-lock.json b/package-lock.json index af7dd68ed44a..a08541746172 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", + "azure-kusto-data": "^7.0.0", "bottleneck": "2.19.5", "boxen": "8.0.1", "cheerio": "^1.0.0-rc.12", @@ -412,6 +413,153 @@ "playwright-core": ">= 1.0.0" } }, + "node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.9.0.tgz", + "integrity": "sha512-FPwHpZywuyasDSLMqJ6fhbOK3TqUdviZNF8OqRGA4W5Ewib2lEEZ+pBsYcBa88B2NGO/SEnYPGhyBqNlE8ilSw==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.4.tgz", + "integrity": "sha512-f7IxTD15Qdux30s2qFARH+JxgwxWLG2Rlr4oSkPGuLWm+1p5y1+C04XGLA0vmX6EtqfutmjvpNmAfgwVIS5hpw==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.20.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.6.1", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.21.0.tgz", + "integrity": "sha512-a4MBwe/5WKbq9MIxikzgxLBbruC5qlkFYlBdI7Ev50Y7ib5Vo/Jvt5jnJo7NaWeJ908LCHL0S1Us4UMf1VoTfg==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.8.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@typespec/ts-http-runtime": "^0.2.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.2.0.tgz", + "integrity": "sha512-UKTiEJPkWcESPYJz3X5uKRYyOcJD+4nYph+KpfdPRnQJVrZfk0KJgdnaAWKfhsBBtAf/D58Az4AvCJEmWgIBAg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.12.0.tgz", + "integrity": "sha512-13IyjTQgABPARvG90+N2dXpC+hwp466XCdQXPCRlbWHgd3SJd5Q1VvaBGv6k1BIa4MQm6hAF1UBU1m8QUxV8sQ==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@typespec/ts-http-runtime": "^0.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.10.1.tgz", + "integrity": "sha512-YM/z6RxRtFlXUH2egAYF/FDPes+MUE6ZoknjEdaq7ebJMMNUzn9zCJ3bd2ZZZlkP0r1xKa88kolhFH/FGV7JnA==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^4.2.0", + "@azure/msal-node": "^3.5.0", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.2.0.tgz", + "integrity": "sha512-0hKEzLhpw+ZTAfNJyRrn6s+V0nDWzXk9OjBr2TiGIu0OfMr5s2V4FpKLTAK3Ca5r5OKLbf4hkOGDPyiRjie/jA==", + "dependencies": { + "@typespec/ts-http-runtime": "^0.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.13.2.tgz", + "integrity": "sha512-lS75bF6FYZRwsacKLXc8UYu/jb+gOB7dtZq5938chCvV/zKTFDnzuXxCXhsSUh0p8s/P8ztgbfdueD9lFARQlQ==", + "dependencies": { + "@azure/msal-common": "15.7.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.7.1.tgz", + "integrity": "sha512-a0eowoYfRfKZEjbiCoA5bPT3IlWRAdGSvi63OU23Hv+X6EI8gbvXCoeqokUceFMoT9NfRUWTJSx5FiuzruqT8g==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.6.1.tgz", + "integrity": "sha512-ctcVz4xS+st5KxOlQqgpvA+uDFAa59CvkmumnuhlD2XmNczloKBdCiMQG7/TigSlaeHe01qoOlDjz3TyUAmKUg==", + "dependencies": { + "@azure/msal-common": "15.7.1", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@babel/code-frame": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", @@ -4730,6 +4878,11 @@ "version": "2.0.6", "license": "MIT" }, + "node_modules/@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" + }, "node_modules/@types/website-scraper": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/@types/website-scraper/-/website-scraper-1.2.10.tgz", @@ -5022,6 +5175,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.2.3.tgz", + "integrity": "sha512-oRhjSzcVjX8ExyaF8hC0zzTqxlVuRlgMHL/Bh4w3xB9+wjbm0FpXylVU/lBrn+kgphwYTrOk3tp+AVShGmlYCg==", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -5465,6 +5631,14 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -5720,8 +5894,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/available-typed-arrays": { "version": "1.0.7", @@ -5752,7 +5925,6 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", - "dev": true, "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -5770,6 +5942,24 @@ "node": ">= 0.4" } }, + "node_modules/azure-kusto-data": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/azure-kusto-data/-/azure-kusto-data-7.0.0.tgz", + "integrity": "sha512-JXvHTC/1oeJYgATcxuJ5dfMT6TdKQkMs6NbSCKreBGP+Wl6itgDMe06OIJDhbXYe7kcVu/CUAexuvdf0acAxBg==", + "dependencies": { + "@azure/core-util": "^1.10.0", + "@azure/identity": "^4.0.1", + "@types/uuid": "^8.3.4", + "axios": "^1.8.4", + "follow-redirects": "^1.15.1", + "https-browserify": "^1.0.0", + "stream-http": "^3.2.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 20.0.0" + } + }, "node_modules/babel-plugin-styled-components": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", @@ -5966,6 +6156,30 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -6419,7 +6633,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -6849,6 +7062,32 @@ "node": ">=0.10.0" } }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defer-to-connect": { "version": "2.0.1", "license": "MIT", @@ -6873,6 +7112,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-properties": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", @@ -6894,7 +7144,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -7094,6 +7343,14 @@ "version": "0.2.0", "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "license": "MIT" @@ -7239,7 +7496,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -8722,7 +8978,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -9256,7 +9511,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -9699,6 +9953,18 @@ "node": ">=8.0.0" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/http-proxy-middleware": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", @@ -9758,6 +10024,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/human-signals": { "version": "2.1.0", "dev": true, @@ -10083,6 +10366,20 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extendable": { "version": "0.1.1", "license": "MIT", @@ -10124,6 +10421,23 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-interactive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", @@ -10337,6 +10651,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -10478,6 +10806,27 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -10493,6 +10842,25 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -10679,16 +11047,46 @@ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, "node_modules/lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==" }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, "node_modules/lodash.isobject": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==" }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.kebabcase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", @@ -10700,6 +11098,11 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/lodash.snakecase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", @@ -12048,7 +12451,6 @@ }, "node_modules/mime-db": { "version": "1.52.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -12056,7 +12458,6 @@ }, "node_modules/mime-types": { "version": "2.1.35", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -12985,6 +13386,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", + "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -13463,7 +13881,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, "license": "MIT" }, "node_modules/ps-tree": { @@ -13634,6 +14051,19 @@ "react": ">=18" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -14043,6 +14473,17 @@ "xml2js": "^0.5.0" } }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "dev": true, @@ -14739,6 +15180,17 @@ "duplexer": "~0.1.1" } }, + "node_modules/stream-http": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", + "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -14754,6 +15206,33 @@ "dev": true, "license": "MIT" }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -15963,6 +16442,19 @@ "dev": true, "license": "WTFPL" }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/uvu": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", @@ -16630,6 +17122,14 @@ "node": ">=4.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yaml": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", diff --git a/package.json b/package.json index ce1679aad97c..64c778bbf72e 100644 --- a/package.json +++ b/package.json @@ -256,6 +256,7 @@ "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", + "azure-kusto-data": "^7.0.0", "bottleneck": "2.19.5", "boxen": "8.0.1", "cheerio": "^1.0.0-rc.12", diff --git a/src/metrics/README.md b/src/metrics/README.md new file mode 100644 index 000000000000..1fb56d230edb --- /dev/null +++ b/src/metrics/README.md @@ -0,0 +1,19 @@ +# Kusto tooling + +CLI tools to fetch data from the Kusto API. + +## Installation and authentication + +1. Install the Azure CLI with `brew install azure-cli`. + * If you have the option to **not** update all your brew packages, choose that, or it will take a really long time. +1. Run `az login`. + * You'll have to run `az login` whenever your session expires. The sessions are fairly long lasting. +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`. + ``` + KUSTO_CLUSTER='' + KUSTO_DATABASE='' + ``` \ No newline at end of file diff --git a/src/metrics/lib/dates.js b/src/metrics/lib/dates.js new file mode 100644 index 000000000000..355c78d3a8a8 --- /dev/null +++ b/src/metrics/lib/dates.js @@ -0,0 +1,28 @@ +const dateOpts = { + year: 'numeric', + month: 'long', + day: 'numeric', +} + +// Default to 30 days ago if a range option is not provided +export function getDates(range = '30') { + // Get current datetime in ISO format + const today = new Date() + const todayISO = today.toISOString() + + // Get datetime from N days ago in ISO format + const daysAgo = getDaysAgo(Number(range)) + const daysAgoISO = daysAgo.toISOString() + + return { + endDate: todayISO, + startDate: daysAgoISO, + friendlyRange: `${daysAgo.toLocaleDateString('en-US', dateOpts)} - ${today.toLocaleDateString('en-US', dateOpts)}`, + } +} + +function getDaysAgo(range) { + const daysAgo = new Date() + daysAgo.setDate(daysAgo.getDate() - range) + return daysAgo +} diff --git a/src/metrics/lib/kusto-client.js b/src/metrics/lib/kusto-client.js new file mode 100644 index 000000000000..eb8b13bdbd50 --- /dev/null +++ b/src/metrics/lib/kusto-client.js @@ -0,0 +1,40 @@ +import { Client as KustoClient, KustoConnectionStringBuilder } from 'azure-kusto-data' + +import dotenv from 'dotenv' +dotenv.config() +if (!(process.env.KUSTO_CLUSTER || process.env.KUSTO_DATABASE)) { + console.error(`Add KUSTO_CLUSTER and KUSTO_DATABASE to your .env file`) + process.exit(0) +} +const KUSTO_CLUSTER = process.env.KUSTO_CLUSTER +const KUSTO_DATABASE = process.env.KUSTO_DATABASE + +export function getKustoClient() { + let client + try { + const kcsb = KustoConnectionStringBuilder.withAzLoginIdentity(KUSTO_CLUSTER) + client = new KustoClient(kcsb) + } catch (error) { + console.error('Error connecting to Kusto') + console.error(error) + } + return client +} + +export async function runQuery(pathToFetch, query, client, queryType, verbose = false) { + // Display query if verbose mode is on + if (verbose) { + console.log(`\n--- EXECUTING QUERY FOR "${queryType.toUpperCase()}" ---`) + console.log(query) + console.log('----------------------\n') + } + + const results = await client.execute(KUSTO_DATABASE, query) + + if (results.primaryResults.length === 0) { + console.log(`No data found for URL: ${pathToFetch}`) + return null + } + + return results +} diff --git a/src/metrics/queries/bounces.js b/src/metrics/queries/bounces.js new file mode 100644 index 000000000000..465d6896bac3 --- /dev/null +++ b/src/metrics/queries/bounces.js @@ -0,0 +1,39 @@ +import { runQuery } from '#src/metrics/lib/kusto-client.js' +import { SHARED_DECLARATIONS, SHARED_FILTERS } from '#src/metrics/queries/constants.js' + +const QUERY_TYPE = 'bounces' + +export async function getBounces( + pathToFetch, + client, + dates, + version = null, + verbose = false, + queryType = QUERY_TYPE, +) { + const query = getBouncesQuery(pathToFetch, dates, version) + const results = await runQuery(pathToFetch, query, client, queryType, verbose) + const data = JSON.parse(results.primaryResults[0].toString()).data[0] + // Extract Bounces + const bounces = data.Bounces + return bounces +} + +export function getBouncesQuery(pathToFetch, dates, version) { + return ` + ${SHARED_DECLARATIONS(pathToFetch, dates, version)} + let _exits = () { + docs_v0_exit_event + ${SHARED_FILTERS} + }; + _exits + | summarize Bounces=round( + countif(exit_scroll_length < 0.1 and exit_visit_duration < 5) / + toreal(count()), + 2 + ) + | project Bounces=strcat(toint( + Bounces * 100 + ), '%') + ` +} diff --git a/src/metrics/queries/constants.js b/src/metrics/queries/constants.js new file mode 100644 index 000000000000..be732f5f01a7 --- /dev/null +++ b/src/metrics/queries/constants.js @@ -0,0 +1,24 @@ +// SHARED QUERY CONSTANTS +export const SHARED_DECLARATIONS = (path, dates, version) => + ` + let _article = dynamic(['${Array.isArray(path) ? path.join("', '") : path}']); + let _articleType = dynamic(null); + let _endTime = datetime(${dates.endDate || null}); + let _language = dynamic(null); + let _pageType = dynamic(null); + let _product = dynamic(null); + let _startTime = datetime(${dates.startDate || null}); + let _version = dynamic(${version ? `['${version}']` : null}); +`.trim() + +export const SHARED_FILTERS = ` + | where timestamp between (_startTime .. _endTime) + | where context.hostname == 'docs.github.com' + | where abs(totimespan(context.created - timestamp)) < 1h + | where isempty(_language) or tostring(context.path_language) in (_language) + | where isempty(_version) or tostring(context.path_version) in (_version) + | where isempty(_article) or context.path_article has_any (_article) + | where isempty(_product) or tostring(context.path_product) in (_product) + | where isempty(_pageType) or tostring(context.page_document_type) in (_pageType) + | where isempty(_articleType) or tostring(context.page_type) in (_articleType) +`.trim() diff --git a/src/metrics/queries/exits-to-support.js b/src/metrics/queries/exits-to-support.js new file mode 100644 index 000000000000..a4ac2d156d86 --- /dev/null +++ b/src/metrics/queries/exits-to-support.js @@ -0,0 +1,38 @@ +import { runQuery } from '#src/metrics/lib/kusto-client.js' +import { SHARED_DECLARATIONS, SHARED_FILTERS } from '#src/metrics/queries/constants.js' + +const QUERY_TYPE = 'exits' + +export async function getExitsToSupport( + pathToFetch, + client, + dates, + version = null, + verbose = false, + queryType = QUERY_TYPE, +) { + const query = getExitsQueryStatement(pathToFetch, dates, version) + const results = await runQuery(pathToFetch, query, client, queryType, verbose) + const data = JSON.parse(results.primaryResults[0].toString()).data[0] + // Extract Column1 + const exitsToSupport = data.Column1 + return exitsToSupport +} + +export function getExitsQueryStatement(pathToFetch, dates, version) { + return ` + ${SHARED_DECLARATIONS(pathToFetch, dates, version)} + let _links = () { + docs_v0_link_event + ${SHARED_FILTERS} + }; + _links + | where isempty(link_samesite) or link_samesite == false + | where link_samepage != true // allow false or null + | extend link_url_parsed=parse_url(link_url) + | extend IsSupport=tostring(link_url_parsed.Host) == "support.github.com" + and tostring(link_url_parsed.path) != "/enterprise/server-upgrade" + | summarize Ratio=round(countif(IsSupport) / toreal(count()), 2) + | project strcat(toint(Ratio * 100), '%') + ` +} diff --git a/src/metrics/queries/survey-score.js b/src/metrics/queries/survey-score.js new file mode 100644 index 000000000000..5c42290eb5b6 --- /dev/null +++ b/src/metrics/queries/survey-score.js @@ -0,0 +1,42 @@ +import { runQuery } from '#src/metrics/lib/kusto-client.js' +import { SHARED_DECLARATIONS, SHARED_FILTERS } from '#src/metrics/queries/constants.js' + +const QUERY_TYPE = 'score' + +export async function getScore( + pathToFetch, + client, + dates, + version = null, + verbose = false, + queryType = QUERY_TYPE, +) { + const query = getScoreQuery(pathToFetch, dates, version) + const results = await runQuery(pathToFetch, query, client, queryType, verbose) + const data = JSON.parse(results.primaryResults[0].toString()).data[0] + // Extract Score + const score = data.Score + return score +} + +export function getScoreQuery(pathToFetch, dates, version) { + return ` + ${SHARED_DECLARATIONS(pathToFetch, dates, version)} + let _surveys = () { + docs_v0_survey_event + ${SHARED_FILTERS} + // Filter out Copilot response thumbs up/down events + | where context.event_group_key != "ask-ai" + // UNIQUE DO NOT DELETE + | summarize timestamp=arg_max(timestamp, *) + by User=toguid(context.user), Path=tostring(context.path_article) + }; + _surveys + | summarize Score=round( + (countif(survey_vote) + 0.75 * 30) + / (count() + 30), + 2 + ) + | project Score=strcat(toint(Score * 100), '%') + ` +} diff --git a/src/metrics/queries/users.js b/src/metrics/queries/users.js new file mode 100644 index 000000000000..0df19d0f4514 --- /dev/null +++ b/src/metrics/queries/users.js @@ -0,0 +1,32 @@ +import { runQuery } from '#src/metrics/lib/kusto-client.js' +import { SHARED_DECLARATIONS, SHARED_FILTERS } from '#src/metrics/queries/constants.js' + +const QUERY_TYPE = 'users' + +export async function getUsers( + pathToFetch, + client, + dates, + version = null, + verbose = false, + queryType = QUERY_TYPE, +) { + const query = getUsersQuery(pathToFetch, dates, version) + const results = await runQuery(pathToFetch, query, client, queryType, verbose) + const data = JSON.parse(results.primaryResults[0].toString()).data[0] + // Extract Users + const users = data.Users + return users.toLocaleString() +} + +export function getUsersQuery(pathToFetch, dates, version) { + return ` + ${SHARED_DECLARATIONS(pathToFetch, dates, version)} + let _pages = () { + docs_v0_page_event + ${SHARED_FILTERS} + }; + _pages + | summarize Users=dcount(tostring(context.user)) + ` +} diff --git a/src/metrics/queries/view-duration.js b/src/metrics/queries/view-duration.js new file mode 100644 index 000000000000..95950e10f8ba --- /dev/null +++ b/src/metrics/queries/view-duration.js @@ -0,0 +1,37 @@ +import { runQuery } from '#src/metrics/lib/kusto-client.js' +import { SHARED_DECLARATIONS, SHARED_FILTERS } from '#src/metrics/queries/constants.js' + +const QUERY_TYPE = 'view duration' + +export async function getViewDuration( + pathToFetch, + client, + dates, + version = null, + verbose = false, + queryType = QUERY_TYPE, +) { + const query = getViewDurationQuery(pathToFetch, dates, version) + const results = await runQuery(pathToFetch, query, client, queryType, verbose) + const data = JSON.parse(results.primaryResults[0].toString()).data[0] + // Extract avg_VisitDuration + const viewDuration = data.avg_VisitDuration + return `${viewDuration} seconds` +} + +export function getViewDurationQuery(pathToFetch, dates, version) { + return ` + ${SHARED_DECLARATIONS(pathToFetch, dates, version)} + let _exits = () { + docs_v0_exit_event + ${SHARED_FILTERS} + }; + _exits + | summarize VisitDuration=round( + percentile(toreal(exit_visit_duration), 50), + 2 + ) + by bin(timestamp, 1d) + | summarize round(avg(VisitDuration), 2) + ` +} diff --git a/src/metrics/queries/views.js b/src/metrics/queries/views.js new file mode 100644 index 000000000000..80438d8c05ca --- /dev/null +++ b/src/metrics/queries/views.js @@ -0,0 +1,32 @@ +import { runQuery } from '#src/metrics/lib/kusto-client.js' +import { SHARED_DECLARATIONS, SHARED_FILTERS } from '#src/metrics/queries/constants.js' + +const QUERY_TYPE = 'views' + +export async function getViews( + pathToFetch, + client, + dates, + version = null, + verbose = false, + queryType = QUERY_TYPE, +) { + const query = getViewsQuery(pathToFetch, dates, version) + const results = await runQuery(pathToFetch, query, client, queryType, verbose) + const data = JSON.parse(results.primaryResults[0].toString()).data[0] + // Extract Views + const views = data.Views + return views.toLocaleString() +} + +export function getViewsQuery(pathToFetch, dates, version) { + return ` + ${SHARED_DECLARATIONS(pathToFetch, dates, version)} + let _pages = () { + docs_v0_page_event + ${SHARED_FILTERS} + }; + _pages + | summarize Views=count() + ` +} diff --git a/src/metrics/scripts/README.md b/src/metrics/scripts/README.md new file mode 100644 index 000000000000..5cbfbddcc21a --- /dev/null +++ b/src/metrics/scripts/README.md @@ -0,0 +1,81 @@ +## Scripts + +See documentation below for: + +* [docstat](#docstat) + + Run this on any GitHub Docs URL to gather a set of metrics about it. + +Print usage info for any script in this directory: + +```bash +tsx src/metrics/scripts/.js --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.js`) 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.js" + ``` +3. Save the file and reload your configuration: + ```bash + source ~/.bash_profile # or ~/.zshrc, etc. + ``` +Now you can run `docstat ` from any directory. + +## 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. \ No newline at end of file diff --git a/src/metrics/scripts/docstat.js b/src/metrics/scripts/docstat.js new file mode 100755 index 000000000000..65428ccb276b --- /dev/null +++ b/src/metrics/scripts/docstat.js @@ -0,0 +1,503 @@ +#!/usr/bin/env node + +import fs from 'fs' +import path from 'path' +import { Command } from 'commander' +import chalk from 'chalk' +import ora from 'ora' +import frontmatter from '@/frame/lib/read-frontmatter.js' +import { getKustoClient } from '#src/metrics/lib/kusto-client.js' +import { getDates } from 'src/metrics/lib/dates.js' +import { getViews } from '#src/metrics/queries/views.js' +import { getUsers } from '#src/metrics/queries/users.js' +import { getViewDuration } from '#src/metrics/queries/view-duration.js' +import { getBounces } from '#src/metrics/queries/bounces.js' +import { getScore } from '#src/metrics/queries/survey-score.js' +import { getExitsToSupport } from '#src/metrics/queries/exits-to-support.js' + +const { green, white, red, blue } = chalk + +const DOCS_ROOT = 'https://docs.github.com' +const DOCS_API_PATH = 'https://docs.github.com/api/pagelist/en' +const FREE_PRO_TEAM = 'free-pro-team@latest' +const ENTERPRISE_REGEX = /enterprise-(server|cloud)@/ + +const program = new Command() + +program + .name('docstat') + .description( + `Get a data snapshot of a given Docs URL for the last 30 days or specified period. By default, it looks up: + +- Views +- Users +- View duration per day average +- Bounces +- Helpfulness score +- Exits to support`, + ) + .argument('', 'URL to query data for') + .option('-r, --range ', 'Number of days to look back', 30) + .option('-c, --compare', 'Compare with top-level docset data') + .option('-v, --views', 'Get page views') + .option('-u, --users', 'Get unique users') + .option('-d, --viewDuration', 'Get view duration per day average') + .option('-b, --bounces', 'Get bounces') + .option('-s, --score', 'Get helpfulness survey score') + .option('-e, --exits', 'Get exits to support percentage') + .option('-j, --json', 'Output results in JSON format') + .option('-k, --skipValidation', 'Skip path validation against Docs API pagelist') + .option('--redirects', 'Include all redirected URLs for the given URL in the queries') + .option( + '--fptOnly', + 'Get data for free-pro-team@latest only (default: all versions if URL is versionless)', + ) + .option('--verbose', 'Display Kusto queries being executed') + .parse(process.argv) + +const options = program.opts() + +// If specific options are not provided, default to all +options.defaultToAll = !( + options.views || + options.users || + options.viewDuration || + options.bounces || + options.score || + options.exits +) + +// If defaulting to all, set all options to true +if (options.defaultToAll) { + options.views = true + options.users = true + options.viewDuration = true + options.bounces = true + options.score = true + options.exits = true +} + +// Get the path to query +// Given input: https://docs.github.com/en/copilot/managing-copilot/ +// Use: copilot/managing-copilot +const providedPath = program.args[0] +let cleanPath = getCleanPath(providedPath) + +// Get the version +let version = getVersion(cleanPath) +let usingFptOnly = !!options.fptOnly + +// If the URL does not specify a version, default to all versions unless --fptOnly is passed +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', + ) + } 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', + ) + } +} 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) { + 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 +} + +// Get the version-specific Docs API path +const VERSIONED_DOCS_API_PATH = path.join(DOCS_API_PATH, version || FREE_PRO_TEAM) +// Remove the version from the path for queries +cleanPath = removeVersionSegment(cleanPath, version || FREE_PRO_TEAM) +// Validate the path against the Docs API pagelist +if (!options.skipValidation) await validatePath(cleanPath, version || FREE_PRO_TEAM) + +if (options.allVersions) version = null + +// Get the path for the overall docset +const docsetPath = cleanPath.split('/')[0] + +// Get redirect_from frontmatter and include those paths in the queries +let redirects = [] +if (options.redirects) { + let contentPath = path.join('content', cleanPath) + contentPath = fs.existsSync(contentPath) + ? path.join(contentPath, 'index.md') + : `${contentPath}.md` + const { data } = frontmatter(fs.readFileSync(contentPath, 'utf8')) + // If redirect_from paths exists, they'll be in this format: /foo/bar + redirects = (data.redirect_from || []).map((oldPath) => oldPath.replace('/', '')) // remove leading '/' +} + +const queryPaths = [cleanPath].concat(redirects) + +// Get dates object in format { endDate, startDate, friendlyRange } +const dates = getDates(options.range) + +async function main() { + const spinner = ora('Connecting to Kusto...').start() + + try { + const client = getKustoClient() + 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) { + console.log(`\n\nSkipping comparison, since '${cleanPath}' is already a docset.\n`) + } + + // Create query promises for all requested metrics + const queryPromises = [] + const results = {} + + // Setup all the promises for parallel execution + if (options.views) { + const queryType = 'views' + queryPromises.push( + getViews(queryPaths, client, dates, version, options.verbose, queryType).then((data) => { + results.views = data + }), + ) + if (options.showDocset) { + const queryType = 'docset views' + queryPromises.push( + getViews(docsetPath, client, dates, version, options.verbose, queryType).then((data) => { + results.viewsDocset = data + }), + ) + } + } + + if (options.users) { + const queryType = 'users' + queryPromises.push( + getUsers(queryPaths, client, dates, version, options.verbose, queryType).then((data) => { + results.users = data + }), + ) + if (options.showDocset) { + const queryType = 'docset users' + queryPromises.push( + getUsers(docsetPath, client, dates, version, options.verbose, queryType).then((data) => { + results.usersDocset = data + }), + ) + } + } + + if (options.viewDuration) { + const queryType = 'view duration' + queryPromises.push( + getViewDuration(queryPaths, client, dates, version, options.verbose, queryType).then( + (data) => { + results.viewDuration = data + }, + ), + ) + if (options.showDocset) { + const queryType = 'docset view duration' + queryPromises.push( + getViewDuration(docsetPath, client, dates, version, options.verbose, queryType).then( + (data) => { + results.viewDurationDocset = data + }, + ), + ) + } + } + + if (options.bounces) { + const queryType = 'bounces' + queryPromises.push( + getBounces(queryPaths, client, dates, version, options.verbose, queryType).then((data) => { + results.bounces = data + }), + ) + if (options.showDocset) { + const queryType = 'docset bounces' + queryPromises.push( + getBounces(docsetPath, client, dates, version, options.verbose, queryType).then( + (data) => { + results.bouncesDocset = data + }, + ), + ) + } + } + + if (options.score) { + const queryType = 'score' + queryPromises.push( + getScore(queryPaths, client, dates, version, options.verbose, queryType).then((data) => { + results.score = data + }), + ) + if (options.showDocset) { + const queryType = 'docset score' + queryPromises.push( + getScore(docsetPath, client, dates, version, options.verbose, queryType).then((data) => { + results.scoreDocset = data + }), + ) + } + } + + if (options.exits) { + const queryType = 'exits' + queryPromises.push( + getExitsToSupport(queryPaths, client, dates, version, options.verbose, queryType).then( + (data) => { + results.exits = data + }, + ), + ) + if (options.showDocset) { + const queryType = 'docset exits' + queryPromises.push( + getExitsToSupport(docsetPath, client, dates, version, options.verbose, queryType).then( + (data) => { + results.exitsDocset = data + }, + ), + ) + } + } + + // Execute all queries in parallel + await Promise.all(queryPromises) + + spinner.succeed('Data retrieved successfully!\n') + + // Extract all results from the results object + const { + views, + viewsDocset, + users, + usersDocset, + viewDuration, + viewDurationDocset, + bounces, + bouncesDocset, + score, + scoreDocset, + exits, + exitsDocset, + } = results + + // Output JSON and exit + if (options.json) { + const jsonOutput = { + daysRange: options.range, + startDate: dates.startDate, + endDate: dates.endDate, + dateRange: dates.friendlyRange, + inputUrl: program.args[0], + data: { + path: cleanPath, + }, + } + + // Add requested data points + if (options.views) { + jsonOutput.data.views = views + } + if (options.users) { + jsonOutput.data.users = users + } + if (options.viewDuration) { + jsonOutput.data.viewDuration = viewDuration + } + if (options.bounces) { + jsonOutput.data.bounces = bounces + } + if (options.score) { + jsonOutput.data.score = score + } + if (options.exits) { + jsonOutput.data.exits = exits + } + + // Add docset comparison if requested + if (options.showDocset) { + jsonOutput.docset = { + path: docsetPath, + data: {}, + } + + if (options.views) { + jsonOutput.docset.data.views = viewsDocset + } + if (options.users) { + jsonOutput.docset.data.users = usersDocset + } + if (options.viewDuration) { + jsonOutput.docset.data.viewDuration = viewDurationDocset + } + if (options.bounces) { + jsonOutput.docset.data.bounces = bouncesDocset + } + if (options.score) { + jsonOutput.docset.data.score = scoreDocset + } + if (options.exits) { + jsonOutput.docset.data.exits = exitsDocset + } + } + + console.log(JSON.stringify(jsonOutput, null, 2)) + return // Exit early + } + + console.log(white(`Last ${options.range} days:`), blue(dates.friendlyRange)) + console.log(green('-------------------------------------------')) + console.log(green('Path:'), white(cleanPath)) + if (options.redirects) { + console.log( + green('Redirects included:'), + white(redirects.length ? redirects.join(', ') : 'none found'), + ) + } + console.log(green('Version:'), white(version || 'all versions')) + console.log('') + + if (options.views) { + console.log(green('Views:'), white(views)) + } + if (options.users) { + console.log(green('Users:'), white(users)) + } + if (options.viewDuration) { + console.log(green('View duration per day average:'), white(viewDuration)) + } + if (options.bounces) { + console.log(green('Bounces:'), white(`${bounces}`)) + } + if (options.score) { + console.log(green('Score:'), white(`${score}`)) + } + if (options.exits) { + console.log(green(`Exits to support:`), white(`${exits}`)) + } + + if (options.showDocset) { + console.log('') + console.log(white('Comparing to...')) + console.log(green('-------------------------------------------')) + console.log(green('Docset:'), white(docsetPath)) + console.log(green('Version:'), white(version || 'all versions')) + console.log('') + + if (options.views) { + console.log(green('Views:'), white(viewsDocset)) + } + if (options.users) { + console.log(green('Users:'), white(usersDocset)) + } + if (options.viewDuration) { + console.log(green(`View duration per day average:`), white(`${viewDurationDocset}`)) + } + if (options.bounces) { + console.log(green(`Bounces:`), white(`${bouncesDocset}`)) + } + if (options.score) { + console.log(green(`Score:`), white(`${scoreDocset}`)) + } + if (options.exits) { + console.log(green(`Exits to support:`), white(`${exitsDocset}`)) + } + } + + console.log(green('-------------------------------------------')) + } catch (error) { + spinner.fail('Error getting data') + console.error(red('Error details:')) + console.error(error) + } +} + +main().catch((error) => { + console.error(red('Unexpected error:')) + console.error(error) + process.exit(1) +}) + +/* -------- UTILITY FUNCTIONS -------- */ + +// Given input: https://docs.github.com/en/copilot/managing-copilot/ +// Use: copilot/managing-copilot +function getCleanPath(providedPath) { + let clean = providedPath + const cleanArr = clean.split('?') // remove query params + if (cleanArr.length > 1) cleanArr.pop() + clean = cleanArr.join('/') + const cleanArr2 = clean.split('#') // remove hash fragments + if (cleanArr2.length > 1) cleanArr2.pop() + clean = cleanArr2.join('/') + if (clean === DOCS_ROOT || clean === `${DOCS_ROOT}/en`) { + // Kusto uses 'index' for the homepage + return 'index' + } + const pathParts = clean.replace(DOCS_ROOT, '').split('/').filter(Boolean) + if (pathParts[0] === 'en') pathParts.shift() // remove English lang code + clean = pathParts.join('/') + + return clean +} + +function getVersion(cleanPath) { + const pathParts = cleanPath.split('/') + const version = ENTERPRISE_REGEX.test(pathParts[0]) ? pathParts[0] : FREE_PRO_TEAM + return version +} + +function removeVersionSegment(cleanPath, version) { + if (version === FREE_PRO_TEAM) return cleanPath + const pathParts = cleanPath.split('/') + pathParts.shift() + if (!pathParts.length) return 'index' + return pathParts.join('/') +} + +// Try to find the path in the list of valid pages at https://docs.github.com/api/pagelist/en +async function validatePath(cleanPath, version) { + // Only Kusto uses 'index' for the homepage; the Docs API uses '/en' + const basePath = cleanPath === 'index' ? '' : cleanPath + + const pathToCheck = + version === FREE_PRO_TEAM + ? path.join('/', 'en', basePath) + : path.join('/', 'en', version, basePath) + + let data + try { + const response = await fetch(VERSIONED_DOCS_API_PATH) + data = await response.text() + } catch (err) { + console.error(`Error fetching data from ${VERSIONED_DOCS_API_PATH}`) + } + + if (data.startsWith('{')) { + if (JSON.parse(data).error) { + console.error(data) + process.exit(1) + } + } + + const isValid = data.includes(pathToCheck) + if (!isValid) { + console.error( + `Error! Provided URL is not in Docs API list of valid paths at ${VERSIONED_DOCS_API_PATH}`, + ) + process.exit(1) + } +} From 983b8c24364461af0eff182b1cae8a4c66ff3b11 Mon Sep 17 00:00:00 2001 From: Sarah Schneider Date: Tue, 24 Jun 2025 21:10:51 -0400 Subject: [PATCH 2/9] Script to audit a top-level content dir (#56208) --- src/metrics/scripts/README.md | 21 +++++++++ src/metrics/scripts/docsaudit.js | 80 ++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 src/metrics/scripts/docsaudit.js diff --git a/src/metrics/scripts/README.md b/src/metrics/scripts/README.md index 5cbfbddcc21a..2a6c94b16b5f 100644 --- a/src/metrics/scripts/README.md +++ b/src/metrics/scripts/README.md @@ -6,6 +6,10 @@ See documentation below for: 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 @@ -70,6 +74,23 @@ To use `docstat` from any location in Terminal, set up a global alias: ``` 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.js --help +``` +Run the script on any top-level content directory: +``` +tsx src/metrics/scripts/docsaudit.js +``` +For example: +``` +tsx src/metrics/scripts/docsaudit.js actions +``` + ## Future development Applies to all scripts in this directory: diff --git a/src/metrics/scripts/docsaudit.js b/src/metrics/scripts/docsaudit.js new file mode 100644 index 000000000000..ff87cafee1b1 --- /dev/null +++ b/src/metrics/scripts/docsaudit.js @@ -0,0 +1,80 @@ +#!/usr/bin/env node + +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' +import { Command } from 'commander' +import walkFiles from '#src/workflows/walk-files.ts' +import readFrontmatter from '@/frame/lib/read-frontmatter.js' +import { getKustoClient } from '#src/metrics/lib/kusto-client.js' +import { getDates } from 'src/metrics/lib/dates.js' +import { getViews } from '#src/metrics/queries/views.js' +import { getUsers } from '#src/metrics/queries/users.js' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const ROOTDIR = process.cwd() + +const program = new Command() + +program + .name('docsaudit') + .description('Get data about a top-level docs product and output a CSV') + .argument('', 'Name of the content directory you want to audit, e.g., actions') + .option('-r, --range ', 'Number of days to look back', 30) + .option('--verbose', 'Display Kusto queries being executed') + .parse(process.argv) + +const options = program.opts() +const [auditDirName] = program.args +const contentDir = path.join(ROOTDIR, 'content') +const auditDir = path.join(contentDir, auditDirName) +const outputFile = path.join(__dirname, `${auditDirName}-audit.csv`) + +if (!fs.existsSync(auditDir)) { + console.error(`${auditDirName} not found`) + process.exit(1) +} + +// Get dates object in format { endDate, startDate, friendlyRange } +const dates = getDates(options.range) + +const files = walkFiles(auditDir, ['.md']) +console.log(`Auditing the ${files.length} "${auditDirName}" files. This may take a while.\n`) + +main() + +async function main() { + const client = getKustoClient() + + let csvString = `title,path,versions,${options.range}d views,${options.range}d users\n` + console.log(`Assembling data for these CSV columns: ${csvString}`) + + // Get the title, path, and versions from the filesystem + // Get the views and users from the Kusto API + const results = [] + for (const file of files) { + const contents = await fs.promises.readFile(file) + const contentPath = path.relative(ROOTDIR, file) + const { data } = readFrontmatter(contents) + const versionString = JSON.stringify(data.versions).replaceAll('"', "'") + const pathToQuery = getPathToQuery(file) + // Pass null to get all versions (the default if no version is provided) + const version = null + // Only pass true for verbose on the first iteration + const isFirst = results.length === 0 + const views = await getViews(pathToQuery, client, dates, version, options.verbose && isFirst) + const users = await getUsers(pathToQuery, client, dates, version, options.verbose && isFirst) + const csvEntry = `"${data.title}","${contentPath}","${versionString}","${views}","${users}"` + console.log(csvEntry) + results.push(csvEntry) + } + csvString += results.join('\n') + '\n' + + fs.writeFileSync(outputFile, csvString.trim(), 'utf8') + console.log(`Done! Wrote ${outputFile}`) +} + +function getPathToQuery(file) { + return path.relative(contentDir, file).replace('/index.md', '').replace('.md', '') +} From 954672bd907a484739d0981e481c2beb372d4039 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Tue, 24 Jun 2025 19:17:08 -0700 Subject: [PATCH 3/9] Enable 'Set up your project' string to be translated (#56244) --- src/frame/lib/frontmatter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frame/lib/frontmatter.js b/src/frame/lib/frontmatter.js index d3942a44cf91..a5aabd505dc8 100644 --- a/src/frame/lib/frontmatter.js +++ b/src/frame/lib/frontmatter.js @@ -117,6 +117,7 @@ export const schema = { // allows you to use an alternate heading for the popular column popularHeading: { type: 'string', + translatable: true, }, videos: { type: 'array', From 69625e035cd6a90bcea5c15dd01c6abf90e1c90d Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Tue, 24 Jun 2025 19:22:14 -0700 Subject: [PATCH 4/9] Fix webhook documentation property inconsistency (#56277) --- src/webhooks/scripts/webhook.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webhooks/scripts/webhook.ts b/src/webhooks/scripts/webhook.ts index 16f7983574a7..3a04d1654514 100644 --- a/src/webhooks/scripts/webhook.ts +++ b/src/webhooks/scripts/webhook.ts @@ -6,6 +6,7 @@ import { getBodyParams, TransformedParam } from '../../rest/scripts/utils/get-bo const NO_CHILD_PROPERTIES = [ 'action', + 'comment', 'enterprise', 'installation', 'organization', From fc8500c1b7879b41a06f8bbb034cf8711797a454 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Tue, 24 Jun 2025 19:24:05 -0700 Subject: [PATCH 5/9] Add alert container tracking for link analytics (#56209) --- src/content-render/tests/render-content.js | 14 ++++++++++++++ src/content-render/unified/alerts.js | 1 + src/events/lib/schema.ts | 1 + src/frame/components/ui/Alert/Alert.tsx | 1 + 4 files changed, 17 insertions(+) diff --git a/src/content-render/tests/render-content.js b/src/content-render/tests/render-content.js index a95e6c6946f2..4c86dbe82231 100644 --- a/src/content-render/tests/render-content.js +++ b/src/content-render/tests/render-content.js @@ -243,4 +243,18 @@ var a = 1 expect(el.data('clipboard')).toBe(2967273189) // Generates a murmurhash based ID that matches a
   })
+
+  test('renders alerts with data-container attribute for analytics', async () => {
+    const template = nl(`
+> [!NOTE]
+> This is a note with a [link](https://example.com)
+    `)
+    const html = await renderContent(template, { alertTitles: { NOTE: 'Note' } })
+    const $ = cheerio.load(html)
+    const alertEl = $('.ghd-alert')
+    expect(alertEl.length).toBe(1)
+    expect(alertEl.attr('data-container')).toBe('alert')
+    expect(alertEl.hasClass('ghd-alert-accent')).toBe(true)
+    expect(alertEl.find('a[href="https://example.com"]').length).toBe(1)
+  })
 })
diff --git a/src/content-render/unified/alerts.js b/src/content-render/unified/alerts.js
index d4feac161b8e..67a9937eea93 100644
--- a/src/content-render/unified/alerts.js
+++ b/src/content-render/unified/alerts.js
@@ -34,6 +34,7 @@ export default function alerts({ alertTitles = {} }) {
       const alertType = alertTypes[getAlertKey(node).toUpperCase()]
       node.tagName = 'div'
       node.properties.className = 'ghd-alert ghd-alert-' + alertType.color
+      node.properties.dataContainer = 'alert'
       node.children = [
         h(
           'p',
diff --git a/src/events/lib/schema.ts b/src/events/lib/schema.ts
index 733d8eceb9c4..9446440d99e9 100644
--- a/src/events/lib/schema.ts
+++ b/src/events/lib/schema.ts
@@ -320,6 +320,7 @@ const link = {
         'lead',
         'notifications',
         'article',
+        'alert',
         'toc',
         'footer',
         'static',
diff --git a/src/frame/components/ui/Alert/Alert.tsx b/src/frame/components/ui/Alert/Alert.tsx
index 58c77975a52e..ea6e20a7e2d5 100644
--- a/src/frame/components/ui/Alert/Alert.tsx
+++ b/src/frame/components/ui/Alert/Alert.tsx
@@ -25,6 +25,7 @@ export function Alert({ className, html, children, type = 'IMPORTANT' }: AlertPr
   return (
     

From 34b4908b42f5a3f8d14a45a524558d55eccbc96c Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Tue, 24 Jun 2025 19:25:49 -0700 Subject: [PATCH 6/9] Add ignore-not-found option for feature-flagged docs URLs (#56273) --- .../validate-github-github-docs-urls.yml | 1 + .../validate-github-github-docs-urls/index.ts | 4 ++++ .../validate.ts | 21 ++++++++++++++++--- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/validate-github-github-docs-urls.yml b/.github/workflows/validate-github-github-docs-urls.yml index 0651b878f7c5..9e2b0917c7c4 100644 --- a/.github/workflows/validate-github-github-docs-urls.yml +++ b/.github/workflows/validate-github-github-docs-urls.yml @@ -50,6 +50,7 @@ jobs: # do other things in other steps. npm run validate-github-github-docs-urls -- validate \ --output checks.json \ + --ignore-not-found \ github/config/docs-urls.json - name: Update config/docs-urls.json in github/github (possibly) diff --git a/src/links/scripts/validate-github-github-docs-urls/index.ts b/src/links/scripts/validate-github-github-docs-urls/index.ts index 79cac3a2aaf4..135ff4445d3c 100644 --- a/src/links/scripts/validate-github-github-docs-urls/index.ts +++ b/src/links/scripts/validate-github-github-docs-urls/index.ts @@ -13,6 +13,10 @@ program .description('Validate config/docs-urls.json in github/github') .option('--fail-on-warning', 'Any warning will make the process exit with a non-zero code') .option('--fail-on-error', 'Any error will make the process exit with a non-zero code') + .option( + '--ignore-not-found', + 'Do not fail validation on 404 errors (pages not found) - useful for feature-flagged content', + ) .option('-o, --output ', 'Output file') .argument('', 'path to the docs-urls JSON file') .action(validate) diff --git a/src/links/scripts/validate-github-github-docs-urls/validate.ts b/src/links/scripts/validate-github-github-docs-urls/validate.ts index ab750c583271..8c5824c1d386 100644 --- a/src/links/scripts/validate-github-github-docs-urls/validate.ts +++ b/src/links/scripts/validate-github-github-docs-urls/validate.ts @@ -7,6 +7,7 @@ type Options = { failOnWarning?: boolean failOnError?: boolean output?: string + ignoreNotFound?: boolean } export async function validate(filePath: string, options: Options) { @@ -26,8 +27,13 @@ export async function validate(filePath: string, options: Options) { console.log(prefix, `✅ ${check.url} (${check.identifier})`) } } else { - if (options.failOnError) exitCode++ - console.log(prefix, `❌ ${check.url} (${check.identifier})`) + // This is a 404 - page not found + if (options.ignoreNotFound) { + console.log(prefix, `⚠️ ${check.url} (${check.identifier})`) + } else { + if (options.failOnError) exitCode++ + console.log(prefix, `❌ ${check.url} (${check.identifier})`) + } } if (check.fragment) { if (check.fragmentFound) { @@ -58,7 +64,16 @@ export async function validate(filePath: string, options: Options) { chalk.yellow(T('Redirects')), checks.filter((check) => check.found && check.redirect).length, ) - console.log(chalk.red(T('Failures')), checks.filter((check) => !check.found).length) + const notFoundChecks = checks.filter((check) => !check.found) + + if (options.ignoreNotFound) { + console.log(chalk.red(T('Failures')), 0) + if (notFoundChecks.length > 0) { + console.log(chalk.yellow(T('Ignored (404s)')), notFoundChecks.length) + } + } else { + console.log(chalk.red(T('Failures')), notFoundChecks.length) + } console.log( chalk.red(T('Failing fragments')), checks.filter((check) => check.found && check.fragment && !check.fragmentFound).length, From f8b29bf093139cd8b46429b2d21ab3d92728ba95 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Tue, 24 Jun 2025 19:26:07 -0700 Subject: [PATCH 7/9] Remove outdated move-help-wanted-issues workflow (#56207) --- .github/workflows/move-help-wanted-issues.yml | 28 ------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/move-help-wanted-issues.yml diff --git a/.github/workflows/move-help-wanted-issues.yml b/.github/workflows/move-help-wanted-issues.yml deleted file mode 100644 index 78fa8dbbad65..000000000000 --- a/.github/workflows/move-help-wanted-issues.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Move help wanted issues - -# **What it does**: In the open source repo, when the "help wanted" or "good first issue" labels are added to an issue, the issue is added to the "Help wanted" column on the project board. -# **Why we have it**: To keep track of help wanted issues. -# **Who does it impact**: Open-source contributors. - -on: - issues: - types: - - labeled - -permissions: - contents: read - -jobs: - move_issues: - if: >- - ${{ - github.repository == 'github/docs' && - (github.event.label.name == 'help wanted' || github.event.label.name == 'good first issue') - }} - runs-on: ubuntu-latest - steps: - - uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 - with: - project: Docs open source board - column: Help wanted - repo-token: ${{ secrets.DOCS_BOT_PAT_BASE }} From c9a1a7154af92c4dd8d0287ac92f563974f9bd7d Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Tue, 24 Jun 2025 19:26:14 -0700 Subject: [PATCH 8/9] Add usage examples for Article API and Search API in llms.txt (#56234) --- src/frame/middleware/llms-txt.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frame/middleware/llms-txt.ts b/src/frame/middleware/llms-txt.ts index e0c7cef1dfda..dd4135786119 100644 --- a/src/frame/middleware/llms-txt.ts +++ b/src/frame/middleware/llms-txt.ts @@ -56,8 +56,8 @@ function generateBasicLlmsTxt(): string { ## Docs Content - [Page List API](${BASE_API_URL}/en/free-pro-team@latest) -- [Article API](https://docs.github.com/api/article) -- [Search API](https://docs.github.com/api/search) +- [Article API](https://docs.github.com/api/article): \`curl "https://docs.github.com/api/article?pathname=/en/get-started/start-your-journey/about-github-and-git"\` +- [Search API](https://docs.github.com/api/search): \`curl "https://docs.github.com/api/search?query=actions&language=en&version=free-pro-team@latest"\` ## Translations From 04f45f8e9bc53b18f9ac3bbd7c1a7837907077f3 Mon Sep 17 00:00:00 2001 From: docs-bot <77750099+docs-bot@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:23:09 -0700 Subject: [PATCH 9/9] Update OpenAPI Description (#56204) Co-authored-by: Sunbrye Ly <56200261+sunbrye@users.noreply.github.com> --- src/rest/data/fpt-2022-11-28/schema.json | 120 ++++----- .../data/ghes-3.15-2022-11-28/schema.json | 252 +++++++++--------- 2 files changed, 186 insertions(+), 186 deletions(-) diff --git a/src/rest/data/fpt-2022-11-28/schema.json b/src/rest/data/fpt-2022-11-28/schema.json index e0872d6cefec..bb7db089882b 100644 --- a/src/rest/data/fpt-2022-11-28/schema.json +++ b/src/rest/data/fpt-2022-11-28/schema.json @@ -2792,13 +2792,13 @@ } ], "previews": [], - "descriptionHTML": "

Get the GitHub-hosted runners limits for an organization.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Get the GitHub-hosted runners limits for an organization.

" }, { "serverUrl": "https://api.github.com", @@ -6252,13 +6252,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets the GitHub Actions permissions policy for a repository, including whether GitHub Actions is enabled and the actions and reusable workflows allowed to run in the repository.

\n

OAuth tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets the GitHub Actions permissions policy for a repository, including whether GitHub Actions is enabled and the actions and reusable workflows allowed to run in the repository.

\n

OAuth tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" }, { "serverUrl": "https://api.github.com", @@ -6425,13 +6425,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets the level of access that workflows outside of the repository have to actions and reusable workflows in the repository.\nThis endpoint only applies to private repositories.\nFor more information, see \"Allowing access to components in a private repository.\"

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets the level of access that workflows outside of the repository have to actions and reusable workflows in the repository.\nThis endpoint only applies to private repositories.\nFor more information, see \"Allowing access to components in a private repository.\"

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" }, { "serverUrl": "https://api.github.com", @@ -6599,13 +6599,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets the settings for selected actions and reusable workflows that are allowed in a repository. To use this endpoint, the repository policy for allowed_actions must be configured to selected. For more information, see \"Set GitHub Actions permissions for a repository.\"

\n

OAuth tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets the settings for selected actions and reusable workflows that are allowed in a repository. To use this endpoint, the repository policy for allowed_actions must be configured to selected. For more information, see \"Set GitHub Actions permissions for a repository.\"

\n

OAuth tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" }, { "serverUrl": "https://api.github.com", @@ -7029,13 +7029,13 @@ } ], "previews": [], - "descriptionHTML": "

Lists all secrets available in an organization without revealing their\nencrypted values.

\n

Authenticated users must have collaborator access to a repository to create, update, or read secrets.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint. If the repository is private, the repo scope is also required.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Lists all secrets available in an organization without revealing their\nencrypted values.

\n

Authenticated users must have collaborator access to a repository to create, update, or read secrets.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint. If the repository is private, the repo scope is also required.

" }, { "serverUrl": "https://api.github.com", @@ -10476,13 +10476,13 @@ } ], "previews": [], - "descriptionHTML": "

Creates a new self-hosted runner group for an organization.

\n

OAuth tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "201", "description": "

Created

" } - ] + ], + "descriptionHTML": "

Creates a new self-hosted runner group for an organization.

\n

OAuth tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

" }, { "serverUrl": "https://api.github.com", @@ -10628,13 +10628,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets a specific self-hosted runner group for an organization.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets a specific self-hosted runner group for an organization.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

" }, { "serverUrl": "https://api.github.com", @@ -10894,13 +10894,13 @@ } ], "previews": [], - "descriptionHTML": "

Deletes a self-hosted runner group for an organization.

\n

OAuth tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

Deletes a self-hosted runner group for an organization.

\n

OAuth tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

" }, { "serverUrl": "https://api.github.com", @@ -20845,13 +20845,13 @@ } ], "previews": [], - "descriptionHTML": "

Lists all organization variables.

\n

Authenticated users must have collaborator access to a repository to create, update, or read variables.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint. If the repository is private, the repo scope is also required.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Lists all organization variables.

\n

Authenticated users must have collaborator access to a repository to create, update, or read variables.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint. If the repository is private, the repo scope is also required.

" }, { "serverUrl": "https://api.github.com", @@ -67540,13 +67540,13 @@ } ], "previews": [], - "descriptionHTML": "

Note

\n

\nThis API is not built to serve real-time use cases. Depending on the time of day, event latency can be anywhere from 30s to 6h.

\n
", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Note

\n

\nThis API is not built to serve real-time use cases. Depending on the time of day, event latency can be anywhere from 30s to 6h.

\n
" }, { "serverUrl": "https://api.github.com", @@ -108743,13 +108743,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets the summary of the free and paid GitHub Actions minutes used.

\n

Paid minutes only apply to workflows in private repositories that use GitHub-hosted runners. Minutes used is listed for each GitHub-hosted runner operating system. Any job re-runs are also included in the usage. The usage returned includes any minute multipliers for macOS and Windows runners, and is rounded up to the nearest whole minute. For more information, see \"Managing billing for GitHub Actions\".

\n

OAuth app tokens and personal access tokens (classic) need the repo or admin:org scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets the summary of the free and paid GitHub Actions minutes used.

\n

Paid minutes only apply to workflows in private repositories that use GitHub-hosted runners. Minutes used is listed for each GitHub-hosted runner operating system. Any job re-runs are also included in the usage. The usage returned includes any minute multipliers for macOS and Windows runners, and is rounded up to the nearest whole minute. For more information, see \"Managing billing for GitHub Actions\".

\n

OAuth app tokens and personal access tokens (classic) need the repo or admin:org scope to use this endpoint.

" }, { "serverUrl": "https://api.github.com", @@ -108907,13 +108907,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets the estimated paid and estimated total storage used for GitHub Actions and GitHub Packages.

\n

Paid minutes only apply to packages stored for private repositories. For more information, see \"Managing billing for GitHub Packages.\"

\n

OAuth app tokens and personal access tokens (classic) need the repo or admin:org scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets the estimated paid and estimated total storage used for GitHub Actions and GitHub Packages.

\n

Paid minutes only apply to packages stored for private repositories. For more information, see \"Managing billing for GitHub Packages.\"

\n

OAuth app tokens and personal access tokens (classic) need the repo or admin:org scope to use this endpoint.

" }, { "serverUrl": "https://api.github.com", @@ -208776,7 +208776,6 @@ } ], "previews": [], - "descriptionHTML": "

Deletes an organization development environment secret using the secret name.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "204", @@ -208786,7 +208785,8 @@ "httpStatusCode": "404", "description": "

Resource not found

" } - ] + ], + "descriptionHTML": "

Deletes an organization development environment secret using the secret name.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

" }, { "serverUrl": "https://api.github.com", @@ -216777,13 +216777,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets your public key, which you need to encrypt secrets. You need to\nencrypt a secret before you can create or update secrets.

\n

If the repository is private, OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets your public key, which you need to encrypt secrets. You need to\nencrypt a secret before you can create or update secrets.

\n

If the repository is private, OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" }, { "serverUrl": "https://api.github.com", @@ -217409,13 +217409,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets a development environment secret available to a user's codespaces without revealing its encrypted value.

\n

The authenticated user must have Codespaces access to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the codespace or codespace:secrets scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets a development environment secret available to a user's codespaces without revealing its encrypted value.

\n

The authenticated user must have Codespaces access to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the codespace or codespace:secrets scope to use this endpoint.

" }, { "serverUrl": "https://api.github.com", @@ -217590,13 +217590,13 @@ } ], "previews": [], - "descriptionHTML": "

Deletes a development environment secret from a user's codespaces using the secret name. Deleting the secret will remove access from all codespaces that were allowed to access the secret.

\n

The authenticated user must have Codespaces access to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the codespace or codespace:secrets scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

Deletes a development environment secret from a user's codespaces using the secret name. Deleting the secret will remove access from all codespaces that were allowed to access the secret.

\n

The authenticated user must have Codespaces access to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the codespace or codespace:secrets scope to use this endpoint.

" }, { "serverUrl": "https://api.github.com", @@ -255927,13 +255927,13 @@ } ], "previews": [], - "descriptionHTML": "

Simple filtering of deployments is available via query parameters:

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Simple filtering of deployments is available via query parameters:

" }, { "serverUrl": "https://api.github.com", @@ -258329,13 +258329,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets a deployment branch or tag policy for an environment.

\n

Anyone with read access to the repository can use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint with a private repository.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets a deployment branch or tag policy for an environment.

\n

Anyone with read access to the repository can use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint with a private repository.

" }, { "serverUrl": "https://api.github.com", @@ -340673,13 +340673,13 @@ } ], "previews": [], - "descriptionHTML": "

Lists labels for issues in a milestone.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Lists labels for issues in a milestone.

" } ], "milestones": [ @@ -341161,7 +341161,6 @@ } ], "previews": [], - "descriptionHTML": "

Lists milestones for a repository.

", "statusCodes": [ { "httpStatusCode": "200", @@ -341171,7 +341170,8 @@ "httpStatusCode": "404", "description": "

Resource not found

" } - ] + ], + "descriptionHTML": "

Lists milestones for a repository.

" }, { "serverUrl": "https://api.github.com", @@ -382834,7 +382834,6 @@ } ], "previews": [], - "descriptionHTML": "

List files larger than 100MB found during the import

\n

Warning

\n

\nEndpoint closing down notice: Due to very low levels of usage and available alternatives, this endpoint is closing down and will no longer be available from 00:00 UTC on April 12, 2024. For more details and alternatives, see the changelog.

\n
", "statusCodes": [ { "httpStatusCode": "200", @@ -382844,7 +382843,8 @@ "httpStatusCode": "503", "description": "

Unavailable due to service under maintenance.

" } - ] + ], + "descriptionHTML": "

List files larger than 100MB found during the import

\n

Warning

\n

\nEndpoint closing down notice: Due to very low levels of usage and available alternatives, this endpoint is closing down and will no longer be available from 00:00 UTC on April 12, 2024. For more details and alternatives, see the changelog.

\n
" }, { "serverUrl": "https://api.github.com", @@ -402332,13 +402332,13 @@ } ], "previews": [], - "descriptionHTML": "

Creates a hosted compute network configuration for an organization.

\n

OAuth app tokens and personal access tokens (classic) need the write:network_configurations scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "201", "description": "

Created

" } - ] + ], + "descriptionHTML": "

Creates a hosted compute network configuration for an organization.

\n

OAuth app tokens and personal access tokens (classic) need the write:network_configurations scope to use this endpoint.

" }, { "serverUrl": "https://api.github.com", @@ -428990,13 +428990,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets a specific package version for a package owned by the authenticated user.

\n

OAuth app tokens and personal access tokens (classic) need the read:packages scope to use this endpoint. For more information, see \"About permissions for GitHub Packages.\"

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets a specific package version for a package owned by the authenticated user.

\n

OAuth app tokens and personal access tokens (classic) need the read:packages scope to use this endpoint. For more information, see \"About permissions for GitHub Packages.\"

" }, { "serverUrl": "https://api.github.com", @@ -433705,13 +433705,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets a specific package version for a public package owned by a specified user.

\n

OAuth app tokens and personal access tokens (classic) need the read:packages scope to use this endpoint. For more information, see \"About permissions for GitHub Packages.\"

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets a specific package version for a public package owned by a specified user.

\n

OAuth app tokens and personal access tokens (classic) need the read:packages scope to use this endpoint. For more information, see \"About permissions for GitHub Packages.\"

" }, { "serverUrl": "https://api.github.com", @@ -466100,13 +466100,13 @@ } ], "previews": [], - "descriptionHTML": "

Lists review comments for all pull requests in a repository. By default,\nreview comments are in ascending order by ID.

\n

This endpoint supports the following custom media types. For more information, see \"Media types.\"

\n
    \n
  • application/vnd.github-commitcomment.raw+json: Returns the raw markdown body. Response will include body. This is the default if you do not pass any specific media type.
  • \n
  • application/vnd.github-commitcomment.text+json: Returns a text only representation of the markdown body. Response will include body_text.
  • \n
  • application/vnd.github-commitcomment.html+json: Returns HTML rendered from the body's markdown. Response will include body_html.
  • \n
  • application/vnd.github-commitcomment.full+json: Returns raw, text, and HTML representations. Response will include body, body_text, and body_html.
  • \n
", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Lists review comments for all pull requests in a repository. By default,\nreview comments are in ascending order by ID.

\n

This endpoint supports the following custom media types. For more information, see \"Media types.\"

\n
    \n
  • application/vnd.github-commitcomment.raw+json: Returns the raw markdown body. Response will include body. This is the default if you do not pass any specific media type.
  • \n
  • application/vnd.github-commitcomment.text+json: Returns a text only representation of the markdown body. Response will include body_text.
  • \n
  • application/vnd.github-commitcomment.html+json: Returns HTML rendered from the body's markdown. Response will include body_html.
  • \n
  • application/vnd.github-commitcomment.full+json: Returns raw, text, and HTML representations. Response will include body, body_text, and body_html.
  • \n
" }, { "serverUrl": "https://api.github.com", @@ -486075,13 +486075,13 @@ } ], "previews": [], - "descriptionHTML": "

Note

\n

\nYou can also specify a team or organization with team_id and org_id using the route DELETE /organizations/:org_id/team/:team_id/discussions/:discussion_number/reactions/:reaction_id.

\n
\n

Delete a reaction to a team discussion.

\n

OAuth app tokens and personal access tokens (classic) need the write:discussion scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

Note

\n

\nYou can also specify a team or organization with team_id and org_id using the route DELETE /organizations/:org_id/team/:team_id/discussions/:discussion_number/reactions/:reaction_id.

\n
\n

Delete a reaction to a team discussion.

\n

OAuth app tokens and personal access tokens (classic) need the write:discussion scope to use this endpoint.

" }, { "serverUrl": "https://api.github.com", @@ -496102,7 +496102,6 @@ } ], "previews": [], - "descriptionHTML": "

Get a published release with the specified tag.

", "statusCodes": [ { "httpStatusCode": "200", @@ -496112,7 +496111,8 @@ "httpStatusCode": "404", "description": "

Resource not found

" } - ] + ], + "descriptionHTML": "

Get a published release with the specified tag.

" }, { "serverUrl": "https://api.github.com", @@ -523778,13 +523778,13 @@ } ], "previews": [], - "descriptionHTML": "

Creates a new repository using a repository template. Use the template_owner and template_repo route parameters to specify the repository to use as the template. If the repository is not public, the authenticated user must own or be a member of an organization that owns the repository. To check if a repository is available to use as a template, get the repository's information using the Get a repository endpoint and check that the is_template key is true.

\n

OAuth app tokens and personal access tokens (classic) need the public_repo or repo scope to create a public repository, and repo scope to create a private repository.

", "statusCodes": [ { "httpStatusCode": "201", "description": "

Created

" } - ] + ], + "descriptionHTML": "

Creates a new repository using a repository template. Use the template_owner and template_repo route parameters to specify the repository to use as the template. If the repository is not public, the authenticated user must own or be a member of an organization that owns the repository. To check if a repository is available to use as a template, get the repository's information using the Get a repository endpoint and check that the is_template key is true.

\n

OAuth app tokens and personal access tokens (classic) need the public_repo or repo scope to create a public repository, and repo scope to create a private repository.

" }, { "serverUrl": "https://api.github.com", @@ -608622,13 +608622,13 @@ } ], "previews": [], - "descriptionHTML": "

List all comments on a team discussion.

\n

Note

\n

\nYou can also specify a team by org_id and team_id using the route GET /organizations/{org_id}/team/{team_id}/discussions/{discussion_number}/comments.

\n
\n

OAuth app tokens and personal access tokens (classic) need the read:discussion scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

List all comments on a team discussion.

\n

Note

\n

\nYou can also specify a team by org_id and team_id using the route GET /organizations/{org_id}/team/{team_id}/discussions/{discussion_number}/comments.

\n
\n

OAuth app tokens and personal access tokens (classic) need the read:discussion scope to use this endpoint.

" }, { "serverUrl": "https://api.github.com", @@ -625652,13 +625652,13 @@ } ], "previews": [], - "descriptionHTML": "

Lists the people who the specified user follows.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Lists the people who the specified user follows.

" }, { "serverUrl": "https://api.github.com", diff --git a/src/rest/data/ghes-3.15-2022-11-28/schema.json b/src/rest/data/ghes-3.15-2022-11-28/schema.json index 7e1c80c77185..b3f888f9f87a 100644 --- a/src/rest/data/ghes-3.15-2022-11-28/schema.json +++ b/src/rest/data/ghes-3.15-2022-11-28/schema.json @@ -1000,13 +1000,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets the total GitHub Actions cache usage for an enterprise.\nThe data fetched using this API is refreshed approximately every 5 minutes, so values returned from this endpoint may take at least 5 minutes to get updated.

\n

OAuth tokens and personal access tokens (classic) need the admin:enterprise scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets the total GitHub Actions cache usage for an enterprise.\nThe data fetched using this API is refreshed approximately every 5 minutes, so values returned from this endpoint may take at least 5 minutes to get updated.

\n

OAuth tokens and personal access tokens (classic) need the admin:enterprise scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -1228,13 +1228,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets the total GitHub Actions cache usage for an organization.\nThe data fetched using this API is refreshed approximately every 5 minutes, so values returned from this endpoint may take at least 5 minutes to get updated.

\n

OAuth tokens and personal access tokens (classic) need the read:org scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets the total GitHub Actions cache usage for an organization.\nThe data fetched using this API is refreshed approximately every 5 minutes, so values returned from this endpoint may take at least 5 minutes to get updated.

\n

OAuth tokens and personal access tokens (classic) need the read:org scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -1468,13 +1468,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets GitHub Actions cache usage for a repository.\nThe data fetched using this API is refreshed approximately every 5 minutes, so values returned from this endpoint may take at least 5 minutes to get updated.

\n

Anyone with read access to the repository can use this endpoint.

\n

If the repository is private, OAuth tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets GitHub Actions cache usage for a repository.\nThe data fetched using this API is refreshed approximately every 5 minutes, so values returned from this endpoint may take at least 5 minutes to get updated.

\n

Anyone with read access to the repository can use this endpoint.

\n

If the repository is private, OAuth tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -2634,13 +2634,13 @@ } ], "previews": [], - "descriptionHTML": "

Sets the GitHub Actions permissions policy for organizations and allowed actions in an enterprise.

\n

OAuth app tokens and personal access tokens (classic) need the admin:enterprise scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

Sets the GitHub Actions permissions policy for organizations and allowed actions in an enterprise.

\n

OAuth app tokens and personal access tokens (classic) need the admin:enterprise scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -2961,13 +2961,13 @@ } ], "previews": [], - "descriptionHTML": "

Adds an organization to the list of selected organizations that are enabled for GitHub Actions in an enterprise. To use this endpoint, the enterprise permission policy for enabled_organizations must be configured to selected. For more information, see \"Set GitHub Actions permissions for an enterprise.\"

\n

OAuth app tokens and personal access tokens (classic) need the admin:enterprise scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

Adds an organization to the list of selected organizations that are enabled for GitHub Actions in an enterprise. To use this endpoint, the enterprise permission policy for enabled_organizations must be configured to selected. For more information, see \"Set GitHub Actions permissions for an enterprise.\"

\n

OAuth app tokens and personal access tokens (classic) need the admin:enterprise scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -3254,13 +3254,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets the default workflow permissions granted to the GITHUB_TOKEN when running workflows in an enterprise,\nas well as whether GitHub Actions can submit approving pull request reviews. For more information, see\n\"Enforcing a policy for workflow permissions in your enterprise.\"

\n

OAuth tokens and personal access tokens (classic) need the admin:enterprise scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

Success response

" } - ] + ], + "descriptionHTML": "

Gets the default workflow permissions granted to the GITHUB_TOKEN when running workflows in an enterprise,\nas well as whether GitHub Actions can submit approving pull request reviews. For more information, see\n\"Enforcing a policy for workflow permissions in your enterprise.\"

\n

OAuth tokens and personal access tokens (classic) need the admin:enterprise scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -5461,13 +5461,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets the level of access that workflows outside of the repository have to actions and reusable workflows in the repository.\nThis endpoint only applies to internal and private repositories.\nFor more information, see \"Allowing access to components in a private repository\" and\n\"Allowing access to components in an internal repository.\"

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets the level of access that workflows outside of the repository have to actions and reusable workflows in the repository.\nThis endpoint only applies to internal and private repositories.\nFor more information, see \"Allowing access to components in a private repository\" and\n\"Allowing access to components in an internal repository.\"

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -5904,7 +5904,6 @@ } ], "previews": [], - "descriptionHTML": "

Sets the default workflow permissions granted to the GITHUB_TOKEN when running workflows in a repository, and sets if GitHub Actions\ncan submit approving pull request reviews.\nFor more information, see \"Setting the permissions of the GITHUB_TOKEN for your repository.\"

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "204", @@ -5914,7 +5913,8 @@ "httpStatusCode": "409", "description": "

Conflict response when changing a setting is prevented by the owning organization or enterprise

" } - ] + ], + "descriptionHTML": "

Sets the default workflow permissions granted to the GITHUB_TOKEN when running workflows in a repository, and sets if GitHub Actions\ncan submit approving pull request reviews.\nFor more information, see \"Setting the permissions of the GITHUB_TOKEN for your repository.\"

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" } ], "secrets": [ @@ -6290,13 +6290,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets a single organization secret without revealing its encrypted value.

\n

The authenticated user must have collaborator access to a repository to create, update, or read secrets

\n

OAuth tokens and personal access tokens (classic) need theadmin:org scope to use this endpoint. If the repository is private, OAuth tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets a single organization secret without revealing its encrypted value.

\n

The authenticated user must have collaborator access to a repository to create, update, or read secrets

\n

OAuth tokens and personal access tokens (classic) need theadmin:org scope to use this endpoint. If the repository is private, OAuth tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -8588,13 +8588,13 @@ } ], "previews": [], - "descriptionHTML": "

Lists all secrets available in an environment without revealing their\nencrypted values.

\n

Authenticated users must have collaborator access to a repository to create, update, or read secrets.

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Lists all secrets available in an environment without revealing their\nencrypted values.

\n

Authenticated users must have collaborator access to a repository to create, update, or read secrets.

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -8833,13 +8833,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets a single environment secret without revealing its encrypted value.

\n

Authenticated users must have collaborator access to a repository to create, update, or read secrets.

\n

OAuth tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets a single environment secret without revealing its encrypted value.

\n

Authenticated users must have collaborator access to a repository to create, update, or read secrets.

\n

OAuth tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -10222,13 +10222,13 @@ } ], "previews": [], - "descriptionHTML": "

Removes an organization from the list of selected organizations that can access a self-hosted runner group. The runner group must have visibility set to selected. For more information, see \"Create a self-hosted runner group for an enterprise.\"

\n

OAuth app tokens and personal access tokens (classic) need the manage_runners:enterprise scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

Removes an organization from the list of selected organizations that can access a self-hosted runner group. The runner group must have visibility set to selected. For more information, see \"Create a self-hosted runner group for an enterprise.\"

\n

OAuth app tokens and personal access tokens (classic) need the manage_runners:enterprise scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -10882,13 +10882,13 @@ } ], "previews": [], - "descriptionHTML": "

Lists all self-hosted runner groups configured in an organization and inherited from an enterprise.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Lists all self-hosted runner groups configured in an organization and inherited from an enterprise.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -13704,13 +13704,13 @@ } ], "previews": [], - "descriptionHTML": "

Lists binaries for the runner application that you can download and run.

\n

OAuth app tokens and personal access tokens (classic) need the manage_runners:enterprise scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Lists binaries for the runner application that you can download and run.

\n

OAuth app tokens and personal access tokens (classic) need the manage_runners:enterprise scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -24825,13 +24825,13 @@ } ], "previews": [], - "descriptionHTML": "

Creates an organization variable that you can reference in a GitHub Actions workflow.

\n

Authenticated users must have collaborator access to a repository to create, update, or read variables.

\n

OAuth tokens and personal access tokens (classic) need theadmin:org scope to use this endpoint. If the repository is private, OAuth tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "201", "description": "

Response when creating a variable

" } - ] + ], + "descriptionHTML": "

Creates an organization variable that you can reference in a GitHub Actions workflow.

\n

Authenticated users must have collaborator access to a repository to create, update, or read variables.

\n

OAuth tokens and personal access tokens (classic) need theadmin:org scope to use this endpoint. If the repository is private, OAuth tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -27651,13 +27651,13 @@ } ], "previews": [], - "descriptionHTML": "

Deletes an environment variable using the variable name.

\n

Authenticated users must have collaborator access to a repository to create, update, or read variables.

\n

OAuth tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

Deletes an environment variable using the variable name.

\n

Authenticated users must have collaborator access to a repository to create, update, or read variables.

\n

OAuth tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" } ], "workflow-jobs": [ @@ -35113,13 +35113,13 @@ } ], "previews": [], - "descriptionHTML": "

Deletes a specific workflow run.

\n

Anyone with write access to the repository can use this endpoint.

\n

If the repository is private, OAuth tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

Deletes a specific workflow run.

\n

Anyone with write access to the repository can use this endpoint.

\n

If the repository is private, OAuth tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -59256,13 +59256,13 @@ } ], "previews": [], - "descriptionHTML": "

Note

\n

\nThis API is not built to serve real-time use cases. Depending on the time of day, event latency can be anywhere from 30s to 6h.

\n
", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Note

\n

\nThis API is not built to serve real-time use cases. Depending on the time of day, event latency can be anywhere from 30s to 6h.

\n
" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -103605,13 +103605,13 @@ } ], "previews": [], - "descriptionHTML": "

Revokes the installation token you're using to authenticate as an installation and access this endpoint.

\n

Once an installation token is revoked, the token is invalidated and cannot be used. Other endpoints that require the revoked installation token must have a new installation token to work. You can create a new token using the \"Create an installation access token for an app\" endpoint.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

Revokes the installation token you're using to authenticate as an installation and access this endpoint.

\n

Once an installation token is revoked, the token is invalidated and cannot be used. Other endpoints that require the revoked installation token must have a new installation token to work. You can create a new token using the \"Create an installation access token for an app\" endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -161963,13 +161963,13 @@ } ], "previews": [], - "descriptionHTML": "

Attach a code security configuration to a set of repositories. If the repositories specified are already attached to a configuration, they will be re-attached to the provided configuration.

\n

If insufficient GHAS licenses are available to attach the configuration to a repository, only free features will be enabled.

\n

The authenticated user must be an administrator or security manager for the organization to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the write:org scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "202", "description": "

Accepted

" } - ] + ], + "descriptionHTML": "

Attach a code security configuration to a set of repositories. If the repositories specified are already attached to a configuration, they will be re-attached to the provided configuration.

\n

If insufficient GHAS licenses are available to attach the configuration to a repository, only free features will be enabled.

\n

The authenticated user must be an administrator or security manager for the organization to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the write:org scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -165923,13 +165923,13 @@ } ], "previews": [], - "descriptionHTML": "

When authenticating as a user with admin rights to a repository, this endpoint will list all currently open repository invitations.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

When authenticating as a user with admin rights to a repository, this endpoint will list all currently open repository invitations.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -167375,13 +167375,13 @@ } ], "previews": [], - "descriptionHTML": "", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -189863,13 +189863,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets a single organization secret without revealing its encrypted value.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets a single organization secret without revealing its encrypted value.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -191144,13 +191144,13 @@ } ], "previews": [], - "descriptionHTML": "

Replaces all repositories for an organization secret when the visibility\nfor repository access is set to selected. The visibility is set when you Create\nor update an organization secret.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

Replaces all repositories for an organization secret when the visibility\nfor repository access is set to selected. The visibility is set when you Create\nor update an organization secret.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -191643,13 +191643,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets a single repository secret without revealing its encrypted value.

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets a single repository secret without revealing its encrypted value.

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -192423,13 +192423,13 @@ } ], "previews": [], - "descriptionHTML": "

Create a new snapshot of a repository's dependencies.

\n

The authenticated user must have access to the repository.

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "201", "description": "

Created

" } - ] + ], + "descriptionHTML": "

Create a new snapshot of a repository's dependencies.

\n

The authenticated user must have access to the repository.

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" } ], "sboms": [ @@ -193213,7 +193213,6 @@ } ], "previews": [], - "descriptionHTML": "", "statusCodes": [ { "httpStatusCode": "200", @@ -193223,7 +193222,8 @@ "httpStatusCode": "404", "description": "

Resource not found

" } - ] + ], + "descriptionHTML": "" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -196589,13 +196589,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets a deployment branch or tag policy for an environment.

\n

Anyone with read access to the repository can use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint with a private repository.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets a deployment branch or tag policy for an environment.

\n

Anyone with read access to the repository can use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint with a private repository.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -199626,13 +199626,13 @@ } ], "previews": [], - "descriptionHTML": "

Enable a custom deployment protection rule for an environment.

\n

The authenticated user must have admin or owner permissions to the repository to use this endpoint.

\n

For more information about the app that is providing this custom deployment rule, see the documentation for the GET /apps/{app_slug} endpoint, as well as the guide to creating custom deployment protection rules.

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "201", "description": "

The enabled custom deployment protection rule

" } - ] + ], + "descriptionHTML": "

Enable a custom deployment protection rule for an environment.

\n

The authenticated user must have admin or owner permissions to the repository to use this endpoint.

\n

For more information about the app that is providing this custom deployment rule, see the documentation for the GET /apps/{app_slug} endpoint, as well as the guide to creating custom deployment protection rules.

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -211057,13 +211057,13 @@ } ], "previews": [], - "descriptionHTML": "

List all pre-receive hooks that are enabled or testing for this organization as well as any disabled hooks that can be configured at the organization level. Globally disabled pre-receive hooks that do not allow downstream configuration are not listed.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

List all pre-receive hooks that are enabled or testing for this organization as well as any disabled hooks that can be configured at the organization level. Globally disabled pre-receive hooks that do not allow downstream configuration are not listed.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -211351,13 +211351,13 @@ } ], "previews": [], - "descriptionHTML": "

Removes any overrides for this hook at the org level for this org.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Removes any overrides for this hook at the org level for this org.

" } ], "orgs": [ @@ -213492,13 +213492,13 @@ } ], "previews": [], - "descriptionHTML": "", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -213704,13 +213704,13 @@ } ], "previews": [], - "descriptionHTML": "

Deletes any overridden enforcement on this repository for the specified hook.

\n

Responds with effective values inherited from owner and/or global level.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

Responds with effective values inherited from owner and/or global level.

" } - ] + ], + "descriptionHTML": "

Deletes any overridden enforcement on this repository for the specified hook.

\n

Responds with effective values inherited from owner and/or global level.

" } ], "scim": [ @@ -222987,13 +222987,13 @@ } ], "previews": [], - "descriptionHTML": "

You can demote any user account except your own.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

You can demote any user account except your own.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -269183,13 +269183,13 @@ } ], "previews": [], - "descriptionHTML": "

Removes one or more assignees from an issue.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Removes one or more assignees from an issue.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -269271,7 +269271,6 @@ } ], "previews": [], - "descriptionHTML": "

Checks if a user has permission to be assigned to a specific issue.

\n

If the assignee can be assigned to this issue, a 204 status code with no content is returned.

\n

Otherwise a 404 status code is returned.

", "statusCodes": [ { "httpStatusCode": "204", @@ -269281,7 +269280,8 @@ "httpStatusCode": "404", "description": "

Response if assignee can not be assigned to issue_number

" } - ] + ], + "descriptionHTML": "

Checks if a user has permission to be assigned to a specific issue.

\n

If the assignee can be assigned to this issue, a 204 status code with no content is returned.

\n

Otherwise a 404 status code is returned.

" } ], "comments": [ @@ -298348,13 +298348,13 @@ } ], "previews": [], - "descriptionHTML": "", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -323441,7 +323441,6 @@ } ], "previews": [], - "descriptionHTML": "

List all the repositories for this organization migration.

", "statusCodes": [ { "httpStatusCode": "200", @@ -323451,7 +323450,8 @@ "httpStatusCode": "404", "description": "

Resource not found

" } - ] + ], + "descriptionHTML": "

List all the repositories for this organization migration.

" } ], "users": [ @@ -338563,13 +338563,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets the audit log for an organization. For more information, see \"Reviewing the audit log for your organization.\"

\n

By default, the response includes up to 30 events from the past three months. Use the phrase parameter to filter results and retrieve older events. For example, use the phrase parameter with the created qualifier to filter events based on when the events occurred. For more information, see \"Reviewing the audit log for your organization.\"

\n

Use pagination to retrieve fewer or more than 30 events. For more information, see \"Using pagination in the REST API.\"

\n

The authenticated user must be an organization owner to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the read:audit_log scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets the audit log for an organization. For more information, see \"Reviewing the audit log for your organization.\"

\n

By default, the response includes up to 30 events from the past three months. Use the phrase parameter to filter results and retrieve older events. For example, use the phrase parameter with the created qualifier to filter events based on when the events occurred. For more information, see \"Reviewing the audit log for your organization.\"

\n

Use pagination to retrieve fewer or more than 30 events. For more information, see \"Using pagination in the REST API.\"

\n

The authenticated user must be an organization owner to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the read:audit_log scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -345119,7 +345119,6 @@ } ], "previews": [], - "descriptionHTML": "

Check if the provided user is a public member of the organization.

", "statusCodes": [ { "httpStatusCode": "204", @@ -345129,7 +345128,8 @@ "httpStatusCode": "404", "description": "

Not Found if user is not a public member

" } - ] + ], + "descriptionHTML": "

Check if the provided user is a public member of the organization.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -347607,13 +347607,13 @@ } ], "previews": [], - "descriptionHTML": "

Removes all assigned organization roles from a team. For more information on organization roles, see \"Using organization roles.\"

\n

The authenticated user must be an administrator for the organization to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

Removes all assigned organization roles from a team. For more information on organization roles, see \"Using organization roles.\"

\n

The authenticated user must be an administrator for the organization to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -347763,13 +347763,13 @@ } ], "previews": [], - "descriptionHTML": "

Removes an organization role from a team. For more information on organization roles, see \"Using organization roles.\"

\n

The authenticated user must be an administrator for the organization to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

Removes an organization role from a team. For more information on organization roles, see \"Using organization roles.\"

\n

The authenticated user must be an administrator for the organization to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -347983,13 +347983,13 @@ } ], "previews": [], - "descriptionHTML": "

Remove an organization role from a user. For more information on organization roles, see \"Using organization roles.\"

\n

The authenticated user must be an administrator for the organization to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

Remove an organization role from a user. For more information on organization roles, see \"Using organization roles.\"

\n

The authenticated user must be an administrator for the organization to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the admin:org scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -353963,7 +353963,6 @@ } ], "previews": [], - "descriptionHTML": "

Gets information about a suite of rule evaluations from within an organization.\nFor more information, see \"Managing rulesets for repositories in your organization.\"

", "statusCodes": [ { "httpStatusCode": "200", @@ -353977,7 +353976,8 @@ "httpStatusCode": "500", "description": "

Internal Error

" } - ] + ], + "descriptionHTML": "

Gets information about a suite of rule evaluations from within an organization.\nFor more information, see \"Managing rulesets for repositories in your organization.\"

" } ], "rules": [ @@ -361373,13 +361373,13 @@ } ], "previews": [], - "descriptionHTML": "

Adds a team as a security manager for an organization. For more information, see \"Managing security for an organization for an organization.\"

\n

The authenticated user must be an administrator for the organization to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the write:org scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

Adds a team as a security manager for an organization. For more information, see \"Managing security for an organization for an organization.\"

\n

The authenticated user must be an administrator for the organization to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need the write:org scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -362721,13 +362721,13 @@ } ], "previews": [], - "descriptionHTML": "

Updates the webhook configuration for an organization. To update more information about the webhook, including the active state and events, use \"Update an organization webhook .\"

\n

You must be an organization owner to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need admin:org_hook scope. OAuth apps cannot list, view, or edit\nwebhooks that they did not create and users cannot list, view, or edit webhooks that were created by OAuth apps.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Updates the webhook configuration for an organization. To update more information about the webhook, including the active state and events, use \"Update an organization webhook .\"

\n

You must be an organization owner to use this endpoint.

\n

OAuth app tokens and personal access tokens (classic) need admin:org_hook scope. OAuth apps cannot list, view, or edit\nwebhooks that they did not create and users cannot list, view, or edit webhooks that were created by OAuth apps.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -367826,13 +367826,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets a specific package version in an organization.

\n

OAuth app tokens and personal access tokens (classic) need the read:packages scope to use this endpoint. For more information, see \"About permissions for GitHub Packages.\"

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets a specific package version in an organization.

\n

OAuth app tokens and personal access tokens (classic) need the read:packages scope to use this endpoint. For more information, see \"About permissions for GitHub Packages.\"

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -378817,13 +378817,13 @@ } ], "previews": [], - "descriptionHTML": "

Gets information about the single most recent build of a GitHub Enterprise Server Pages site.

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Gets information about the single most recent build of a GitHub Enterprise Server Pages site.

\n

OAuth app tokens and personal access tokens (classic) need the repo scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -383846,7 +383846,6 @@ } ], "previews": [], - "descriptionHTML": "

Warning

\n

\nClosing down notice: Projects (classic) is being deprecated in favor of the new Projects experience.\nSee the changelog for more information.

\n
", "statusCodes": [ { "httpStatusCode": "204", @@ -383868,7 +383867,8 @@ "httpStatusCode": "404", "description": "

Resource not found

" } - ] + ], + "descriptionHTML": "

Warning

\n

\nClosing down notice: Projects (classic) is being deprecated in favor of the new Projects experience.\nSee the changelog for more information.

\n
" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -407137,7 +407137,6 @@ } ], "previews": [], - "descriptionHTML": "

Checks if a pull request has been merged into the base branch. The HTTP status of the response indicates whether or not the pull request has been merged; the response body is empty.

", "statusCodes": [ { "httpStatusCode": "204", @@ -407147,7 +407146,8 @@ "httpStatusCode": "404", "description": "

Not Found if pull request has not been merged

" } - ] + ], + "descriptionHTML": "

Checks if a pull request has been merged into the base branch. The HTTP status of the response indicates whether or not the pull request has been merged; the response body is empty.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -424542,7 +424542,6 @@ } ], "previews": [], - "descriptionHTML": "

Lists comments for a specific pull request review.

\n

This endpoint supports the following custom media types. For more information, see \"Media types.\"

\n
    \n
  • application/vnd.github-commitcomment.raw+json: Returns the raw markdown body. Response will include body. This is the default if you do not pass any specific media type.
  • \n
  • application/vnd.github-commitcomment.text+json: Returns a text only representation of the markdown body. Response will include body_text.
  • \n
  • application/vnd.github-commitcomment.html+json: Returns HTML rendered from the body's markdown. Response will include body_html.
  • \n
  • application/vnd.github-commitcomment.full+json: Returns raw, text, and HTML representations. Response will include body, body_text, and body_html.
  • \n
", "statusCodes": [ { "httpStatusCode": "200", @@ -424552,7 +424551,8 @@ "httpStatusCode": "404", "description": "

Resource not found

" } - ] + ], + "descriptionHTML": "

Lists comments for a specific pull request review.

\n

This endpoint supports the following custom media types. For more information, see \"Media types.\"

\n
    \n
  • application/vnd.github-commitcomment.raw+json: Returns the raw markdown body. Response will include body. This is the default if you do not pass any specific media type.
  • \n
  • application/vnd.github-commitcomment.text+json: Returns a text only representation of the markdown body. Response will include body_text.
  • \n
  • application/vnd.github-commitcomment.html+json: Returns HTML rendered from the body's markdown. Response will include body_html.
  • \n
  • application/vnd.github-commitcomment.full+json: Returns raw, text, and HTML representations. Response will include body, body_text, and body_html.
  • \n
" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -427963,7 +427963,6 @@ } ], "previews": [], - "descriptionHTML": "

Create a reaction to a team discussion.

\n

A response with an HTTP 200 status means that you already added the reaction type to this team discussion.

\n

Note

\n

\nYou can also specify a team by org_id and team_id using the route POST /organizations/:org_id/team/:team_id/discussions/:discussion_number/reactions.

\n
\n

OAuth app tokens and personal access tokens (classic) need the write:discussion scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", @@ -427973,7 +427972,8 @@ "httpStatusCode": "201", "description": "

Created

" } - ] + ], + "descriptionHTML": "

Create a reaction to a team discussion.

\n

A response with an HTTP 200 status means that you already added the reaction type to this team discussion.

\n

Note

\n

\nYou can also specify a team by org_id and team_id using the route POST /organizations/:org_id/team/:team_id/discussions/:discussion_number/reactions.

\n
\n

OAuth app tokens and personal access tokens (classic) need the write:discussion scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -431686,7 +431686,6 @@ } ], "previews": [], - "descriptionHTML": "

List the reactions to a pull request review comment.

", "statusCodes": [ { "httpStatusCode": "200", @@ -431696,7 +431695,8 @@ "httpStatusCode": "404", "description": "

Resource not found

" } - ] + ], + "descriptionHTML": "

List the reactions to a pull request review comment.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -432767,7 +432767,6 @@ } ], "previews": [], - "descriptionHTML": "

List the reactions to a release.

", "statusCodes": [ { "httpStatusCode": "200", @@ -432777,7 +432776,8 @@ "httpStatusCode": "404", "description": "

Resource not found

" } - ] + ], + "descriptionHTML": "

List the reactions to a release.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -433475,13 +433475,13 @@ } ], "previews": [], - "descriptionHTML": "

Note

\n

\nYou can also specify a repository by repository_id using the route DELETE delete /repositories/:repository_id/releases/:release_id/reactions/:reaction_id.

\n
\n

Delete a reaction to a release.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

Note

\n

\nYou can also specify a repository by repository_id using the route DELETE delete /repositories/:repository_id/releases/:release_id/reactions/:reaction_id.

\n
\n

Delete a reaction to a release.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -437298,13 +437298,13 @@ } ], "previews": [], - "descriptionHTML": "

View the latest published full release for the repository.

\n

The latest release is the most recent non-prerelease, non-draft release, sorted by the created_at attribute. The created_at attribute is the date of the commit used for the release, and not the date when the release was drafted or published.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

View the latest published full release for the repository.

\n

The latest release is the most recent non-prerelease, non-draft release, sorted by the created_at attribute. The created_at attribute is the date of the commit used for the release, and not the date when the release was drafted or published.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -494701,7 +494701,6 @@ } ], "previews": [], - "descriptionHTML": "

This will trigger a ping event to be sent to the hook.

", "statusCodes": [ { "httpStatusCode": "204", @@ -494711,7 +494710,8 @@ "httpStatusCode": "404", "description": "

Resource not found

" } - ] + ], + "descriptionHTML": "

This will trigger a ping event to be sent to the hook.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -516088,13 +516088,13 @@ } ], "previews": [], - "descriptionHTML": "

If the authenticated user is an organization owner or a team maintainer, they can remove any repositories from the team. To remove a repository from a team as an organization member, the authenticated user must have admin access to the repository and must be able to see the team. This does not delete the repository, it just removes it from the team.

\n

Note

\n

\nYou can also specify a team by org_id and team_id using the route DELETE /organizations/{org_id}/team/{team_id}/repos/{owner}/{repo}.

\n
", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

If the authenticated user is an organization owner or a team maintainer, they can remove any repositories from the team. To remove a repository from a team as an organization member, the authenticated user must have admin access to the repository and must be able to see the team. This does not delete the repository, it just removes it from the team.

\n

Note

\n

\nYou can also specify a team by org_id and team_id using the route DELETE /organizations/{org_id}/team/{team_id}/repos/{owner}/{repo}.

\n
" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -528123,13 +528123,13 @@ } ], "previews": [], - "descriptionHTML": "

Get a specific discussion on a team's page.

\n

Note

\n

\nYou can also specify a team by org_id and team_id using the route GET /organizations/{org_id}/team/{team_id}/discussions/{discussion_number}.

\n
\n

OAuth app tokens and personal access tokens (classic) need the read:discussion scope to use this endpoint.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Get a specific discussion on a team's page.

\n

Note

\n

\nYou can also specify a team by org_id and team_id using the route GET /organizations/{org_id}/team/{team_id}/discussions/{discussion_number}.

\n
\n

OAuth app tokens and personal access tokens (classic) need the read:discussion scope to use this endpoint.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -531039,13 +531039,13 @@ } ], "previews": [], - "descriptionHTML": "

Lists external groups available in an organization. You can query the groups using the display_name parameter, only groups with a group_name containing the text provided in the display_name parameter will be returned. You can also limit your page results using the per_page parameter. GitHub Enterprise Server generates a url-encoded page token using a cursor value for where the next page begins. For more information on cursor pagination, see \"Offset and Cursor Pagination explained.\"

\n

You can manage team membership with your identity provider using Enterprise Managed Users for GitHub Enterprise Cloud. For more information, see \"GitHub's products\" in the GitHub Help documentation.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Lists external groups available in an organization. You can query the groups using the display_name parameter, only groups with a group_name containing the text provided in the display_name parameter will be returned. You can also limit your page results using the per_page parameter. GitHub Enterprise Server generates a url-encoded page token using a cursor value for where the next page begins. For more information on cursor pagination, see \"Offset and Cursor Pagination explained.\"

\n

You can manage team membership with your identity provider using Enterprise Managed Users for GitHub Enterprise Cloud. For more information, see \"GitHub's products\" in the GitHub Help documentation.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -531172,13 +531172,13 @@ } ], "previews": [], - "descriptionHTML": "

Lists a connection between a team and an external group.

\n

You can manage team membership with your identity provider using Enterprise Managed Users for GitHub Enterprise Cloud. For more information, see \"GitHub's products\" in the GitHub Help documentation.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Lists a connection between a team and an external group.

\n

You can manage team membership with your identity provider using Enterprise Managed Users for GitHub Enterprise Cloud. For more information, see \"GitHub's products\" in the GitHub Help documentation.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -531406,13 +531406,13 @@ } ], "previews": [], - "descriptionHTML": "

Creates a connection between a team and an external group. Only one external group can be linked to a team.

\n

You can manage team membership with your identity provider using Enterprise Managed Users for GitHub Enterprise Cloud. For more information, see \"GitHub's products\" in the GitHub Help documentation.

", "statusCodes": [ { "httpStatusCode": "200", "description": "

OK

" } - ] + ], + "descriptionHTML": "

Creates a connection between a team and an external group. Only one external group can be linked to a team.

\n

You can manage team membership with your identity provider using Enterprise Managed Users for GitHub Enterprise Cloud. For more information, see \"GitHub's products\" in the GitHub Help documentation.

" }, { "serverUrl": "http(s)://HOSTNAME/api/v3", @@ -531470,13 +531470,13 @@ } ], "previews": [], - "descriptionHTML": "

Deletes a connection between a team and an external group.

\n

You can manage team membership with your IdP using Enterprise Managed Users for GitHub Enterprise Cloud. For more information, see GitHub's products in the GitHub Help documentation.

", "statusCodes": [ { "httpStatusCode": "204", "description": "

No Content

" } - ] + ], + "descriptionHTML": "

Deletes a connection between a team and an external group.

\n

You can manage team membership with your IdP using Enterprise Managed Users for GitHub Enterprise Cloud. For more information, see GitHub's products in the GitHub Help documentation.

" } ], "members": [