mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00: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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
 | 
						|
        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);
 | 
						|
            }
 |