mirror of
https://github.com/esphome/esphome.git
synced 2025-09-02 19:32:19 +01:00
164 lines
6.7 KiB
YAML
164 lines
6.7 KiB
YAML
# This workflow automatically notifies codeowners when an issue is labeled with component labels.
|
|
# It reads the CODEOWNERS file to find the maintainers for the labeled components
|
|
# and posts a comment mentioning them to ensure they're aware of the issue.
|
|
|
|
name: Notify Issue Codeowners
|
|
|
|
on:
|
|
issues:
|
|
types: [labeled]
|
|
|
|
permissions:
|
|
issues: write
|
|
contents: read
|
|
|
|
jobs:
|
|
notify-codeowners:
|
|
name: Run
|
|
if: ${{ startsWith(github.event.label.name, format('component{0} ', ':')) }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Notify codeowners for component issues
|
|
uses: actions/github-script@v7.0.1
|
|
with:
|
|
script: |
|
|
const owner = context.repo.owner;
|
|
const repo = context.repo.repo;
|
|
const issue_number = context.payload.issue.number;
|
|
const labelName = context.payload.label.name;
|
|
|
|
console.log(`Processing issue #${issue_number} with label: ${labelName}`);
|
|
|
|
// Hidden marker to identify bot comments from this workflow
|
|
const BOT_COMMENT_MARKER = '<!-- issue-codeowner-notify-bot -->';
|
|
|
|
// Extract component name from label
|
|
const componentName = labelName.replace('component: ', '');
|
|
console.log(`Component: ${componentName}`);
|
|
|
|
try {
|
|
// Fetch CODEOWNERS file from root
|
|
const { data: codeownersFile } = await github.rest.repos.getContent({
|
|
owner,
|
|
repo,
|
|
path: 'CODEOWNERS'
|
|
});
|
|
const codeownersContent = Buffer.from(codeownersFile.content, 'base64').toString('utf8');
|
|
|
|
// Parse CODEOWNERS file to extract component mappings
|
|
const codeownersLines = codeownersContent.split('\n')
|
|
.map(line => line.trim())
|
|
.filter(line => line && !line.startsWith('#'));
|
|
|
|
let componentOwners = null;
|
|
|
|
for (const line of codeownersLines) {
|
|
const parts = line.split(/\s+/);
|
|
if (parts.length < 2) continue;
|
|
|
|
const pattern = parts[0];
|
|
const owners = parts.slice(1);
|
|
|
|
// Look for component patterns: esphome/components/{component}/*
|
|
const componentMatch = pattern.match(/^esphome\/components\/([^\/]+)\/\*$/);
|
|
if (componentMatch && componentMatch[1] === componentName) {
|
|
componentOwners = owners;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!componentOwners) {
|
|
console.log(`No codeowners found for component: ${componentName}`);
|
|
return;
|
|
}
|
|
|
|
console.log(`Found codeowners for '${componentName}': ${componentOwners.join(', ')}`);
|
|
|
|
// Separate users and teams
|
|
const userOwners = [];
|
|
const teamOwners = [];
|
|
|
|
for (const owner of componentOwners) {
|
|
const cleanOwner = owner.startsWith('@') ? owner.slice(1) : owner;
|
|
if (cleanOwner.includes('/')) {
|
|
// Team mention (org/team-name)
|
|
teamOwners.push(`@${cleanOwner}`);
|
|
} else {
|
|
// Individual user
|
|
userOwners.push(`@${cleanOwner}`);
|
|
}
|
|
}
|
|
|
|
// Remove issue author from mentions to avoid self-notification
|
|
const issueAuthor = context.payload.issue.user.login;
|
|
const filteredUserOwners = userOwners.filter(mention =>
|
|
mention !== `@${issueAuthor}`
|
|
);
|
|
|
|
// Check for previous comments from this workflow to avoid duplicate pings
|
|
const comments = await github.paginate(
|
|
github.rest.issues.listComments,
|
|
{
|
|
owner,
|
|
repo,
|
|
issue_number: issue_number
|
|
}
|
|
);
|
|
|
|
const previouslyPingedUsers = new Set();
|
|
const previouslyPingedTeams = new Set();
|
|
|
|
// Look for comments from github-actions bot that contain codeowner pings for this component
|
|
const workflowComments = comments.filter(comment =>
|
|
comment.user.type === 'Bot' &&
|
|
comment.body.includes(BOT_COMMENT_MARKER) &&
|
|
comment.body.includes(`component: ${componentName}`)
|
|
);
|
|
|
|
// Extract previously mentioned users and teams from workflow comments
|
|
for (const comment of workflowComments) {
|
|
// Match @username patterns (not team mentions)
|
|
const userMentions = comment.body.match(/@([a-zA-Z0-9_.-]+)(?![/])/g) || [];
|
|
userMentions.forEach(mention => {
|
|
previouslyPingedUsers.add(mention); // Keep @ prefix for easy comparison
|
|
});
|
|
|
|
// Match @org/team patterns
|
|
const teamMentions = comment.body.match(/@[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+/g) || [];
|
|
teamMentions.forEach(mention => {
|
|
previouslyPingedTeams.add(mention);
|
|
});
|
|
}
|
|
|
|
console.log(`Found ${previouslyPingedUsers.size} previously pinged users and ${previouslyPingedTeams.size} previously pinged teams for component ${componentName}`);
|
|
|
|
// Remove previously pinged users and teams
|
|
const newUserOwners = filteredUserOwners.filter(mention => !previouslyPingedUsers.has(mention));
|
|
const newTeamOwners = teamOwners.filter(mention => !previouslyPingedTeams.has(mention));
|
|
|
|
const allMentions = [...newUserOwners, ...newTeamOwners];
|
|
|
|
if (allMentions.length === 0) {
|
|
console.log('No new codeowners to notify (all previously pinged or issue author is the only codeowner)');
|
|
return;
|
|
}
|
|
|
|
// Create comment body
|
|
const mentionString = allMentions.join(', ');
|
|
const commentBody = `${BOT_COMMENT_MARKER}\n👋 Hey ${mentionString}!\n\nThis issue has been labeled with \`component: ${componentName}\` and you've been identified as a codeowner of this component. Please take a look when you have a chance!\n\nThanks for maintaining this component! 🙏`;
|
|
|
|
// Post comment
|
|
await github.rest.issues.createComment({
|
|
owner,
|
|
repo,
|
|
issue_number: issue_number,
|
|
body: commentBody
|
|
});
|
|
|
|
console.log(`Successfully notified new codeowners: ${mentionString}`);
|
|
|
|
} catch (error) {
|
|
console.log('Failed to process codeowner notifications:', error.message);
|
|
console.error(error);
|
|
}
|