Chore: Remove betterer (#110469)
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
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 %>](https://github.com/grafana/grafana/blob/main/<%= file.fileName %>) <% }) %>
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to create GitHub issues from text files in a directory
|
||||
# Usage: ./create_issues_from_files.sh <directory> <owner/repo>
|
||||
|
||||
# FYI: Generated almost entirely with warp.dev as a quick hack for creating issues from betterer issues output
|
||||
|
||||
set -e
|
||||
|
||||
# Function to display usage
|
||||
usage() {
|
||||
echo "Usage: $0 <directory> <owner/repo>"
|
||||
echo ""
|
||||
echo "Arguments:"
|
||||
echo " directory Directory containing text files"
|
||||
echo " owner/repo GitHub repository in format 'owner/repository'"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 ./issues myuser/myrepo"
|
||||
echo " $0 /path/to/issues organization/project"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if correct number of arguments provided
|
||||
if [ $# -ne 2 ]; then
|
||||
echo "Error: Incorrect number of arguments"
|
||||
usage
|
||||
fi
|
||||
|
||||
DIRECTORY="$1"
|
||||
REPO="$2"
|
||||
|
||||
# Validate directory exists
|
||||
if [ ! -d "$DIRECTORY" ]; then
|
||||
echo "Error: Directory '$DIRECTORY' does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate repository format
|
||||
if [[ ! "$REPO" =~ ^[^/]+/[^/]+$ ]]; then
|
||||
echo "Error: Repository must be in format 'owner/repository'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Creating issues from text files in '$DIRECTORY' for repository '$REPO'"
|
||||
echo
|
||||
|
||||
# Counter for created issues
|
||||
created_count=0
|
||||
error_count=0
|
||||
|
||||
# Process each text file in the directory
|
||||
for file in "$DIRECTORY"/*.txt; do
|
||||
# Check if glob matched any files
|
||||
[ -e "$file" ] || continue
|
||||
|
||||
filename=$(basename "$file")
|
||||
|
||||
echo "Processing: $filename"
|
||||
|
||||
# Read file content
|
||||
if [ ! -r "$file" ]; then
|
||||
echo " ❌ Error: Cannot read file '$file'"
|
||||
((error_count++))
|
||||
continue
|
||||
fi
|
||||
|
||||
content=$(cat "$file")
|
||||
|
||||
# Skip empty files
|
||||
if [ -z "$content" ]; then
|
||||
echo " ⚠️ Skipping empty file"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Parse codeowner from first line (assuming format like "@username" or "@team/name")
|
||||
first_line=$(echo "$content" | head -n 1)
|
||||
codeowner=""
|
||||
|
||||
# Extract codeowner if first line contains @username or @team/name pattern
|
||||
if [[ "$first_line" =~ @[a-zA-Z0-9_/-]+ ]]; then
|
||||
codeowner=$(echo "$first_line" | grep -o '@[a-zA-Z0-9_/-]*' | head -n 1)
|
||||
# Strip @grafana/ prefix if present
|
||||
codeowner=${codeowner#@grafana/}
|
||||
fi
|
||||
|
||||
title="Betterer: React Hooks ($codeowner)"
|
||||
|
||||
# Create the issue using GitHub CLI
|
||||
if gh issue create \
|
||||
--repo "$REPO" \
|
||||
--title "$title" \
|
||||
--body "$content" > /dev/null 2>&1; then
|
||||
echo " ✅ Created issue: $title"
|
||||
((created_count++))
|
||||
else
|
||||
echo " ❌ Failed to create issue: $title"
|
||||
((error_count++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
echo "Summary:"
|
||||
echo " Issues created: $created_count"
|
||||
echo " Errors: $error_count"
|
||||
|
||||
if [ $created_count -eq 0 ] && [ $error_count -eq 0 ]; then
|
||||
echo " No .txt files found in '$DIRECTORY'"
|
||||
fi
|
||||
@@ -1,148 +0,0 @@
|
||||
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<string, FileDetails[]> = {};
|
||||
|
||||
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);
|
||||
@@ -1,51 +0,0 @@
|
||||
import { betterer } from '@betterer/betterer';
|
||||
import { camelCase } from 'lodash';
|
||||
|
||||
function logStat(name: string, value: number) {
|
||||
// Note that this output format must match the parsing in ci-frontend-metrics.sh
|
||||
// which expects the two values to be separated by a space
|
||||
console.log(`${name} ${value}`);
|
||||
}
|
||||
/**
|
||||
* Array of regexes + name overrides for legacy checks that have been moved to ESLint
|
||||
*
|
||||
* This is so we can still report things like "gfFormUsage..." as "noGfFormUsage_gfFormUsage..."
|
||||
* rather than "betterEslint_gfFormUsage..." for continuity on our dashboards
|
||||
*/
|
||||
const legacyChecksToTransform = [
|
||||
{ messageRegex: /gfFormUsage/i, prefix: 'noGfFormUsage' },
|
||||
{ messageRegex: /noUndocumentedStories/i, prefix: 'noUndocumentedStories' },
|
||||
{ messageRegex: /noSkippingOfA11YTests/i, prefix: 'noSkippingA11YTestsInStories' },
|
||||
];
|
||||
|
||||
async function main() {
|
||||
const results = await betterer.results();
|
||||
|
||||
for (const testResults of results.resultSummaries) {
|
||||
const countByMessage = {};
|
||||
const name = camelCase(testResults.name);
|
||||
Object.values(testResults.details)
|
||||
.flatMap((v) => v)
|
||||
.forEach((detail) => {
|
||||
const message = camelCase(detail.message);
|
||||
const nameToUse =
|
||||
legacyChecksToTransform.find((v) => {
|
||||
return v.messageRegex.test(message);
|
||||
})?.prefix || name;
|
||||
|
||||
const metricName = `${nameToUse}_${message}`;
|
||||
|
||||
if (metricName in countByMessage) {
|
||||
countByMessage[metricName]++;
|
||||
} else {
|
||||
countByMessage[metricName] = 1;
|
||||
}
|
||||
});
|
||||
|
||||
for (const [metricName, count] of Object.entries<number>(countByMessage)) {
|
||||
logStat(metricName, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
Reference in New Issue
Block a user