feat(test): calculate coverage summaries in codeowner jest config

This commit is contained in:
Jesse David Peterson
2026-01-14 20:54:32 -04:00
parent 7ae2eed876
commit 87b0212844
2 changed files with 71 additions and 22 deletions
+26 -21
View File
@@ -3,8 +3,10 @@ const open = require('open').default;
const path = require('path');
const baseConfig = require('./jest.config.js');
const { createOwnerDirectory, createOwnerFilenameSlug } = require('./scripts/codeowners-manifest/utils.js');
const CODEOWNERS_MANIFEST_FILENAMES_BY_TEAM_PATH = 'codeowners-manifest/filenames-by-team.json';
const COVERAGE_SUMMARY_OUTPUT_PATH = './coverage-summary.json';
const codeownerName = process.env.CODEOWNER_NAME;
if (!codeownerName) {
@@ -100,6 +102,8 @@ module.exports = {
openCoverageReport(reportURL);
}
writeCoverageSummaryArtifact(coverageResults);
// TODO: Emit coverage metrics https://github.com/grafana/grafana/issues/111208
},
},
@@ -111,30 +115,31 @@ module.exports = {
testMatch: testFiles.map((file) => `<rootDir>/${file}`),
};
/**
* Create a filesystem-safe directory structure for different owner types
* @param {string} owner - CODEOWNERS owner (username, team, or email)
* @returns {string} Directory path relative to coverage/by-team/
*/
function createOwnerDirectory(owner) {
if (owner.includes('@') && owner.includes('/')) {
// Example: @grafana/dataviz-squad
const [org, team] = owner.substring(1).split('/');
return `teams/${org}/${team}`;
} else if (owner.startsWith('@')) {
// Example: @jesdavpet
return `users/${owner.substring(1)}`;
} else {
// Example: user@domain.tld
const [user, domain] = owner.split('@');
return `emails/${user}-at-${domain}`;
function writeCoverageSummaryArtifact(coverageResults) {
if (!coverageResults || !coverageResults.summary) {
return;
}
const summary = {
team: codeownerName,
commit: process.env.GITHUB_SHA || 'unknown',
timestamp: new Date().toISOString(),
summary: {
lines: { pct: coverageResults.summary.lines.pct },
statements: { pct: coverageResults.summary.statements.pct },
functions: { pct: coverageResults.summary.functions.pct },
branches: { pct: coverageResults.summary.branches.pct },
},
};
try {
fs.writeFileSync(COVERAGE_SUMMARY_OUTPUT_PATH, JSON.stringify(summary, null, 2));
console.log(`📊 Coverage summary written to ${COVERAGE_SUMMARY_OUTPUT_PATH}`);
} catch (err) {
console.error(`Failed to write coverage summary: ${err}`);
}
}
/**
* Open the given file URL in the default browser safely, without shell injection risk.
* @param {string} reportURL
*/
async function openCoverageReport(reportURL) {
try {
await open(reportURL);
+45 -1
View File
@@ -4,9 +4,29 @@ const { CODEOWNERS_JSON_PATH: CODEOWNERS_MANIFEST_CODEOWNERS_PATH } = require('.
let _codeownersCache = null;
/**
* Creates a filesystem-safe slug for different CODEOWNERS owner types
* @param {string} owner - CODEOWNERS owner (username, team, or email)
* @param {string} delimiter - Delimiter to use between parts (default: '/')
* @returns {string} Slugified owner string with type prefix to avoid collisions
*/
function createOwnerSlug(owner, delimiter = '/') {
if (owner.includes('@') && owner.includes('/')) {
const [org, team] = owner.substring(1).split('/');
return ['team', org, team].join(delimiter);
} else if (owner.startsWith('@')) {
return ['user', owner.substring(1)].join(delimiter);
} else {
const [user, domain] = owner.split('@');
const sanitizedUser = user.replace(/[+.]/g, delimiter);
const sanitizedDomain = domain.replace(/\./g, delimiter);
return ['email', `${sanitizedUser}-at-${sanitizedDomain}`].join(delimiter);
}
}
module.exports = {
/**
* import the contents of the codeowners manifest JSON file, with caching
* Imports the contents of the codeowners manifest JSON file, with caching
* @param {boolean} clearCache - if true, clear the cached data and reload the codeowners manifest
* @returns {Promise<Array<string>>} - list of codeowners which own at least one file in the project
*/
@@ -31,4 +51,28 @@ module.exports = {
return _codeownersCache;
},
/**
* Create a filesystem-safe directory structure for different owner types
* @param {string} owner - CODEOWNERS owner (username, team, or email)
* @returns {string} Directory path relative to coverage/by-team/
*
* @example
* createOwnerDirectory('@grafana/dataviz-squad') => 'teams/grafana/dataviz-squad'
*/
createOwnerDirectory(owner) {
return createOwnerSlug(owner, '/');
},
/**
* Create a filename-safe slug for artifacts and filenames
* @param {string} owner - CODEOWNERS owner (username, team, or email)
* @returns {string} Filename-safe slug
*
* @example
* createOwnerFilenameSlug('@grafana/dataviz-squad') => 'teams-grafana-dataviz-squad'
*/
createOwnerFilenameSlug(owner) {
return createOwnerSlug(owner, '-');
},
};