diff --git a/jest.config.codeowner.js b/jest.config.codeowner.js index 8e26692a8d4..d2e8e6434b4 100644 --- a/jest.config.codeowner.js +++ b/jest.config.codeowner.js @@ -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) => `/${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); diff --git a/scripts/codeowners-manifest/utils.js b/scripts/codeowners-manifest/utils.js index ac9d81763a5..cca3fdfe8bc 100644 --- a/scripts/codeowners-manifest/utils.js +++ b/scripts/codeowners-manifest/utils.js @@ -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>} - 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, '-'); + }, };