diff --git a/.prettierignore b/.prettierignore index 62700e93ea4..0f07251b065 100644 --- a/.prettierignore +++ b/.prettierignore @@ -36,3 +36,5 @@ kinds/report.json # Generated schema docs docs/sources/developers/kinds/ + +scripts/cli/bettererIssueTemplate.md diff --git a/package.json b/package.json index 0b666b4c385..de784f647b4 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,8 @@ "i18n:compile": "echo 'no i18n compile yet, all good'", "betterer": "betterer", "betterer:merge": "betterer merge", - "betterer:stats": "ts-node --transpile-only --project ./scripts/cli/tsconfig.json ./scripts/cli/reportBettererStats.ts" + "betterer:stats": "ts-node --transpile-only --project ./scripts/cli/tsconfig.json ./scripts/cli/reportBettererStats.ts", + "betterer:issues": "ts-node --transpile-only --project ./scripts/cli/tsconfig.json ./scripts/cli/generateBettererIssues.ts" }, "grafana": { "whatsNewUrl": "https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v9-2/", @@ -164,6 +165,7 @@ "@types/testing-library__jest-dom": "5.14.5", "@types/tinycolor2": "1.4.3", "@types/uuid": "8.3.4", + "@types/yargs": "17.0.12", "@typescript-eslint/eslint-plugin": "5.42.0", "@typescript-eslint/parser": "5.42.0", "autoprefixer": "10.4.13", @@ -173,6 +175,7 @@ "babel-plugin-macros": "3.1.0", "blob-polyfill": "7.0.20220408", "browserslist": "^4.21.4", + "codeowners": "^5.1.1", "copy-webpack-plugin": "9.0.1", "css-loader": "6.7.1", "css-minimizer-webpack-plugin": "4.2.2", @@ -244,7 +247,8 @@ "webpack-cli": "4.10.0", "webpack-dev-server": "4.11.1", "webpack-manifest-plugin": "5.0.0", - "webpack-merge": "5.8.0" + "webpack-merge": "5.8.0", + "yargs": "^17.5.1" }, "dependencies": { "@daybrush/utils": "1.10.0", diff --git a/scripts/cli/bettererIssueTemplate.md b/scripts/cli/bettererIssueTemplate.md new file mode 100644 index 00000000000..2c6a9b64f14 --- /dev/null +++ b/scripts/cli/bettererIssueTemplate.md @@ -0,0 +1,7 @@ +Hi <%= owner %>! + +The following files have been marked as having issues regarding `<%= issueFilter %>: <%= issueMessageFilter %>`. + +There are <%= totalIssueCount %> <%= plural('issue', totalIssueCount) %> over <%= fileCount %> <%= plural('file', fileCount) %>: +<% files.forEach((file) => { %> +- [ ] <%= file.issueCount %> <%= plural('issue', file.issueCount) %> in `<%= file.fileName %>` <% }) %> diff --git a/scripts/cli/generateBettererIssues.ts b/scripts/cli/generateBettererIssues.ts new file mode 100644 index 00000000000..39a1485cce9 --- /dev/null +++ b/scripts/cli/generateBettererIssues.ts @@ -0,0 +1,148 @@ +import { betterer, BettererFileIssues } from '@betterer/betterer'; +import Codeowners from 'codeowners'; +import { readFile, writeFile } from 'fs/promises'; +import { template } from 'lodash'; +import path from 'path'; +import { hideBin } from 'yargs/helpers'; +import yargs from 'yargs/yargs'; + +const argv = yargs(hideBin(process.argv)) + .option('template', { + demandOption: true, + alias: 't', + describe: 'Path to a template to use for each issue. See source bettererIssueTemplate.md for an example', + type: 'string', + default: './scripts/cli/bettererIssueTemplate.md', + }) + .option('output', { + demandOption: true, + alias: 'o', + describe: 'Path to directory to save issues to', + type: 'string', + }) + .option('test', { + demandOption: true, + alias: 'b', + describe: 'Name of the betterer test to produce the report for', + type: 'string', + }) + .option('test-message', { + alias: 'm', + describe: 'Filter issues containing this message', + type: 'string', + }) + .option('single-owner', { + type: 'boolean', + alias: 's', + describe: 'Only use first owner for files with multiple owners', + default: false, + }) + + .usage('Usage: yarn betterer:issues -t [path] -o [path] -b [string]') + .version(false) + .help('help').argv; + +interface FileDetails { + fileName: string; + issueCount: number; + issues: BettererFileIssues; +} + +// really dumb and simple pluralize function. not meant to be exhaustive +function plural(word: string, count: number) { + if (count === 0 || count > 1) { + return word + 's'; + } + + return word; +} + +async function main() { + const args = await argv; + const templatePath = path.resolve(args.template); + const outputPath = path.resolve(args.output); + const templateString = (await readFile(templatePath)).toString(); + + const owners = new Codeowners(); + const results = await betterer.results(); + + const filesByOwner: Record = {}; + + for (const testResults of results.resultSummaries) { + if (testResults.name !== args.test) { + continue; + } + + if (typeof testResults.details === 'string') { + continue; + } + + for (const _fileName in testResults.details) { + const fileName = _fileName.replace(process.cwd() + '/', ''); + const _details = testResults.details[_fileName]; + let ownersForFile = owners.getOwner(fileName); + + if (args.singleOwner) { + ownersForFile = [ownersForFile[0]]; + } + + const filterByMessage = args.testMessage?.length ? args.testMessage.toLowerCase() : undefined; + + const filteredDetails = filterByMessage + ? _details.filter((v) => v.message.toLowerCase().includes(filterByMessage)) + : _details; + + const numberOfIssues = filteredDetails.length; + + if (numberOfIssues === 0) { + continue; + } + + for (const owner of ownersForFile) { + if (!filesByOwner[owner]) { + filesByOwner[owner] = []; + } + + filesByOwner[owner].push({ + fileName, + issueCount: numberOfIssues, + issues: filteredDetails, + }); + } + } + } + + const contexts = Object.entries(filesByOwner).map(([owner, files]) => { + const fileCount = files.length; + const totalIssueCount = files.reduce((acc, v) => acc + v.issueCount, 0); + + return { + owner, + files, + fileCount, + totalIssueCount, + issueFilter: args.test, + issueMessageFilter: args.testMessage, + }; + }); + + const compiledTemplate = template(templateString, { imports: { plural } }); + + for (const context of contexts) { + const fileSafeOwner = context.owner.replace(/[^a-z0-9-]/gi, '_'); + const fileSafeTestName = args.test.replace(/[^a-z0-9-]/gi, '_'); + const outputFilePath = path.join(outputPath, `${fileSafeTestName}_${fileSafeOwner}.txt`); + const printed = compiledTemplate(context); + await writeFile(outputFilePath, printed); + + const indented = printed + .split('\n') + .map((v) => `\t${v}`) + .join('\n'); + + console.log(`Printed issue for owner`, context.owner, 'to', outputFilePath); + console.log(indented); + } +} + +main().catch(console.error); diff --git a/yarn.lock b/yarn.lock index 65143dc9c5e..46ac7a70454 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7082,7 +7082,7 @@ __metadata: languageName: node linkType: hard -"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": +"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.6, @nodelib/fs.walk@npm:^1.2.8": version: 1.2.8 resolution: "@nodelib/fs.walk@npm:1.2.8" dependencies: @@ -12111,6 +12111,15 @@ __metadata: languageName: node linkType: hard +"@types/yargs@npm:17.0.12": + version: 17.0.12 + resolution: "@types/yargs@npm:17.0.12" + dependencies: + "@types/yargs-parser": "*" + checksum: 5b41d21d8624199f89db82209b2adab2e47867b3677e852fde65698be2ca48364b14c2e70cb0adc9bca4a2102c93dad2409cae0ad666ea36ae031ae1cb08a7b5 + languageName: node + linkType: hard + "@types/yargs@npm:^15.0.0": version: 15.0.14 resolution: "@types/yargs@npm:15.0.14" @@ -15861,6 +15870,25 @@ __metadata: languageName: node linkType: hard +"codeowners@npm:^5.1.1": + version: 5.1.1 + resolution: "codeowners@npm:5.1.1" + dependencies: + "@nodelib/fs.walk": ^1.2.6 + commander: ^6.2.1 + find-up: ^2.1.0 + ignore: ^3.3.10 + is-directory: ^0.3.1 + lodash.intersection: ^4.4.0 + lodash.maxby: ^4.6.0 + lodash.padend: ^4.6.1 + true-case-path: ^1.0.3 + bin: + codeowners: index.js + checksum: 9ffd67403e9d0defc5b9906dd986734c2c2a02cad758ab95b722558a1817f47925dd2bac58327b860edd66806bf5cd72a24b1f377fe6215cf0576fee3bfbac48 + languageName: node + linkType: hard + "collapse-white-space@npm:^1.0.2": version: 1.0.6 resolution: "collapse-white-space@npm:1.0.6" @@ -21731,6 +21759,7 @@ __metadata: "@types/tinycolor2": 1.4.3 "@types/uuid": 8.3.4 "@types/webpack-env": 1.18.0 + "@types/yargs": 17.0.12 "@typescript-eslint/eslint-plugin": 5.42.0 "@typescript-eslint/parser": 5.42.0 "@visx/event": 2.17.0 @@ -21758,6 +21787,7 @@ __metadata: calculate-size: 1.1.1 centrifuge: 3.1.0 classnames: 2.3.2 + codeowners: ^5.1.1 comlink: 4.3.1 common-tags: 1.8.2 copy-webpack-plugin: 9.0.1 @@ -21929,6 +21959,7 @@ __metadata: webpack-merge: 5.8.0 whatwg-fetch: 3.6.2 xlsx: "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz" + yargs: ^17.5.1 languageName: unknown linkType: soft @@ -22887,6 +22918,13 @@ __metadata: languageName: node linkType: hard +"ignore@npm:^3.3.10": + version: 3.3.10 + resolution: "ignore@npm:3.3.10" + checksum: 23e8cc776e367b56615ab21b78decf973a35dfca5522b39d9b47643d8168473b0d1f18dd1321a1bab466a12ea11a2411903f3b21644f4d5461ee0711ec8678bd + languageName: node + linkType: hard + "ignore@npm:^4.0.3": version: 4.0.6 resolution: "ignore@npm:4.0.6" @@ -23514,6 +23552,13 @@ __metadata: languageName: node linkType: hard +"is-directory@npm:^0.3.1": + version: 0.3.1 + resolution: "is-directory@npm:0.3.1" + checksum: dce9a9d3981e38f2ded2a80848734824c50ee8680cd09aa477bef617949715cfc987197a2ca0176c58a9fb192a1a0d69b535c397140d241996a609d5906ae524 + languageName: node + linkType: hard + "is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": version: 2.2.1 resolution: "is-docker@npm:2.2.1" @@ -26395,6 +26440,13 @@ __metadata: languageName: node linkType: hard +"lodash.intersection@npm:^4.4.0": + version: 4.4.0 + resolution: "lodash.intersection@npm:4.4.0" + checksum: 98935dcba1bbb981c3927e3822f6f6f344736c881df4b622e4e40ca4a125490425449e23179f46294a1b4c351de4e9a7bb60207cc6ddd65ecfd45ef727d35123 + languageName: node + linkType: hard + "lodash.isequal@npm:^4.0.0": version: 4.5.0 resolution: "lodash.isequal@npm:4.5.0" @@ -26416,6 +26468,13 @@ __metadata: languageName: node linkType: hard +"lodash.maxby@npm:^4.6.0": + version: 4.6.0 + resolution: "lodash.maxby@npm:4.6.0" + checksum: 2f508383545bd9450e6509f1e5f3a3f737aac25a54225fe981b1a3c80faacc6d48d047695d799f5a7db80e8fc3c600e4736573cb2e6d0365c8f929bba5e5a1dd + languageName: node + linkType: hard + "lodash.memoize@npm:4.x, lodash.memoize@npm:^4.1.2": version: 4.1.2 resolution: "lodash.memoize@npm:4.1.2" @@ -26437,6 +26496,13 @@ __metadata: languageName: node linkType: hard +"lodash.padend@npm:^4.6.1": + version: 4.6.1 + resolution: "lodash.padend@npm:4.6.1" + checksum: c2e6e789debf83b98f5c085305cdcfff1067e7a31bda2a110fd765d3c11a99edfbeef570d9ef737ab3212006bdb8114e77622e518c18c1fce52b8fdfd9dab685 + languageName: node + linkType: hard + "lodash.truncate@npm:^4.4.2": version: 4.4.2 resolution: "lodash.truncate@npm:4.4.2" @@ -36890,6 +36956,15 @@ __metadata: languageName: node linkType: hard +"true-case-path@npm:^1.0.3": + version: 1.0.3 + resolution: "true-case-path@npm:1.0.3" + dependencies: + glob: ^7.1.2 + checksum: 2e2e3bf37b4b05db2e2a1d60329960a4aa697ad7a89bd97c66f5f4da83977897c29c704276e62bca62d055d8078065bc08a1c7a01f409de11c6592af8b442cbe + languageName: node + linkType: hard + "ts-dedent@npm:^2.0.0": version: 2.2.0 resolution: "ts-dedent@npm:2.2.0" @@ -39351,7 +39426,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.3.1, yargs@npm:^17.4.0": +"yargs@npm:^17.3.1, yargs@npm:^17.4.0, yargs@npm:^17.5.1": version: 17.5.1 resolution: "yargs@npm:17.5.1" dependencies: